Skip to content

Commit

Permalink
Optimize operand reading
Browse files Browse the repository at this point in the history
  • Loading branch information
HalidOdat committed Apr 9, 2023
1 parent 7f95532 commit 4ebb6a2
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 85 deletions.
16 changes: 7 additions & 9 deletions boa_engine/src/bytecompiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::cell::Cell;
use crate::{
builtins::function::ThisMode,
environments::{BindingLocator, CompileTimeEnvironment},
vm::{BindingOpcode, CodeBlock, InlineCache, Opcode},
vm::{BindingOpcode, CodeBlock, InlineCache, InlineCachePayload, Opcode},
Context, JsBigInt, JsString, JsValue,
};
use boa_ast::{
Expand Down Expand Up @@ -456,16 +456,14 @@ impl<'b, 'host> ByteCompiler<'b, 'host> {
self.ic.push(InlineCache::new(literal_index));

self.emit_opcode(Opcode::GetPropertyByName);

self.emit_u32(ic_index);
self.emit_usize(0);
self.emit_u32(0);
self.emit_u8(0);
let payload = InlineCachePayload::new(ic_index);
self.bytecode
.extend(payload.to_byte_array().into_iter().map(Cell::new));
}

fn emit_usize(&mut self, value: usize) {
self.bytecode.extend(value.to_ne_bytes().map(Cell::new));
}
// fn emit_usize(&mut self, value: usize) {
// self.bytecode.extend(value.to_ne_bytes().map(Cell::new));
// }

fn emit_u64(&mut self, value: u64) {
self.bytecode.extend(value.to_ne_bytes().map(Cell::new));
Expand Down
104 changes: 75 additions & 29 deletions boa_engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ use crate::{
environments::{BindingLocator, CompileTimeEnvironment},
error::JsNativeError,
object::{
internal_methods::get_prototype_from_constructor, shape::Shape, JsObject, ObjectData,
PROTOTYPE,
internal_methods::get_prototype_from_constructor,
shape::{
slot::{Slot, SlotAttributes},
Shape,
},
JsObject, ObjectData, PROTOTYPE,
},
property::PropertyDescriptor,
string::utf16,
Expand Down Expand Up @@ -77,6 +81,53 @@ impl InlineCache {
}
}

// TODO: find a better way to store these information,
// maybe move it into the `CodeBlock`, need to profile
#[derive(Clone, Copy)]
#[repr(C)]
pub(crate) struct InlineCachePayload {
pub(crate) shape_ptr: usize,
pub(crate) encode_slot: u32,
pub(crate) ic_index: u32,
}

unsafe impl Readable for InlineCachePayload {}

impl InlineCachePayload {
const SIZE: usize = size_of::<Self>();
pub(crate) const fn new(ic_index: u32) -> Self {
Self {
ic_index,
shape_ptr: 0,
encode_slot: 0,
}
}

pub(crate) const fn slot(&self) -> Slot {
let encode_slot = self.encode_slot;

let slot_attributes = SlotAttributes::from_bits_retain((encode_slot & 0xFF) as u8);
let slot_index = encode_slot >> 8;

// TODO: Maybe store slot like this to begin with, limiting object property count to 2^24 seems
// fine.
Slot {
index: slot_index,
attributes: slot_attributes,
}
}

pub(crate) fn set(&mut self, shape_ptr: usize, slot: Slot) {
self.shape_ptr = shape_ptr;
self.encode_slot = (slot.index << 8) | u32::from(slot.attributes.bits());
}

// maybe move this to `Readable`
pub(crate) fn to_byte_array(self) -> [u8; Self::SIZE] {
unsafe { std::mem::transmute_copy(&self) }
}
}

/// The internal representation of a JavaScript function.
///
/// A `CodeBlock` is generated for each function compiled by the
Expand Down Expand Up @@ -231,35 +282,33 @@ impl CodeBlock {
}

/// TODO: doc
pub(crate) fn write_usize(&self, offset: usize, value: usize) {
let bytes = value.to_ne_bytes();
for (bytecode, byte) in self.bytecode[offset..offset + size_of::<usize>()]
pub(crate) fn write_inline_caching_payload(&self, offset: usize, value: &InlineCachePayload) {
let bytes = value.to_byte_array();
for (bytecode, byte) in self.bytecode[offset..offset + size_of::<InlineCachePayload>()]
.iter()
.zip(bytes.into_iter())
{
bytecode.set(byte);
}
}

/// TODO: doc
pub(crate) fn write_u32(&self, offset: usize, value: u32) {
let bytes = value.to_ne_bytes();
for (bytecode, byte) in self.bytecode[offset..offset + size_of::<u32>()]
.iter()
.zip(bytes.into_iter())
{
bytecode.set(byte);
}
}
// /// TODO: doc
// pub(crate) fn write_u32(&self, offset: usize, value: u32) {
// let bytes = value.to_ne_bytes();
// for (bytecode, byte) in self.bytecode[offset..offset + size_of::<u32>()]
// .iter()
// .zip(bytes.into_iter())
// {
// bytecode.set(byte);
// }
// }

/// Get the operands after the `Opcode` pointed to by `pc` as a `String`.
/// Modifies the `pc` to point to the next instruction.
///
/// Returns an empty `String` if no operands are present.
#[cfg(any(feature = "trace", feature = "flowgraph"))]
pub(crate) fn instruction_operands(&self, pc: &mut usize, interner: &Interner) -> String {
use crate::object::shape::slot::SlotAttributes;

let opcode: Opcode = self.bytecode[*pc].get().try_into().expect("invalid opcode");
*pc += size_of::<Opcode>();
match opcode {
Expand Down Expand Up @@ -374,22 +423,19 @@ impl CodeBlock {
)
}
Opcode::GetPropertyByName => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let shape_ptr = self.read::<usize>(*pc);
*pc += size_of::<usize>();
let slot_index = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let slot_attributes = self.read::<u8>(*pc);
*pc += size_of::<u8>();
let payload = self.read::<InlineCachePayload>(*pc);
*pc += size_of::<InlineCachePayload>();

let slot_attributes =
SlotAttributes::from_bits(slot_attributes).expect("should not fail!");
let slot = payload.slot();

let ic = &self.ic[operand as usize];
let ic = &self.ic[payload.ic_index as usize];
format!(
"{operand:04}: '{}', Shape: {shape_ptr}, Slot: index: {slot_index}, attributes {slot_attributes:?}",
"{:04}: '{}', Shape: {}, Slot: index: {}, attributes {:?}",
payload.ic_index,
interner.resolve_expect(self.names[ic.literal_index as usize].sym()),
payload.shape_ptr,
slot.index,
slot.attributes,
)
}
Opcode::GetMethod
Expand Down
14 changes: 4 additions & 10 deletions boa_engine/src/vm/flowgraph/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! This module is responsible for generating the vm instruction flowgraph.
use crate::vm::{CodeBlock, Opcode};
use crate::vm::{CodeBlock, InlineCachePayload, Opcode};
use boa_interner::{Interner, Sym};
use std::mem::size_of;

Expand Down Expand Up @@ -382,16 +382,10 @@ impl CodeBlock {
}
Opcode::GetPropertyByName => {
// TODO: Implement better trace format
let operand = self.read::<u32>(pc);
pc += size_of::<u32>();
let _shape_ptr = self.read::<usize>(pc);
pc += size_of::<usize>();
let _slot_index = self.read::<u32>(pc);
pc += size_of::<u32>();
let _slot_attributes = self.read::<u8>(pc);
pc += size_of::<u8>();
let payload = self.read::<InlineCachePayload>(pc);
pc += size_of::<InlineCachePayload>();

let ic = &self.ic[operand as usize];
let ic = &self.ic[payload.ic_index as usize];
let label = format!(
"{opcode_str} '{}'",
interner.resolve_expect(self.names[ic.literal_index as usize].sym()),
Expand Down
20 changes: 12 additions & 8 deletions boa_engine/src/vm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub use {
opcode::Opcode,
};

pub(crate) use code_block::InlineCachePayload;

pub(crate) use {
call_frame::GeneratorResumeKind,
code_block::{create_function_object, create_generator_function_object},
Expand Down Expand Up @@ -100,17 +102,19 @@ impl Vm {
(value, location)
}

pub(crate) fn write_usize(&self, location: usize, value: usize) {
self.frame().code_block.write_usize(location, value);
pub(crate) fn write_inline_caching_payload(&self, location: usize, value: &InlineCachePayload) {
self.frame()
.code_block
.write_inline_caching_payload(location, value);
}

pub(crate) fn write_u32(&self, location: usize, value: u32) {
self.frame().code_block.write_u32(location, value);
}
// pub(crate) fn write_u32(&self, location: usize, value: u32) {
// self.frame().code_block.write_u32(location, value);
// }

pub(crate) fn write_u8(&self, location: usize, value: u8) {
self.frame().code_block.bytecode[location].set(value);
}
// pub(crate) fn write_u8(&self, location: usize, value: u8) {
// self.frame().code_block.bytecode[location].set(value);
// }

/// Retrieves the VM frame
///
Expand Down
57 changes: 28 additions & 29 deletions boa_engine/src/vm/opcode/get/property.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::{
js_string,
object::shape::slot::{Slot, SlotAttributes},
object::shape::slot::SlotAttributes,
property::PropertyKey,
vm::{opcode::Operation, CompletionType},
vm::{code_block::InlineCachePayload, opcode::Operation, CompletionType},
Context, JsResult, JsValue,
};

Expand All @@ -18,10 +18,8 @@ impl Operation for GetPropertyByName {
const INSTRUCTION: &'static str = "INST - GetPropertyByName";

fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let ic_index = context.vm.read::<u32>();
let (shape_ptr, shape_ptr_location) = context.vm.read_and_get_location::<usize>();
let (slot_index, slot_index_location) = context.vm.read_and_get_location::<u32>();
let (slot_attributes, slot_attributes_locations) = context.vm.read_and_get_location::<u8>();
let (mut payload, payload_location) =
context.vm.read_and_get_location::<InlineCachePayload>();

let value = context.vm.pop();
let object = if let Some(object) = value.as_object() {
Expand All @@ -30,24 +28,23 @@ impl Operation for GetPropertyByName {
value.to_object(context)?
};

if slot_attributes & SlotAttributes::NOT_CACHABLE.bits() != 0 {
// TODO: passible optimization, inline sym into bytecode, since it fits in the shape pointer.
let ic = &context.vm.frame().code_block.ic[ic_index as usize];
let name = context.vm.frame().code_block.names[ic.literal_index as usize];
let key: PropertyKey = context.interner().resolve_expect(name.sym()).utf16().into();
let result = object.__get__(&key, value, context)?;
context.vm.push(result);
return Ok(CompletionType::Normal);
}
// TODO: implement non-cachable slots
// if slot_attributes & SlotAttributes::NOT_CACHABLE.bits() != 0 {
// // TODO: passible optimization, inline sym into bytecode, since it fits in the shape pointer.
// let ic = &context.vm.frame().code_block.ic[ic_index as usize];
// let name = context.vm.frame().code_block.names[ic.literal_index as usize];
// let key: PropertyKey = context.interner().resolve_expect(name.sym()).utf16().into();
// let result = object.__get__(&key, value, context)?;
// context.vm.push(result);
// return Ok(CompletionType::Normal);
// }

// Use inline cache.
if shape_ptr == object.borrow().properties().shape.to_addr_usize() {
let slot = Slot {
index: slot_index,
attributes: SlotAttributes::from_bits_retain(slot_attributes),
};
let object_borrowed = object.borrow();
if payload.shape_ptr == object_borrowed.properties().shape.to_addr_usize() {
let slot = payload.slot();
let mut result = if slot.attributes.contains(SlotAttributes::DIRECT) {
object.borrow().properties().storage[slot.index as usize].clone()
object_borrowed.properties().storage[slot.index as usize].clone()
} else {
let prototype = object
.borrow()
Expand All @@ -58,23 +55,28 @@ impl Operation for GetPropertyByName {
let prototype = prototype.borrow();
prototype.properties().storage[slot.index as usize].clone()
};
drop(object_borrowed);
if slot.attributes.has_get() {
result = result.call(&value, &[], context)?;
}
context.vm.push(result);
return Ok(CompletionType::Normal);
}

let ic = &context.vm.frame().code_block.ic[ic_index as usize];
let ic = &context.vm.frame().code_block.ic[payload.ic_index as usize];
let name = context.vm.frame().code_block.names[ic.literal_index as usize];
let key: PropertyKey = context.interner().resolve_expect(name.sym()).utf16().into();

// Check if it can be cached.
let shape = object.borrow().properties().shape.clone();
let shape = object_borrowed.properties().shape.clone();
drop(object_borrowed);
let slot = if let Some(mut slot) = shape.lookup(&key) {
slot.attributes |= SlotAttributes::DIRECT;
slot
} else if let Some(mut slot) = shape.prototype().and_then(|prototype| prototype.borrow().properties().shape.lookup(&key)) {
} else if let Some(mut slot) = shape
.prototype()
.and_then(|prototype| prototype.borrow().properties().shape.lookup(&key))
{
slot.attributes.set(SlotAttributes::DIRECT, false);
slot
} else {
Expand All @@ -84,13 +86,10 @@ impl Operation for GetPropertyByName {
};

// Write inline to bytecode inline.
payload.set(shape.to_addr_usize(), slot);
context
.vm
.write_usize(shape_ptr_location, shape.to_addr_usize());
context.vm.write_u32(slot_index_location, slot.index);
context
.vm
.write_u8(slot_attributes_locations, slot.attributes.bits());
.write_inline_caching_payload(payload_location, &payload);

*ic.shape.borrow_mut() = Some(shape);

Expand Down

0 comments on commit 4ebb6a2

Please sign in to comment.