Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions core/engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,32 @@ impl<'ctx> ByteCompiler<'ctx> {
}
}

fn optimize(bytecode: &mut Vec<u8>, flags: CodeBlockFlags) {
// only perform Tail Call Optimisation in strict mode
if flags.contains(CodeBlockFlags::STRICT) && flags.is_ordinary() {
// if a sequence of Call, SetReturnValue, CheckReturn, Return is found
// replace Call with TailCall to allow frame elimination
for i in 0..bytecode.len() {
let opcode: Opcode = bytecode[i].into();
if !matches!(opcode, Opcode::Call) {
continue;
}
if bytecode.len() <= i + 5 {
return;
}

let second = bytecode[i + 2].into();
let third = bytecode[i + 3].into();
let fourth = bytecode[i + 4].into();
if let (Opcode::SetReturnValue, Opcode::CheckReturn, Opcode::Return) =
(second, third, fourth)
{
bytecode[i] = Opcode::TailCall as u8;
}
}
}
}

/// Finish compiling code with the [`ByteCompiler`] and return the generated [`CodeBlock`].
#[inline]
#[must_use]
Expand All @@ -1521,6 +1547,8 @@ impl<'ctx> ByteCompiler<'ctx> {
}
self.r#return(false);

Self::optimize(&mut self.bytecode, self.code_block_flags);

CodeBlock {
name: self.function_name,
length: self.length,
Expand Down
2 changes: 1 addition & 1 deletion core/engine/src/vm/call_frame/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl CallFrame {
/// \ /
/// ------------
/// |
/// function prolugue
/// function prologue
/// ```
pub(crate) const FUNCTION_PROLOGUE: usize = 2;
pub(crate) const THIS_POSITION: usize = 0;
Expand Down
73 changes: 54 additions & 19 deletions core/engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
unsafe impl Trace for CodeBlockFlags {
empty_trace!();
Expand Down Expand Up @@ -232,7 +273,7 @@ impl CodeBlock {
/// Check if the function is traced.
#[cfg(feature = "trace")]
pub(crate) fn traceable(&self) -> bool {
self.flags.get().contains(CodeBlockFlags::TRACEABLE)
self.flags.get().traceable()
}
/// Enable or disable instruction tracing to `stdout`.
#[cfg(feature = "trace")]
Expand All @@ -245,50 +286,42 @@ impl CodeBlock {

/// Check if the function is a class constructor.
pub(crate) fn is_class_constructor(&self) -> bool {
self.flags
.get()
.contains(CodeBlockFlags::IS_CLASS_CONSTRUCTOR)
self.flags.get().is_class_constructor()
}

/// Check if the function is in strict mode.
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.
pub(crate) fn has_binding_identifier(&self) -> bool {
self.flags
.get()
.contains(CodeBlockFlags::HAS_BINDING_IDENTIFIER)
self.flags.get().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.flags
.get()
.contains(CodeBlockFlags::IN_CLASS_FIELD_INITIALIZER)
self.flags.get().in_class_field_initializer()
}

/// Returns true if this function is a derived constructor.
pub(crate) fn is_derived_constructor(&self) -> bool {
self.flags
.get()
.contains(CodeBlockFlags::IS_DERIVED_CONSTRUCTOR)
self.flags.get().is_derived_constructor()
}

/// Returns true if this function an async function.
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.
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.
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.
Expand Down Expand Up @@ -444,6 +477,9 @@ impl CodeBlock {
| Instruction::Call {
argument_count: value,
}
| Instruction::TailCall {
argument_count: value,
}
| Instruction::New {
argument_count: value,
}
Expand Down Expand Up @@ -746,8 +782,7 @@ impl CodeBlock {
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved57 => unreachable!("Reserved opcodes are unreachable"),
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions core/engine/src/vm/flowgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ impl CodeBlock {
}
Instruction::CallEval { .. }
| Instruction::Call { .. }
| Instruction::TailCall { .. }
| Instruction::New { .. }
| Instruction::SuperCall { .. }
| Instruction::ConcatToString { .. }
Expand Down Expand Up @@ -515,8 +516,7 @@ impl CodeBlock {
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved57 => unreachable!("Reserved opcodes are unreachable"),
}
}

Expand Down
66 changes: 64 additions & 2 deletions core/engine/src/vm/opcode/call/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{
builtins::{promise::PromiseCapability, Promise},
builtins::{function::OrdinaryFunction, promise::PromiseCapability, Promise},
error::JsNativeError,
module::{ModuleKind, Referrer},
object::FunctionObjectBuilder,
vm::{opcode::Operation, CompletionType},
vm::{opcode::Operation, CallFrame, CompletionType},
Context, JsObject, JsResult, JsValue, NativeFunction,
};

Expand Down Expand Up @@ -200,6 +200,68 @@ impl Operation for Call {
}
}

/// `TailCall` implements the Opcode Operation for `Opcode::TailCall`
///
/// Operation:
/// - Tail call a function
#[derive(Debug, Clone, Copy)]
pub(crate) struct TailCall;

impl TailCall {
fn operation(context: &mut Context, argument_count: usize) -> JsResult<CompletionType> {
let at = context.vm.stack.len() - argument_count;
let func = context.vm.stack[at - 1].clone();

let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
.with_message("not a callable function")
.into());
};

let mut early_exit = false;
if object.is::<OrdinaryFunction>() {
if let Some(caller) = context.vm.pop_frame() {
let fp = caller.fp as usize;
context
.vm
.stack
.drain(fp..(at - CallFrame::FUNCTION_PROLOGUE));
early_exit = caller.exit_early();
}
}

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)
}
}

impl Operation for TailCall {
const NAME: &'static str = "TailCall";
const INSTRUCTION: &'static str = "INST - TailCall";
const COST: u8 = 3;

fn execute(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u8>();
Self::operation(context, argument_count as usize)
}

fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u16>() as usize;
Self::operation(context, argument_count)
}

fn execute_with_u32_operands(context: &mut Context) -> JsResult<CompletionType> {
let argument_count = context.vm.read::<u32>();
Self::operation(context, argument_count as usize)
}
}

#[derive(Debug, Clone, Copy)]
pub(crate) struct CallSpread;

Expand Down
9 changes: 7 additions & 2 deletions core/engine/src/vm/opcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,13 @@ generate_opcodes! {
/// Stack: this, func, argument_1, ... argument_n **=>** result
Call { argument_count: VaryingOperand },

/// Tail call a function.
///
/// Operands: argument_count: `u32`
///
/// Stack: this, func, argument_1, ... argument_n **=>**
TailCall { argument_count: VaryingOperand },

/// Call a function where the arguments contain spreads.
///
/// Operands:
Expand Down Expand Up @@ -2220,8 +2227,6 @@ generate_opcodes! {
Reserved56 => Reserved,
/// Reserved [`Opcode`].
Reserved57 => Reserved,
/// Reserved [`Opcode`].
Reserved58 => Reserved,
}

/// Specific opcodes for bindings.
Expand Down