Browse Source

Refactor call frame access to avoid panic checks (#3888)

pull/3895/head
raskad 5 months ago committed by GitHub
parent
commit
961d7b4d82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      core/engine/src/builtins/generator/mod.rs
  2. 10
      core/engine/src/context/mod.rs
  3. 4
      core/engine/src/module/source.rs
  4. 12
      core/engine/src/object/operations.rs
  5. 127
      core/engine/src/vm/mod.rs
  6. 9
      core/engine/src/vm/opcode/arguments.rs
  7. 4
      core/engine/src/vm/opcode/await/mod.rs
  8. 4
      core/engine/src/vm/opcode/control_flow/return.rs
  9. 10
      core/engine/src/vm/opcode/define/mod.rs
  10. 11
      core/engine/src/vm/opcode/delete/mod.rs
  11. 7
      core/engine/src/vm/opcode/iteration/loop_ops.rs
  12. 9
      core/engine/src/vm/opcode/push/literal.rs
  13. 5
      core/engine/src/vm/opcode/rest_parameter/mod.rs
  14. 22
      core/engine/src/vm/opcode/set/name.rs

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

@ -96,8 +96,9 @@ impl GeneratorContext {
let rp = frame.rp; let rp = frame.rp;
context.vm.push_frame(frame); context.vm.push_frame(frame);
context.vm.frame_mut().rp = rp; let frame = context.vm.frame_mut();
context.vm.frame_mut().set_exit_early(true); frame.rp = rp;
frame.set_exit_early(true);
if let Some(value) = value { if let Some(value) = value {
context.vm.push(value); context.vm.push(value);

10
core/engine/src/context/mod.rs

@ -828,6 +828,10 @@ impl Context {
// 1. If the execution context stack is empty, return null. // 1. If the execution context stack is empty, return null.
// 2. Let ec be the topmost execution context on the execution context stack whose ScriptOrModule component is not null. // 2. Let ec be the topmost execution context on the execution context stack whose ScriptOrModule component is not null.
// 3. If no such execution context exists, return null. Otherwise, return ec's ScriptOrModule. // 3. If no such execution context exists, return null. Otherwise, return ec's ScriptOrModule.
if let Some(active_runnable) = &self.vm.frame.active_runnable {
return Some(active_runnable.clone());
}
self.vm self.vm
.frames .frames
.iter() .iter()
@ -846,11 +850,7 @@ impl Context {
return self.vm.native_active_function.clone(); return self.vm.native_active_function.clone();
} }
if let Some(frame) = self.vm.frames.last() { self.vm.frame.function(&self.vm)
return frame.function(&self.vm);
}
None
} }
} }

4
core/engine/src/module/source.rs

@ -1772,9 +1772,7 @@ impl SourceTextModule {
context context
.vm .vm
.frames .frame
.last()
.expect("there should be a frame")
.set_promise_capability(&mut context.vm.stack, capability); .set_promise_capability(&mut context.vm.stack, capability);
// 9. If module.[[HasTLA]] is false, then // 9. If module.[[HasTLA]] is false, then

12
core/engine/src/object/operations.rs

@ -411,7 +411,11 @@ impl JsObject {
return Ok(context.vm.pop()); return Ok(context.vm.pop());
} }
context.vm.frames[frame_index].set_exit_early(true); if frame_index + 1 == context.vm.frames.len() {
context.vm.frame.set_exit_early(true);
} else {
context.vm.frames[frame_index + 1].set_exit_early(true);
}
let result = context.run().consume(); let result = context.run().consume();
@ -461,7 +465,11 @@ impl JsObject {
.clone()); .clone());
} }
context.vm.frames[frame_index].set_exit_early(true); if frame_index + 1 == context.vm.frames.len() {
context.vm.frame.set_exit_early(true);
} else {
context.vm.frames[frame_index + 1].set_exit_early(true);
}
let result = context.run().consume(); let result = context.run().consume();

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

@ -9,8 +9,9 @@ use crate::{
Context, JsError, JsNativeError, JsObject, JsResult, JsValue, Module, Context, JsError, JsNativeError, JsObject, JsResult, JsValue, Module,
}; };
use boa_gc::{custom_trace, Finalize, Trace}; use boa_gc::{custom_trace, Finalize, Gc, Trace};
use boa_profiler::Profiler; use boa_profiler::Profiler;
use boa_string::JsString;
use std::{future::Future, mem::size_of, ops::ControlFlow, pin::Pin, task}; use std::{future::Future, mem::size_of, ops::ControlFlow, pin::Pin, task};
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
@ -52,7 +53,18 @@ mod tests;
/// Virtual Machine. /// Virtual Machine.
#[derive(Debug)] #[derive(Debug)]
pub struct Vm { pub struct Vm {
/// The current call frame.
///
/// Whenever a new frame is pushed, it will be swaped into this field.
/// Then the old frame will get pushed to the [`Self::frames`] stack.
/// Whenever the current frame gets poped, the last frame on the [`Self::frames`] stack will be swaped into this field.
///
/// By default this is a dummy frame that gets pushed to [`Self::frames`] when the first real frame is pushed.
pub(crate) frame: CallFrame,
/// The stack for call frames.
pub(crate) frames: Vec<CallFrame>, pub(crate) frames: Vec<CallFrame>,
pub(crate) stack: Vec<JsValue>, pub(crate) stack: Vec<JsValue>,
pub(crate) return_value: JsValue, pub(crate) return_value: JsValue,
@ -100,6 +112,12 @@ impl Vm {
pub(crate) fn new(realm: Realm) -> Self { pub(crate) fn new(realm: Realm) -> Self {
Self { Self {
frames: Vec::with_capacity(16), frames: Vec::with_capacity(16),
frame: CallFrame::new(
Gc::new(CodeBlock::new(JsString::default(), 0, true)),
None,
EnvironmentStack::new(realm.environment().clone()),
realm.clone(),
),
stack: Vec::with_capacity(1024), stack: Vec::with_capacity(1024),
return_value: JsValue::undefined(), return_value: JsValue::undefined(),
environments: EnvironmentStack::new(realm.environment().clone()), environments: EnvironmentStack::new(realm.environment().clone()),
@ -132,29 +150,22 @@ impl Vm {
#[track_caller] #[track_caller]
pub(crate) fn read<T: Readable>(&mut self) -> T { pub(crate) fn read<T: Readable>(&mut self) -> T {
let value = self.frame().code_block.read::<T>(self.frame().pc as usize); let frame = self.frame_mut();
self.frame_mut().pc += size_of::<T>() as u32; let value = frame.code_block.read::<T>(frame.pc as usize);
frame.pc += size_of::<T>() as u32;
value value
} }
/// Retrieves the VM frame /// Retrieves the VM frame.
///
/// # Panics
///
/// If there is no frame, then this will panic.
#[track_caller] #[track_caller]
pub(crate) fn frame(&self) -> &CallFrame { pub(crate) fn frame(&self) -> &CallFrame {
self.frames.last().expect("no frame found") &self.frame
} }
/// Retrieves the VM frame mutably /// Retrieves the VM frame mutably.
///
/// # Panics
///
/// If there is no frame, then this will panic.
#[track_caller] #[track_caller]
pub(crate) fn frame_mut(&mut self) -> &mut CallFrame { pub(crate) fn frame_mut(&mut self) -> &mut CallFrame {
self.frames.last_mut().expect("no frame found") &mut self.frame
} }
pub(crate) fn push_frame(&mut self, mut frame: CallFrame) { pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
@ -177,9 +188,12 @@ impl Vm {
// Keep carrying the last active runnable in case the current callframe // Keep carrying the last active runnable in case the current callframe
// yields. // yields.
if frame.active_runnable.is_none() { if frame.active_runnable.is_none() {
frame.active_runnable = self.frames.last().and_then(|fr| fr.active_runnable.clone()); frame
.active_runnable
.clone_from(&self.frame.active_runnable);
} }
std::mem::swap(&mut self.frame, &mut frame);
self.frames.push(frame); self.frames.push(frame);
} }
@ -196,13 +210,14 @@ impl Vm {
} }
pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> { pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> {
let mut frame = self.frames.pop(); if let Some(mut frame) = self.frames.pop() {
if let Some(frame) = &mut frame { std::mem::swap(&mut self.frame, &mut frame);
std::mem::swap(&mut self.environments, &mut frame.environments); std::mem::swap(&mut self.environments, &mut frame.environments);
std::mem::swap(&mut self.realm, &mut frame.realm); std::mem::swap(&mut self.realm, &mut frame.realm);
Some(frame)
} else {
None
} }
frame
} }
/// Handles an exception thrown at position `pc`. /// Handles an exception thrown at position `pc`.
@ -271,16 +286,17 @@ impl Context {
const NUMBER_OF_COLUMNS: usize = 4; const NUMBER_OF_COLUMNS: usize = 4;
pub(crate) fn trace_call_frame(&self) { pub(crate) fn trace_call_frame(&self) {
let msg = if self.vm.frames.last().is_some() { let frame = self.vm.frame();
let msg = if self.vm.frames.is_empty() {
" VM Start ".to_string()
} else {
format!( format!(
" Call Frame -- {} ", " Call Frame -- {} ",
self.vm.frame().code_block().name().to_std_string_escaped() frame.code_block().name().to_std_string_escaped()
) )
} else {
" VM Start ".to_string()
}; };
println!("{}", self.vm.frame().code_block); println!("{}", frame.code_block);
println!( println!(
"{msg:-^width$}", "{msg:-^width$}",
width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10 width = Self::COLUMN_WIDTH * Self::NUMBER_OF_COLUMNS - 10
@ -300,16 +316,13 @@ impl Context {
where where
F: FnOnce(Opcode, &mut Context) -> JsResult<CompletionType>, F: FnOnce(Opcode, &mut Context) -> JsResult<CompletionType>,
{ {
let bytecodes = &self.vm.frame().code_block.bytecode; let frame = self.vm.frame();
let pc = self.vm.frame().pc as usize; let bytecodes = &frame.code_block.bytecode;
let pc = frame.pc as usize;
let (_, varying_operand_kind, instruction) = InstructionIterator::with_pc(bytecodes, pc) let (_, varying_operand_kind, instruction) = InstructionIterator::with_pc(bytecodes, pc)
.next() .next()
.expect("There should be an instruction left"); .expect("There should be an instruction left");
let operands = self let operands = frame.code_block.instruction_operands(&instruction);
.vm
.frame()
.code_block
.instruction_operands(&instruction);
let opcode = instruction.opcode(); let opcode = instruction.opcode();
match opcode { match opcode {
@ -332,12 +345,11 @@ impl Context {
let result = self.execute_instruction(f); let result = self.execute_instruction(f);
let duration = instant.elapsed(); let duration = instant.elapsed();
let fp = self let fp = if self.vm.frames.is_empty() {
.vm None
.frames } else {
.last() Some(self.vm.frame.fp() as usize)
.map(CallFrame::fp) };
.map(|fp| fp as usize);
let stack = { let stack = {
let mut stack = String::from("[ "); let mut stack = String::from("[ ");
@ -434,14 +446,16 @@ impl Context {
if !err.is_catchable() { if !err.is_catchable() {
let mut fp = self.vm.stack.len(); let mut fp = self.vm.stack.len();
let mut env_fp = self.vm.environments.len(); let mut env_fp = self.vm.environments.len();
while let Some(frame) = self.vm.frames.last() { loop {
if frame.exit_early() { if self.vm.frame.exit_early() {
break; break;
} }
fp = frame.fp() as usize; fp = self.vm.frame.fp() as usize;
env_fp = frame.env_fp as usize; env_fp = self.vm.frame.env_fp as usize;
self.vm.pop_frame(); if self.vm.pop_frame().is_none() {
break;
}
} }
self.vm.environments.truncate(env_fp); self.vm.environments.truncate(env_fp);
self.vm.stack.truncate(fp); self.vm.stack.truncate(fp);
@ -466,11 +480,13 @@ impl Context {
match result { match result {
CompletionType::Normal => {} CompletionType::Normal => {}
CompletionType::Return => { CompletionType::Return => {
let fp = self.vm.frame().fp() as usize; let frame = self.vm.frame();
let fp = frame.fp() as usize;
let exit_early = frame.exit_early();
self.vm.stack.truncate(fp); self.vm.stack.truncate(fp);
let result = self.vm.take_return_value(); let result = self.vm.take_return_value();
if self.vm.frame().exit_early() { if exit_early {
return ControlFlow::Break(CompletionRecord::Normal(result)); return ControlFlow::Break(CompletionRecord::Normal(result));
} }
@ -478,9 +494,10 @@ impl Context {
self.vm.pop_frame(); self.vm.pop_frame();
} }
CompletionType::Throw => { CompletionType::Throw => {
let mut fp = self.vm.frame().fp(); let frame = self.vm.frame();
let mut env_fp = self.vm.frame().env_fp; let mut fp = frame.fp();
if self.vm.frame().exit_early() { let mut env_fp = frame.env_fp;
if frame.exit_early() {
self.vm.environments.truncate(env_fp as usize); self.vm.environments.truncate(env_fp as usize);
self.vm.stack.truncate(fp as usize); self.vm.stack.truncate(fp as usize);
return ControlFlow::Break(CompletionRecord::Throw( return ControlFlow::Break(CompletionRecord::Throw(
@ -493,11 +510,11 @@ impl Context {
self.vm.pop_frame(); self.vm.pop_frame();
while let Some(frame) = self.vm.frames.last_mut() { loop {
fp = frame.fp(); fp = self.vm.frame.fp();
env_fp = frame.env_fp; env_fp = self.vm.frame.env_fp;
let pc = frame.pc; let pc = self.vm.frame.pc;
let exit_early = frame.exit_early(); let exit_early = self.vm.frame.exit_early();
if self.vm.handle_exception_at(pc) { if self.vm.handle_exception_at(pc) {
return ControlFlow::Continue(()); return ControlFlow::Continue(());
@ -512,7 +529,9 @@ impl Context {
)); ));
} }
self.vm.pop_frame(); if self.vm.pop_frame().is_none() {
break;
}
} }
self.vm.environments.truncate(env_fp as usize); self.vm.environments.truncate(env_fp as usize);
self.vm.stack.truncate(fp as usize); self.vm.stack.truncate(fp as usize);

9
core/engine/src/vm/opcode/arguments.rs

@ -19,14 +19,13 @@ impl Operation for CreateMappedArgumentsObject {
const COST: u8 = 8; const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let function_object = context let frame = context.vm.frame();
.vm let function_object = frame
.frame()
.function(&context.vm) .function(&context.vm)
.clone() .clone()
.expect("there should be a function object"); .expect("there should be a function object");
let code = context.vm.frame().code_block().clone(); let code = frame.code_block().clone();
let args = context.vm.frame().arguments(&context.vm).to_vec(); let args = frame.arguments(&context.vm).to_vec();
let env = context.vm.environments.current_ref(); let env = context.vm.environments.current_ref();
let arguments = MappedArguments::new( let arguments = MappedArguments::new(

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

@ -183,9 +183,7 @@ impl Operation for CreatePromiseCapability {
context context
.vm .vm
.frames .frame
.last()
.expect("there should be a frame")
.set_promise_capability(&mut context.vm.stack, Some(&promise_capability)); .set_promise_capability(&mut context.vm.stack, Some(&promise_capability));
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }

4
core/engine/src/vm/opcode/control_flow/return.rs

@ -33,10 +33,10 @@ impl Operation for CheckReturn {
const COST: u8 = 3; const COST: u8 = 3;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
if !context.vm.frame().construct() { let frame = context.vm.frame();
if !frame.construct() {
return Ok(CompletionType::Normal); return Ok(CompletionType::Normal);
} }
let frame = context.vm.frame();
let this = frame.this(&context.vm); let this = frame.this(&context.vm);
let result = context.vm.take_return_value(); let result = context.vm.take_return_value();

10
core/engine/src/vm/opcode/define/mod.rs

@ -62,13 +62,11 @@ pub(crate) struct DefInitVar;
impl DefInitVar { impl DefInitVar {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let value = context.vm.pop(); let value = context.vm.pop();
let mut binding_locator = context.vm.frame().code_block.bindings[index].clone(); let frame = context.vm.frame();
let strict = frame.code_block.strict();
let mut binding_locator = frame.code_block.bindings[index].clone();
context.find_runtime_binding(&mut binding_locator)?; context.find_runtime_binding(&mut binding_locator)?;
context.set_binding( context.set_binding(&binding_locator, value, strict)?;
&binding_locator,
value,
context.vm.frame().code_block.strict(),
)?;
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }

11
core/engine/src/vm/opcode/delete/mod.rs

@ -16,15 +16,12 @@ impl DeletePropertyByName {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let value = context.vm.pop(); let value = context.vm.pop();
let object = value.to_object(context)?; let object = value.to_object(context)?;
let key = context let code_block = context.vm.frame().code_block();
.vm let key = code_block.constant_string(index).into();
.frame() let strict = code_block.strict();
.code_block()
.constant_string(index)
.into();
let result = object.__delete__(&key, &mut InternalMethodContext::new(context))?; let result = object.__delete__(&key, &mut InternalMethodContext::new(context))?;
if !result && context.vm.frame().code_block().strict() { if !result && strict {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
.with_message("Cannot delete property") .with_message("Cannot delete property")
.into()); .into());

7
core/engine/src/vm/opcode/iteration/loop_ops.rs

@ -17,16 +17,17 @@ impl Operation for IncrementLoopIteration {
const COST: u8 = 3; const COST: u8 = 3;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let previous_iteration_count = context.vm.frame_mut().loop_iteration_count;
let max = context.vm.runtime_limits.loop_iteration_limit(); let max = context.vm.runtime_limits.loop_iteration_limit();
let frame = context.vm.frame_mut();
let previous_iteration_count = frame.loop_iteration_count;
if previous_iteration_count > max { if previous_iteration_count > max {
return Err(JsNativeError::runtime_limit() return Err(JsNativeError::runtime_limit()
.with_message(format!("Maximum loop iteration limit {max} exceeded")) .with_message(format!("Maximum loop iteration limit {max} exceeded"))
.into()); .into());
} }
context.vm.frame_mut().loop_iteration_count = previous_iteration_count.wrapping_add(1); frame.loop_iteration_count = previous_iteration_count.wrapping_add(1);
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

9
core/engine/src/vm/opcode/push/literal.rs

@ -59,12 +59,9 @@ impl PushRegExp {
pattern_index: usize, pattern_index: usize,
flags_index: usize, flags_index: usize,
) -> JsResult<CompletionType> { ) -> JsResult<CompletionType> {
let pattern = context let code_block = context.vm.frame().code_block();
.vm let pattern = code_block.constant_string(pattern_index);
.frame() let flags = code_block.constant_string(flags_index);
.code_block()
.constant_string(pattern_index);
let flags = context.vm.frame().code_block().constant_string(flags_index);
let regexp = JsRegExp::new(pattern, flags, context)?; let regexp = JsRegExp::new(pattern, flags, context)?;
context.vm.push(regexp); context.vm.push(regexp);

5
core/engine/src/vm/opcode/rest_parameter/mod.rs

@ -17,8 +17,9 @@ impl Operation for RestParameterInit {
const COST: u8 = 6; const COST: u8 = 6;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.frame().argument_count; let frame = context.vm.frame();
let param_count = context.vm.frame().code_block().parameter_length; let argument_count = frame.argument_count;
let param_count = frame.code_block().parameter_length;
let array = if argument_count >= param_count { let array = if argument_count >= param_count {
let rest_count = argument_count - param_count + 1; let rest_count = argument_count - param_count + 1;

22
core/engine/src/vm/opcode/set/name.rs

@ -54,18 +54,16 @@ pub(crate) struct SetName;
impl SetName { impl SetName {
fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context, index: usize) -> JsResult<CompletionType> {
let mut binding_locator = context.vm.frame().code_block.bindings[index].clone(); let code_block = context.vm.frame().code_block();
let mut binding_locator = code_block.bindings[index].clone();
let strict = code_block.strict();
let value = context.vm.pop(); let value = context.vm.pop();
context.find_runtime_binding(&mut binding_locator)?; context.find_runtime_binding(&mut binding_locator)?;
verify_initialized(&binding_locator, context)?; verify_initialized(&binding_locator, context)?;
context.set_binding( context.set_binding(&binding_locator, value, strict)?;
&binding_locator,
value,
context.vm.frame().code_block.strict(),
)?;
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
@ -105,9 +103,9 @@ impl Operation for SetNameByLocator {
const COST: u8 = 4; const COST: u8 = 4;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let binding_locator = context let frame = context.vm.frame_mut();
.vm let strict = frame.code_block.strict();
.frame_mut() let binding_locator = frame
.binding_stack .binding_stack
.pop() .pop()
.expect("locator should have been popped before"); .expect("locator should have been popped before");
@ -115,11 +113,7 @@ impl Operation for SetNameByLocator {
verify_initialized(&binding_locator, context)?; verify_initialized(&binding_locator, context)?;
context.set_binding( context.set_binding(&binding_locator, value, strict)?;
&binding_locator,
value,
context.vm.frame().code_block.strict(),
)?;
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }

Loading…
Cancel
Save