Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inline cache for getting bindings from the global object #4067

Merged
merged 4 commits into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
6 changes: 6 additions & 0 deletions core/ast/src/scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,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: `u32`
raskad marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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
Loading