diff --git a/core/engine/src/builtins/array/mod.rs b/core/engine/src/builtins/array/mod.rs index 45f4f0dab1..947f76d0ca 100644 --- a/core/engine/src/builtins/array/mod.rs +++ b/core/engine/src/builtins/array/mod.rs @@ -382,7 +382,7 @@ impl Array { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createarrayfromlist - pub(crate) fn create_array_from_list(elements: I, context: &mut Context) -> JsObject + pub(crate) fn create_array_from_list(elements: I, context: &Context) -> JsObject where I: IntoIterator, { diff --git a/core/engine/src/builtins/eval/mod.rs b/core/engine/src/builtins/eval/mod.rs index 527bd8e786..75b8448580 100644 --- a/core/engine/src/builtins/eval/mod.rs +++ b/core/engine/src/builtins/eval/mod.rs @@ -254,6 +254,8 @@ impl Eval { false, var_env.clone(), lex_env.clone(), + false, + false, context.interner_mut(), in_with, ); diff --git a/core/engine/src/builtins/json/mod.rs b/core/engine/src/builtins/json/mod.rs index 06e047ea7c..08dcdd7d54 100644 --- a/core/engine/src/builtins/json/mod.rs +++ b/core/engine/src/builtins/json/mod.rs @@ -120,6 +120,8 @@ impl Json { true, context.realm().environment().compile_env(), context.realm().environment().compile_env(), + false, + false, context.interner_mut(), in_with, ); diff --git a/core/engine/src/bytecompiler/class.rs b/core/engine/src/bytecompiler/class.rs index baddbf1bfd..81bfd1178a 100644 --- a/core/engine/src/bytecompiler/class.rs +++ b/core/engine/src/bytecompiler/class.rs @@ -92,6 +92,8 @@ impl ByteCompiler<'_> { self.json_parse, self.variable_environment.clone(), self.lexical_environment.clone(), + false, + false, self.interner, self.in_with, ); @@ -274,6 +276,8 @@ impl ByteCompiler<'_> { self.json_parse, self.variable_environment.clone(), self.lexical_environment.clone(), + false, + false, self.interner, self.in_with, ); @@ -308,6 +312,8 @@ impl ByteCompiler<'_> { self.json_parse, self.variable_environment.clone(), self.lexical_environment.clone(), + false, + false, self.interner, self.in_with, ); @@ -347,6 +353,8 @@ impl ByteCompiler<'_> { self.json_parse, self.variable_environment.clone(), self.lexical_environment.clone(), + false, + false, self.interner, self.in_with, ); @@ -388,6 +396,8 @@ impl ByteCompiler<'_> { false, self.variable_environment.clone(), self.lexical_environment.clone(), + false, + false, self.interner, self.in_with, ); diff --git a/core/engine/src/bytecompiler/function.rs b/core/engine/src/bytecompiler/function.rs index fceb90ce13..b06fcf7006 100644 --- a/core/engine/src/bytecompiler/function.rs +++ b/core/engine/src/bytecompiler/function.rs @@ -112,16 +112,12 @@ impl FunctionCompiler { false, variable_environment, lexical_environment, + self.r#async, + self.generator, interner, self.in_with, ); compiler.length = length; - compiler - .code_block_flags - .set(CodeBlockFlags::IS_ASYNC, self.r#async); - compiler - .code_block_flags - .set(CodeBlockFlags::IS_GENERATOR, self.generator); compiler.code_block_flags.set( CodeBlockFlags::HAS_PROTOTYPE_PROPERTY, !self.arrow && !self.method && !self.r#async && !self.generator, diff --git a/core/engine/src/bytecompiler/mod.rs b/core/engine/src/bytecompiler/mod.rs index 2d9f3000aa..74073baf20 100644 --- a/core/engine/src/bytecompiler/mod.rs +++ b/core/engine/src/bytecompiler/mod.rs @@ -8,6 +8,7 @@ mod expression; mod function; mod jump_control; mod module; +mod register; mod statement; mod utils; @@ -18,8 +19,8 @@ use crate::{ environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment}, js_string, vm::{ - BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler, - InlineCache, Opcode, VaryingOperandKind, + BindingOpcode, CallFrame, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, + Handler, InlineCache, Opcode, VaryingOperandKind, }, JsBigInt, JsStr, JsString, }; @@ -54,6 +55,7 @@ pub(crate) use declarations::{ }; pub(crate) use function::FunctionCompiler; pub(crate) use jump_control::JumpControlInfo; +pub(crate) use register::*; pub(crate) trait ToJsString { fn to_js_string(&self, interner: &Interner) -> JsString; @@ -384,7 +386,7 @@ pub struct ByteCompiler<'ctx> { /// The number of arguments expected. pub(crate) length: u32, - pub(crate) register_count: u32, + pub(crate) register_allocator: RegisterAllocator, /// `[[ThisMode]]` pub(crate) this_mode: ThisMode, @@ -408,7 +410,7 @@ pub struct ByteCompiler<'ctx> { pub(crate) current_open_environments_count: u32, current_stack_value_count: u32, - pub(crate) code_block_flags: CodeBlockFlags, + code_block_flags: CodeBlockFlags, handlers: ThinVec, pub(crate) ic: Vec, literals_map: FxHashMap, @@ -441,18 +443,53 @@ impl<'ctx> ByteCompiler<'ctx> { /// Creates a new [`ByteCompiler`]. #[inline] + #[allow(clippy::too_many_arguments)] + #[allow(clippy::fn_params_excessive_bools)] pub(crate) fn new( name: JsString, strict: bool, json_parse: bool, variable_environment: Rc, lexical_environment: Rc, + is_async: bool, + is_generator: bool, interner: &'ctx mut Interner, in_with: bool, ) -> ByteCompiler<'ctx> { let mut code_block_flags = CodeBlockFlags::empty(); code_block_flags.set(CodeBlockFlags::STRICT, strict); + code_block_flags.set(CodeBlockFlags::IS_ASYNC, is_async); + code_block_flags.set(CodeBlockFlags::IS_GENERATOR, is_generator); code_block_flags |= CodeBlockFlags::HAS_PROTOTYPE_PROPERTY; + + let mut register_allocator = RegisterAllocator::default(); + if is_async { + let promise_register = register_allocator.alloc_persistent(); + let resolve_register = register_allocator.alloc_persistent(); + let reject_register = register_allocator.alloc_persistent(); + + debug_assert_eq!( + promise_register.index(), + CallFrame::PROMISE_CAPABILITY_PROMISE_REGISTER_INDEX + ); + debug_assert_eq!( + resolve_register.index(), + CallFrame::PROMISE_CAPABILITY_RESOLVE_REGISTER_INDEX + ); + debug_assert_eq!( + reject_register.index(), + CallFrame::PROMISE_CAPABILITY_REJECT_REGISTER_INDEX + ); + + if is_generator { + let async_function_object_register = register_allocator.alloc_persistent(); + debug_assert_eq!( + async_function_object_register.index(), + CallFrame::ASYNC_GENERATOR_OBJECT_REGISTER_INDEX + ); + } + } + Self { function_name: name, length: 0, @@ -463,7 +500,7 @@ impl<'ctx> ByteCompiler<'ctx> { params: FormalParameterList::default(), current_open_environments_count: 0, - register_count: 0, + register_allocator, current_stack_value_count: 0, code_block_flags, handlers: ThinVec::default(), @@ -640,6 +677,17 @@ impl<'ctx> ByteCompiler<'ctx> { } } + /// TODO: Temporary function, remove once transition is complete. + #[allow(unused)] + fn pop_into_register(&mut self, dst: &Register) { + self.emit(Opcode::PopIntoRegister, &[Operand::Varying(dst.index())]); + } + /// TODO: Temporary function, remove once transition is complete. + #[allow(unused)] + fn push_from_register(&mut self, src: &Register) { + self.emit(Opcode::PushFromRegister, &[Operand::Varying(src.index())]); + } + /// Emits an opcode with one varying operand. /// /// Simpler version of [`ByteCompiler::emit()`]. @@ -1689,19 +1737,12 @@ impl<'ctx> ByteCompiler<'ctx> { } self.r#return(false); - if self.is_async() { - // NOTE: +3 for the promise capability - self.register_count += 3; - if self.is_generator() { - // NOTE: +1 for the async generator function - self.register_count += 1; - } - } + let register_count = self.register_allocator.finish(); // NOTE: Offset the handlers stack count so we don't pop the registers // when a exception is thrown. for handler in &mut self.handlers { - handler.stack_count += self.register_count; + handler.stack_count += register_count; } let mapped_arguments_binding_indices = if self.emitted_mapped_arguments_object_opcode { @@ -1713,7 +1754,7 @@ impl<'ctx> ByteCompiler<'ctx> { CodeBlock { name: self.function_name, length: self.length, - register_count: self.register_count, + register_count, this_mode: self.this_mode, parameter_length: self.params.as_ref().len() as u32, mapped_arguments_binding_indices, diff --git a/core/engine/src/bytecompiler/register.rs b/core/engine/src/bytecompiler/register.rs new file mode 100644 index 0000000000..b0c288637b --- /dev/null +++ b/core/engine/src/bytecompiler/register.rs @@ -0,0 +1,140 @@ +use std::mem::forget; + +bitflags::bitflags! { + #[derive(Debug, Default, Clone, Copy)] + struct RegisterFlags: u8 { + /// Whether the register is still in use (not deallocated). + const USED = 0b0000_0001; + + /// Is the register presistent (not deallocatable). + const PERSISTENT = 0b0000_0010; + } +} + +impl RegisterFlags { + fn is_used(self) -> bool { + self.contains(Self::USED) + } + fn is_persistent(self) -> bool { + self.contains(Self::PERSISTENT) + } +} + +/// An entry in the [`RegisterAllocator`]. +#[derive(Debug, Default)] +pub(crate) struct RegisterEntry { + flags: RegisterFlags, +} + +/// Represent a VM register. +/// +/// This is intented to be passed by reference or to be moved, dropping this is a bug, +/// it should only be dropped though the [`RegisterAllocator::dealloc()`] method. +/// This doesn't apply to persistent registers. +/// +/// A [`Register`] is index into the register allocator, +/// as well as an index into the registers on the stack using the register pointer (`rp`). +#[derive(Debug)] +pub(crate) struct Register { + index: u32, + flags: RegisterFlags, +} + +impl Register { + /// The index of the [`Register`]. + pub(crate) fn index(&self) -> u32 { + self.index + } +} + +impl Drop for Register { + /// This method should never be called. + /// It is used to detect when a register has not been deallocated. + fn drop(&mut self) { + if self.flags.is_persistent() { + return; + } + + // Prevent double panic. + if std::thread::panicking() { + return; + } + + unreachable!("forgot to deallocate a register!") + } +} + +#[derive(Debug, Default)] +pub(crate) struct RegisterAllocator { + registers: Vec, +} + +impl RegisterAllocator { + pub(crate) fn alloc(&mut self) -> Register { + if let Some((i, register)) = self + .registers + .iter_mut() + .filter(|reg| !reg.flags.is_used()) + .enumerate() + .next() + { + assert!(!register.flags.is_persistent()); + + register.flags |= RegisterFlags::USED; + return Register { + index: i as u32, + flags: register.flags, + }; + } + + let flags = RegisterFlags::USED; + + let index = self.registers.len() as u32; + self.registers.push(RegisterEntry { flags }); + + Register { index, flags } + } + + pub(crate) fn alloc_persistent(&mut self) -> Register { + let mut reg = self.alloc(); + + let index = reg.index(); + + let register = &mut self.registers[index as usize]; + + register.flags |= RegisterFlags::PERSISTENT; + + reg.flags = register.flags; + reg + } + + #[allow(unused)] + pub(crate) fn dealloc(&mut self, reg: Register) { + assert!( + !reg.flags.is_persistent(), + "Trying to deallocate a persistent register" + ); + + let register = &mut self.registers[reg.index as usize]; + + assert!( + register.flags.is_used(), + "Cannot deallocate unused variable" + ); + register.flags.set(RegisterFlags::USED, false); + + // NOTE: We should not drop it since, dropping it used to detect bugs. + forget(reg); + } + + pub(crate) fn finish(self) -> u32 { + for register in &self.registers { + debug_assert!( + !register.flags.is_used() + || (register.flags.is_used() && register.flags.is_persistent()) + ); + } + + self.registers.len() as u32 + } +} diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index c8a22437e3..d35a4ca521 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -28,7 +28,7 @@ use crate::{ realm::Realm, vm::{ create_function_object_fast, ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock, - CodeBlockFlags, CompletionRecord, Opcode, + CompletionRecord, Opcode, }, Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction, }; @@ -1432,11 +1432,12 @@ impl SourceTextModule { false, env.clone(), env.clone(), + true, + false, context.interner_mut(), false, ); - compiler.code_block_flags |= CodeBlockFlags::IS_ASYNC; compiler.async_handler = Some(compiler.push_handler()); let mut imports = Vec::new(); diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 20f60a8ff7..381fe6a32c 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -286,6 +286,8 @@ impl SyntheticModule { false, module_compile_env.clone(), module_compile_env.clone(), + false, + false, context.interner_mut(), false, ); diff --git a/core/engine/src/script.rs b/core/engine/src/script.rs index 31152ee889..17c76a4550 100644 --- a/core/engine/src/script.rs +++ b/core/engine/src/script.rs @@ -134,6 +134,8 @@ impl Script { false, self.inner.realm.environment().compile_env(), self.inner.realm.environment().compile_env(), + false, + false, context.interner_mut(), false, ); diff --git a/core/engine/src/vm/call_frame/mod.rs b/core/engine/src/vm/call_frame/mod.rs index 92ee7f13c0..1aee80f95b 100644 --- a/core/engine/src/vm/call_frame/mod.rs +++ b/core/engine/src/vm/call_frame/mod.rs @@ -43,6 +43,7 @@ pub struct CallFrame { pub(crate) code_block: Gc, pub(crate) pc: u32, /// The register pointer, points to the first register in the stack. + /// // TODO: Check if storing the frame pointer instead of argument count and computing the // argument count based on the pointers would be better for accessing the arguments // and the elements before the register pointer. @@ -119,10 +120,10 @@ impl CallFrame { /// caller prologue caller arguments callee prologue callee arguments /// ┌─────────────────┐ ┌─────────┐ ┌─────────────────┐ ┌──────┐ /// ▼ ▼ ▼ ▼ │ ▼ ▼ ▼ - /// | 0: undefined | 1: y | 2: 1 | 3: 2 | 4: undefined | 5: x | 6: 3 | - /// ▲ ▲ ▲ - /// │ caller register pointer ────┤ │ - /// │ │ callee register pointer + /// | 0: undefined | 1: y | 2: 1 | 3: 2 | 4: undefined | 5: x | 6: 3 | + /// ▲ ▲ ▲ + /// │ caller register pointer ────┤ │ + /// │ │ callee register pointer /// │ callee frame pointer /// │ /// └───── caller frame pointer diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index cf899f6c37..0a7c2871b8 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -549,6 +549,8 @@ impl CodeBlock { } => { format!("is_anonymous_function: {is_anonymous_function}") } + Instruction::PopIntoRegister { dst } => format!("dst:reg{}", dst.value()), + Instruction::PushFromRegister { src } => format!("src:reg{}", src.value()), Instruction::Pop | Instruction::Dup | Instruction::Swap @@ -724,9 +726,7 @@ impl CodeBlock { | Instruction::Reserved48 | Instruction::Reserved49 | Instruction::Reserved50 - | Instruction::Reserved51 - | Instruction::Reserved52 - | Instruction::Reserved53 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved51 => unreachable!("Reserved opcodes are unrechable"), } } } diff --git a/core/engine/src/vm/flowgraph/mod.rs b/core/engine/src/vm/flowgraph/mod.rs index f2b540c0c4..05d83a7ab1 100644 --- a/core/engine/src/vm/flowgraph/mod.rs +++ b/core/engine/src/vm/flowgraph/mod.rs @@ -455,6 +455,8 @@ impl CodeBlock { | Instruction::CreateUnmappedArgumentsObject | Instruction::CreateGlobalFunctionBinding { .. } | Instruction::CreateGlobalVarBinding { .. } + | Instruction::PopIntoRegister { .. } + | Instruction::PushFromRegister { .. } | Instruction::Nop => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); @@ -514,9 +516,7 @@ impl CodeBlock { | Instruction::Reserved48 | Instruction::Reserved49 | Instruction::Reserved50 - | Instruction::Reserved51 - | Instruction::Reserved52 - | Instruction::Reserved53 => unreachable!("Reserved opcodes are unrechable"), + | Instruction::Reserved51 => unreachable!("Reserved opcodes are unrechable"), } } diff --git a/core/engine/src/vm/opcode/control_flow/return.rs b/core/engine/src/vm/opcode/control_flow/return.rs index 6b3ffa4b94..03ef1e0aef 100644 --- a/core/engine/src/vm/opcode/control_flow/return.rs +++ b/core/engine/src/vm/opcode/control_flow/return.rs @@ -114,3 +114,76 @@ impl Operation for SetReturnValue { Ok(CompletionType::Normal) } } + +/// TODO: doc +#[derive(Debug, Clone, Copy)] +pub(crate) struct PopIntoRegister; + +impl PopIntoRegister { + #[allow(clippy::unnecessary_wraps)] + #[allow(clippy::needless_pass_by_value)] + fn operation(dst: u32, context: &mut Context) -> JsResult { + let value = context.vm.pop(); + + let rp = context.vm.frame().rp; + context.vm.stack[(rp + dst) as usize] = value; + Ok(CompletionType::Normal) + } +} + +impl Operation for PopIntoRegister { + const NAME: &'static str = "PopIntoRegister"; + const INSTRUCTION: &'static str = "INST - PopIntoRegister"; + const COST: u8 = 2; + + fn execute(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let dst = context.vm.read::(); + Self::operation(dst, context) + } +} + +/// TODO: doc +#[derive(Debug, Clone, Copy)] +pub(crate) struct PushFromRegister; + +impl PushFromRegister { + #[allow(clippy::unnecessary_wraps)] + #[allow(clippy::needless_pass_by_value)] + fn operation(dst: u32, context: &mut Context) -> JsResult { + let rp = context.vm.frame().rp; + let value = context.vm.stack[(rp + dst) as usize].clone(); + context.vm.push(value); + Ok(CompletionType::Normal) + } +} + +impl Operation for PushFromRegister { + const NAME: &'static str = "PushFromRegister"; + const INSTRUCTION: &'static str = "INST - PushFromRegister"; + const COST: u8 = 2; + + fn execute(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u16_operands(context: &mut Context) -> JsResult { + let dst = u32::from(context.vm.read::()); + Self::operation(dst, context) + } + + fn execute_with_u32_operands(context: &mut Context) -> JsResult { + let dst = context.vm.read::(); + Self::operation(dst, context) + } +} diff --git a/core/engine/src/vm/opcode/mod.rs b/core/engine/src/vm/opcode/mod.rs index 70fbdde554..92812e4642 100644 --- a/core/engine/src/vm/opcode/mod.rs +++ b/core/engine/src/vm/opcode/mod.rs @@ -1780,6 +1780,20 @@ generate_opcodes! { /// Stack: value **=>** SetReturnValue, + /// Pop value from the stack and push to register `dst` + /// + /// Operands: + /// + /// Stack: value **=>** + PopIntoRegister { dst: VaryingOperand }, + + /// Copy value at register `src` and push it into the stack. + /// + /// Operands: + /// + /// Stack: **=>** value + PushFromRegister { src: VaryingOperand }, + /// Push a declarative environment. /// /// Operands: compile_environments_index: `VaryingOperand` @@ -2255,10 +2269,6 @@ generate_opcodes! { Reserved50 => Reserved, /// Reserved [`Opcode`]. Reserved51 => Reserved, - /// Reserved [`Opcode`]. - Reserved52 => Reserved, - /// Reserved [`Opcode`]. - Reserved53 => Reserved, } /// Specific opcodes for bindings. diff --git a/core/engine/src/vm/opcode/rest_parameter/mod.rs b/core/engine/src/vm/opcode/rest_parameter/mod.rs index 59db486fb4..c086f8b788 100644 --- a/core/engine/src/vm/opcode/rest_parameter/mod.rs +++ b/core/engine/src/vm/opcode/rest_parameter/mod.rs @@ -20,16 +20,30 @@ impl Operation for RestParameterInit { let frame = context.vm.frame(); let argument_count = frame.argument_count; let param_count = frame.code_block().parameter_length; + let register_count = frame.code_block().register_count; - let array = if argument_count >= param_count { + if argument_count >= param_count { let rest_count = argument_count - param_count + 1; - let args = context.vm.pop_n_values(rest_count as usize); - Array::create_array_from_list(args, context) + + let len = context.vm.stack.len() as u32; + let start = (len - rest_count - register_count) as usize; + let end = (len - register_count) as usize; + + let args = &context.vm.stack[start..end]; + + let array = Array::create_array_from_list(args.iter().cloned(), context); + context.vm.stack.drain(start..end); + + context.vm.frame_mut().rp -= (start..end).len() as u32; + context.vm.frame_mut().argument_count -= (start..end).len() as u32; + + context.vm.push(array); } else { - Array::array_create(0, None, context).expect("could not create an empty array") - }; + let array = + Array::array_create(0, None, context).expect("could not create an empty array"); + context.vm.push(array); + } - context.vm.push(array); Ok(CompletionType::Normal) } }