Skip to content

Commit

Permalink
Add inline cache for getting bindings from the global object (#4067)
Browse files Browse the repository at this point in the history
* Add inline cache for getting bindings from the global object

* cargo fmt

* fix typos

* fix doc comment
  • Loading branch information
raskad authored Dec 20, 2024
1 parent 3777df0 commit 02ba398
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 10 deletions.
6 changes: 6 additions & 0 deletions core/ast/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,12 @@ impl IdentifierReference {
self.locator.scope > 0 && !self.escapes
}

/// Returns if the binding is on the global object.
#[must_use]
pub fn is_global_object(&self) -> bool {
self.locator.scope == 0
}

/// Check if this identifier reference is lexical.
#[must_use]
pub fn is_lexical(&self) -> bool {
Expand Down
25 changes: 25 additions & 0 deletions core/engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ pub struct ByteCompiler<'ctx> {
pub(crate) enum BindingKind {
Stack(u32),
Local(u32),
Global(u32),
}

impl<'ctx> ByteCompiler<'ctx> {
Expand Down Expand Up @@ -601,6 +602,17 @@ impl<'ctx> ByteCompiler<'ctx> {

#[inline]
pub(crate) fn get_or_insert_binding(&mut self, binding: IdentifierReference) -> BindingKind {
if binding.is_global_object() {
if let Some(index) = self.bindings_map.get(&binding.locator()) {
return BindingKind::Global(*index);
}

let index = self.bindings.len() as u32;
self.bindings.push(binding.locator().clone());
self.bindings_map.insert(binding.locator(), index);
return BindingKind::Global(index);
}

if binding.local() {
return BindingKind::Local(
*self
Expand Down Expand Up @@ -732,6 +744,19 @@ impl<'ctx> ByteCompiler<'ctx> {

pub(crate) fn emit_binding_access(&mut self, opcode: Opcode, binding: &BindingKind) {
match binding {
BindingKind::Global(index) => match opcode {
Opcode::SetNameByLocator => self.emit_opcode(opcode),
Opcode::GetName => {
let ic_index = self.ic.len() as u32;
let name = self.bindings[*index as usize].name().clone();
self.ic.push(InlineCache::new(name));
self.emit(
Opcode::GetNameGlobal,
&[Operand::Varying(*index), Operand::Varying(ic_index)],
);
}
_ => self.emit_with_varying_operand(opcode, *index),
},
BindingKind::Stack(index) => match opcode {
Opcode::SetNameByLocator => self.emit_opcode(opcode),
_ => self.emit_with_varying_operand(opcode, *index),
Expand Down
4 changes: 2 additions & 2 deletions core/engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ impl CodeBlock {
| Instruction::DefInitVar { index }
| Instruction::PutLexicalValue { index }
| Instruction::GetName { index }
| Instruction::GetNameGlobal { index, .. }
| Instruction::GetLocator { index }
| Instruction::GetNameAndLocator { index }
| Instruction::GetNameOrUndefined { index }
Expand Down Expand Up @@ -737,8 +738,7 @@ impl CodeBlock {
| Instruction::Reserved45
| Instruction::Reserved46
| Instruction::Reserved47
| Instruction::Reserved48
| Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved48 => 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 @@ -250,6 +250,7 @@ impl CodeBlock {
| Instruction::DefInitVar { .. }
| Instruction::PutLexicalValue { .. }
| Instruction::GetName { .. }
| Instruction::GetNameGlobal { .. }
| Instruction::GetLocator { .. }
| Instruction::GetNameAndLocator { .. }
| Instruction::GetNameOrUndefined { .. }
Expand Down Expand Up @@ -515,8 +516,7 @@ impl CodeBlock {
| Instruction::Reserved45
| Instruction::Reserved46
| Instruction::Reserved47
| Instruction::Reserved48
| Instruction::Reserved49 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved48 => unreachable!("Reserved opcodes are unreachable"),
}
}

Expand Down
108 changes: 104 additions & 4 deletions core/engine/src/vm/opcode/get/name.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::{
error::JsNativeError,
object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes},
property::PropertyKey,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue,
};
Expand Down Expand Up @@ -31,8 +33,8 @@ impl Operation for GetName {
const COST: u8 = 4;

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

fn execute_with_u16_operands(context: &mut Context) -> JsResult<CompletionType> {
Expand All @@ -41,8 +43,106 @@ impl Operation for GetName {
}

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

/// `GetNameGlobal` implements the Opcode Operation for `Opcode::GetNameGlobal`
///
/// Operation:
/// - Find a binding in the global object and push its value.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetNameGlobal;

impl GetNameGlobal {
fn operation(context: &mut Context, index: usize, ic_index: usize) -> JsResult<CompletionType> {
let mut binding_locator = context.vm.frame().code_block.bindings[index].clone();
context.find_runtime_binding(&mut binding_locator)?;

if binding_locator.is_global() {
let object = context.global_object();

let ic = &context.vm.frame().code_block().ic[ic_index];

let object_borrowed = object.borrow();
if let Some((shape, slot)) = ic.match_or_reset(object_borrowed.shape()) {
let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
let prototype = shape.prototype().expect("prototype should have value");
let prototype = prototype.borrow();
prototype.properties().storage[slot.index as usize].clone()
} else {
object_borrowed.properties().storage[slot.index as usize].clone()
};

drop(object_borrowed);
if slot.attributes.has_get() && result.is_object() {
result = result.as_object().expect("should contain getter").call(
&object.clone().into(),
&[],
context,
)?;
}
context.vm.push(result);
return Ok(CompletionType::Normal);
}

drop(object_borrowed);

let key: PropertyKey = ic.name.clone().into();

let context = &mut InternalMethodContext::new(context);
let Some(result) = object.__try_get__(&key, object.clone().into(), context)? else {
let name = binding_locator.name().to_std_string_escaped();
return Err(JsNativeError::reference()
.with_message(format!("{name} is not defined"))
.into());
};

// Cache the property.
let slot = *context.slot();
if slot.is_cachable() {
let ic = &context.vm.frame().code_block.ic[ic_index];
let object_borrowed = object.borrow();
let shape = object_borrowed.shape();
ic.set(shape, slot);
}

context.vm.push(result);
return Ok(CompletionType::Normal);
}

let value = context.get_binding(&binding_locator)?.ok_or_else(|| {
let name = binding_locator.name().to_std_string_escaped();
JsNativeError::reference().with_message(format!("{name} is not defined"))
})?;

context.vm.push(value);
Ok(CompletionType::Normal)
}
}

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

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

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

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

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 @@ -1115,6 +1115,13 @@ generate_opcodes! {
/// Stack: **=>** value
GetName { index: VaryingOperand },

/// Find a binding in the global object and push its value.
///
/// Operands: index: `VaryingOperand`, ic_index: `VaryingOperand`,
///
/// Stack: **=>** value
GetNameGlobal { index: VaryingOperand, ic_index: VaryingOperand },

/// Find a binding on the environment and set the `current_binding` of the current frame.
///
/// Operands: index: `u32`
Expand Down Expand Up @@ -2280,8 +2287,6 @@ generate_opcodes! {
Reserved47 => Reserved,
/// Reserved [`Opcode`].
Reserved48 => Reserved,
/// Reserved [`Opcode`].
Reserved49 => Reserved,
}

/// Specific opcodes for bindings.
Expand Down

0 comments on commit 02ba398

Please sign in to comment.