Browse Source

Fix panics and refactor code

tco
Haled Odat 1 year ago committed by RageKnify
parent
commit
a7da92ab71
No known key found for this signature in database
  1. 29
      core/engine/src/bytecompiler/mod.rs
  2. 67
      core/engine/src/vm/code_block.rs
  3. 46
      core/engine/src/vm/opcode/call/mod.rs

29
core/engine/src/bytecompiler/mod.rs

@ -1512,24 +1512,25 @@ impl<'ctx> ByteCompiler<'ctx> {
fn optimize(bytecode: &mut Vec<u8>, flags: CodeBlockFlags) { fn optimize(bytecode: &mut Vec<u8>, flags: CodeBlockFlags) {
// only perform Tail Call Optimisation in strict mode // only perform Tail Call Optimisation in strict mode
if flags.contains(CodeBlockFlags::STRICT) { if flags.contains(CodeBlockFlags::STRICT) && flags.is_ordinary() {
// if a sequence of Call, SetReturnValue, CheckReturn, Return is found // if a sequence of Call, SetReturnValue, CheckReturn, Return is found
// replace Call with TailCall to allow frame elimination // replace Call with TailCall to allow frame elimination
for i in 0..bytecode.len() { for i in 0..bytecode.len() {
let opcode: Opcode = bytecode[i].into(); let opcode: Opcode = bytecode[i].into();
if matches!(opcode, Opcode::Call) { if !matches!(opcode, Opcode::Call) {
if bytecode.len() > i + 5 { continue;
let second = bytecode[i + 2].into(); }
let third = bytecode[i + 3].into(); if bytecode.len() <= i + 5 {
let fourth = bytecode[i + 4].into(); return;
if let (Opcode::SetReturnValue, Opcode::CheckReturn, Opcode::Return) = }
(second, third, fourth)
{ let second = bytecode[i + 2].into();
bytecode[i] = Opcode::TailCall as u8; let third = bytecode[i + 3].into();
} let fourth = bytecode[i + 4].into();
} else { if let (Opcode::SetReturnValue, Opcode::CheckReturn, Opcode::Return) =
return; (second, third, fourth)
} {
bytecode[i] = Opcode::TailCall as u8;
} }
} }
} }

67
core/engine/src/vm/code_block.rs

@ -76,6 +76,47 @@ bitflags! {
} }
} }
impl CodeBlockFlags {
/// Check if the function is traced.
#[cfg(feature = "trace")]
pub(crate) fn traceable(&self) -> bool {
self.contains(CodeBlockFlags::TRACEABLE)
}
/// Check if the function is a class constructor.
pub(crate) fn is_class_constructor(&self) -> bool {
self.contains(CodeBlockFlags::IS_CLASS_CONSTRUCTOR)
}
/// Check if the function is in strict mode.
pub(crate) fn strict(&self) -> bool {
self.contains(CodeBlockFlags::STRICT)
}
/// Indicates if the function is an expression and has a binding identifier.
pub(crate) fn has_binding_identifier(&self) -> bool {
self.contains(CodeBlockFlags::HAS_BINDING_IDENTIFIER)
}
/// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value.
pub(crate) fn in_class_field_initializer(&self) -> bool {
self.contains(CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER)
}
/// Returns true if this function is a derived constructor.
pub(crate) fn is_derived_constructor(&self) -> bool {
self.contains(CodeBlockFlags::IS_DERIVED_CONSTRUCTOR)
}
/// Returns true if this function an async function.
pub(crate) fn is_async(self) -> bool {
self.contains(CodeBlockFlags::IS_ASYNC)
}
/// Returns true if this function an generator function.
pub(crate) fn is_generator(self) -> bool {
self.contains(CodeBlockFlags::IS_GENERATOR)
}
/// Returns true if this function an async function.
pub(crate) fn is_ordinary(self) -> bool {
!self.is_async() && !self.is_generator()
}
}
// SAFETY: Nothing in CodeBlockFlags needs tracing, so this is safe. // SAFETY: Nothing in CodeBlockFlags needs tracing, so this is safe.
unsafe impl Trace for CodeBlockFlags { unsafe impl Trace for CodeBlockFlags {
empty_trace!(); empty_trace!();
@ -232,7 +273,7 @@ impl CodeBlock {
/// Check if the function is traced. /// Check if the function is traced.
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
pub(crate) fn traceable(&self) -> bool { pub(crate) fn traceable(&self) -> bool {
self.flags.get().contains(CodeBlockFlags::TRACEABLE) self.flags.get().traceable()
} }
/// Enable or disable instruction tracing to `stdout`. /// Enable or disable instruction tracing to `stdout`.
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
@ -245,50 +286,42 @@ impl CodeBlock {
/// Check if the function is a class constructor. /// Check if the function is a class constructor.
pub(crate) fn is_class_constructor(&self) -> bool { pub(crate) fn is_class_constructor(&self) -> bool {
self.flags self.flags.get().is_class_constructor()
.get()
.contains(CodeBlockFlags::IS_CLASS_CONSTRUCTOR)
} }
/// Check if the function is in strict mode. /// Check if the function is in strict mode.
pub(crate) fn strict(&self) -> bool { pub(crate) fn strict(&self) -> bool {
self.flags.get().contains(CodeBlockFlags::STRICT) self.flags.get().strict()
} }
/// Indicates if the function is an expression and has a binding identifier. /// Indicates if the function is an expression and has a binding identifier.
pub(crate) fn has_binding_identifier(&self) -> bool { pub(crate) fn has_binding_identifier(&self) -> bool {
self.flags self.flags.get().has_binding_identifier()
.get()
.contains(CodeBlockFlags::HAS_BINDING_IDENTIFIER)
} }
/// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value. /// Does this function have the `[[ClassFieldInitializerName]]` internal slot set to non-empty value.
pub(crate) fn in_class_field_initializer(&self) -> bool { pub(crate) fn in_class_field_initializer(&self) -> bool {
self.flags self.flags.get().in_class_field_initializer()
.get()
.contains(CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER)
} }
/// Returns true if this function is a derived constructor. /// Returns true if this function is a derived constructor.
pub(crate) fn is_derived_constructor(&self) -> bool { pub(crate) fn is_derived_constructor(&self) -> bool {
self.flags self.flags.get().is_derived_constructor()
.get()
.contains(CodeBlockFlags::IS_DERIVED_CONSTRUCTOR)
} }
/// Returns true if this function an async function. /// Returns true if this function an async function.
pub(crate) fn is_async(&self) -> bool { pub(crate) fn is_async(&self) -> bool {
self.flags.get().contains(CodeBlockFlags::IS_ASYNC) self.flags.get().is_async()
} }
/// Returns true if this function an generator function. /// Returns true if this function an generator function.
pub(crate) fn is_generator(&self) -> bool { pub(crate) fn is_generator(&self) -> bool {
self.flags.get().contains(CodeBlockFlags::IS_GENERATOR) self.flags.get().is_generator()
} }
/// Returns true if this function an async function. /// Returns true if this function an async function.
pub(crate) fn is_ordinary(&self) -> bool { pub(crate) fn is_ordinary(&self) -> bool {
!self.is_async() && !self.is_generator() self.flags.get().is_ordinary()
} }
/// Returns true if this function has the `"prototype"` property when function object is created. /// Returns true if this function has the `"prototype"` property when function object is created.

46
core/engine/src/vm/opcode/call/mod.rs

@ -3,7 +3,7 @@ use crate::{
error::JsNativeError, error::JsNativeError,
module::{ModuleKind, Referrer}, module::{ModuleKind, Referrer},
object::FunctionObjectBuilder, object::FunctionObjectBuilder,
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CallFrame, CompletionType},
Context, JsObject, JsResult, JsValue, NativeFunction, Context, JsObject, JsResult, JsValue, NativeFunction,
}; };
@ -210,7 +210,7 @@ pub(crate) struct TailCall;
impl TailCall { impl TailCall {
fn operation(context: &mut Context, argument_count: usize) -> JsResult<CompletionType> { fn operation(context: &mut Context, argument_count: usize) -> JsResult<CompletionType> {
let at = context.vm.stack.len() - argument_count; let at = context.vm.stack.len() - argument_count;
let func = &context.vm.stack[at - 1]; let func = context.vm.stack[at - 1].clone();
let Some(object) = func.as_object() else { let Some(object) = func.as_object() else {
return Err(JsNativeError::typ() return Err(JsNativeError::typ()
@ -218,39 +218,25 @@ impl TailCall {
.into()); .into());
}; };
let is_ordinary_function = object.is::<OrdinaryFunction>(); let mut early_exit = false;
object.__call__(argument_count).resolve(context)?; if object.is::<OrdinaryFunction>() {
if let Some(caller) = context.vm.pop_frame() {
// only tail call for ordinary functions let fp = caller.fp as usize;
// don't tail call on the main script
// TODO: the 3 needs to be reviewed
if is_ordinary_function && context.vm.frames.len() > 3 {
// check that caller is also ordinary function
let frames = &context.vm.frames;
let caller_frame = &frames[frames.len() - 2];
let caller_function = caller_frame
.function(&context.vm)
.expect("there must be a caller function");
if caller_function.is::<OrdinaryFunction>() {
// remove caller's CallFrame
let frames = &mut context.vm.frames;
let caller_frame = frames.swap_remove(frames.len() - 2);
// remove caller's prologue from stack
// this + func + arguments
let to_remove = 1 + 1 + caller_frame.argument_count as usize;
context context
.vm .vm
.stack .stack
.drain((caller_frame.fp as usize)..(caller_frame.fp as usize + to_remove)); .drain(fp..(at - CallFrame::FUNCTION_PROLOGUE));
early_exit = caller.exit_early();
// update invoked function's fp
let frames = &mut context.vm.frames;
let invoked_frame = frames.last_mut().expect("invoked frame must exist");
invoked_frame.set_exit_early(caller_frame.exit_early());
invoked_frame.fp -= to_remove as u32;
} }
} }
object.__call__(argument_count).resolve(context)?;
// If the caller is early exit, since it has been poped from the frames
// we have to mark the current frame as early exit.
if early_exit {
context.vm.frame_mut().set_exit_early(true);
}
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

Loading…
Cancel
Save