Skip to content

Commit

Permalink
Implement register allocation (#3942)
Browse files Browse the repository at this point in the history
* Implement register allocation

* Apply review

* Update core/engine/src/vm/opcode/rest_parameter/mod.rs

Co-authored-by: raskad <[email protected]>

---------

Co-authored-by: raskad <[email protected]>
  • Loading branch information
HalidOdat and raskad committed Sep 12, 2024
1 parent 4db0388 commit c480a42
Show file tree
Hide file tree
Showing 16 changed files with 338 additions and 44 deletions.
2 changes: 1 addition & 1 deletion core/engine/src/builtins/array/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ impl Array {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createarrayfromlist
pub(crate) fn create_array_from_list<I>(elements: I, context: &mut Context) -> JsObject
pub(crate) fn create_array_from_list<I>(elements: I, context: &Context) -> JsObject
where
I: IntoIterator<Item = JsValue>,
{
Expand Down
2 changes: 2 additions & 0 deletions core/engine/src/builtins/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ impl Eval {
false,
var_env.clone(),
lex_env.clone(),
false,
false,
context.interner_mut(),
in_with,
);
Expand Down
2 changes: 2 additions & 0 deletions core/engine/src/builtins/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ impl Json {
true,
context.realm().environment().compile_env(),
context.realm().environment().compile_env(),
false,
false,
context.interner_mut(),
in_with,
);
Expand Down
10 changes: 10 additions & 0 deletions core/engine/src/bytecompiler/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
false,
false,
self.interner,
self.in_with,
);
Expand Down Expand Up @@ -274,6 +276,8 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
false,
false,
self.interner,
self.in_with,
);
Expand Down Expand Up @@ -308,6 +312,8 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
false,
false,
self.interner,
self.in_with,
);
Expand Down Expand Up @@ -347,6 +353,8 @@ impl ByteCompiler<'_> {
self.json_parse,
self.variable_environment.clone(),
self.lexical_environment.clone(),
false,
false,
self.interner,
self.in_with,
);
Expand Down Expand Up @@ -388,6 +396,8 @@ impl ByteCompiler<'_> {
false,
self.variable_environment.clone(),
self.lexical_environment.clone(),
false,
false,
self.interner,
self.in_with,
);
Expand Down
8 changes: 2 additions & 6 deletions core/engine/src/bytecompiler/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
71 changes: 56 additions & 15 deletions core/engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod expression;
mod function;
mod jump_control;
mod module;
mod register;
mod statement;
mod utils;

Expand All @@ -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,
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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<Handler>,
pub(crate) ic: Vec<InlineCache>,
literals_map: FxHashMap<Literal, u32>,
Expand Down Expand Up @@ -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<CompileTimeEnvironment>,
lexical_environment: Rc<CompileTimeEnvironment>,
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,
Expand All @@ -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(),
Expand Down Expand Up @@ -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()`].
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
Expand Down
140 changes: 140 additions & 0 deletions core/engine/src/bytecompiler/register.rs
Original file line number Diff line number Diff line change
@@ -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<RegisterEntry>,
}

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

0 comments on commit c480a42

Please sign in to comment.