Skip to content

Commit 9bc4452

Browse files
authored
feat(vm): Global property lookup caches (#822)
1 parent eef808d commit 9bc4452

33 files changed

+995
-313
lines changed

nova_vm/src/ecmascript/builtins/array.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,13 @@ impl<'a> InternalMethods<'a> for Array<'a> {
595595
if let Some(backing_object) = self.get_backing_object(agent) {
596596
// Note: this looks up in the prototype chain as well, so we
597597
// don't need to fall-through if this returns false or such.
598-
return ordinary_try_has_property(agent, backing_object, property_key, gc);
598+
return ordinary_try_has_property(
599+
agent,
600+
self.into_object(),
601+
backing_object,
602+
property_key,
603+
gc,
604+
);
599605
}
600606
}
601607
// Data is not found in the array or its backing object (or one does
@@ -654,7 +660,13 @@ impl<'a> InternalMethods<'a> for Array<'a> {
654660
if let Some(backing_object) = self.get_backing_object(agent) {
655661
// Note: this looks up in the prototype chain as well, so we
656662
// don't need to fall-through if this returns false or such.
657-
return ordinary_has_property(agent, backing_object, property_key, gc);
663+
return ordinary_has_property(
664+
agent,
665+
self.into_object(),
666+
backing_object,
667+
property_key,
668+
gc,
669+
);
658670
}
659671
}
660672
// Data is not found in the array or its backing object (or one does

nova_vm/src/ecmascript/builtins/error.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ use crate::{
3131
};
3232

3333
use super::ordinary::{
34-
caches::PropertyLookupCache, ordinary_delete, ordinary_get_own_property, ordinary_set,
35-
ordinary_try_get, ordinary_try_set,
34+
caches::PropertyLookupCache, ordinary_delete, ordinary_get_own_property, ordinary_has_property,
35+
ordinary_set, ordinary_try_get, ordinary_try_has_property, ordinary_try_set,
3636
};
3737

3838
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -219,7 +219,13 @@ impl<'a> InternalMethods<'a> for Error<'a> {
219219
gc: NoGcScope,
220220
) -> TryResult<bool> {
221221
match self.get_backing_object(agent) {
222-
Some(backing_object) => backing_object.try_has_property(agent, property_key, gc),
222+
Some(backing_object) => ordinary_try_has_property(
223+
agent,
224+
self.into_object(),
225+
backing_object,
226+
property_key,
227+
gc,
228+
),
223229
None => {
224230
let found_direct =
225231
if property_key == PropertyKey::from(BUILTIN_STRING_MEMORY.message) {
@@ -250,9 +256,13 @@ impl<'a> InternalMethods<'a> for Error<'a> {
250256
) -> JsResult<'gc, bool> {
251257
let property_key = property_key.bind(gc.nogc());
252258
match self.get_backing_object(agent) {
253-
Some(backing_object) => {
254-
backing_object.internal_has_property(agent, property_key.unbind(), gc)
255-
}
259+
Some(backing_object) => ordinary_has_property(
260+
agent,
261+
self.into_object(),
262+
backing_object,
263+
property_key.unbind(),
264+
gc,
265+
),
256266
None => {
257267
let found_direct =
258268
if property_key == PropertyKey::from(BUILTIN_STRING_MEMORY.message) {

nova_vm/src/ecmascript/builtins/global_object.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ use crate::{
4141
heap::IntrinsicFunctionIndexes,
4242
};
4343

44-
use super::{ArgumentsList, Behaviour, Builtin, BuiltinIntrinsic};
44+
use super::{
45+
ArgumentsList, Behaviour, Builtin, BuiltinIntrinsic, ordinary::caches::PropertyLookupCache,
46+
};
4547

4648
pub(crate) struct GlobalObject;
4749

@@ -386,8 +388,15 @@ pub fn perform_eval<'gc>(
386388
// 29. If result is a normal completion, then
387389
match result {
388390
Ok(_) => {
389-
let exe =
390-
Executable::compile_eval_body(agent, body, gc.nogc()).scope(agent, gc.nogc());
391+
let source_code = agent
392+
.running_execution_context()
393+
.ecmascript_code
394+
.as_ref()
395+
.unwrap()
396+
.source_code
397+
.bind(gc.nogc());
398+
let exe = Executable::compile_eval_body(agent, body, source_code, gc.nogc())
399+
.scope(agent, gc.nogc());
391400
// a. Set result to Completion(Evaluation of body).
392401
// 30. If result is a normal completion and result.[[Value]] is empty, then
393402
// a. Set result to NormalCompletion(undefined).
@@ -750,16 +759,25 @@ fn eval_declaration_instantiation<'a>(
750759
// 3. Perform ! varEnv.InitializeBinding(fn, fo).
751760
scoped_var_env
752761
.get(agent)
753-
.initialize_binding(agent, function_name.get(agent).unbind(), fo, gc.reborrow())
762+
.initialize_binding(
763+
agent,
764+
function_name.get(agent).unbind(),
765+
None,
766+
fo,
767+
gc.reborrow(),
768+
)
754769
.unwrap();
755770
} else {
756771
// iii. Else,
757772
// 1. Perform ! varEnv.SetMutableBinding(fn, fo, false).
773+
let function_name = function_name.get(agent).bind(gc.nogc());
774+
let cache = PropertyLookupCache::new(agent, function_name.to_property_key());
758775
scoped_var_env
759776
.get(agent)
760777
.set_mutable_binding(
761778
agent,
762-
function_name.get(agent).unbind(),
779+
function_name.unbind(),
780+
Some(cache.unbind()),
763781
fo,
764782
false,
765783
gc.reborrow(),
@@ -773,9 +791,10 @@ fn eval_declaration_instantiation<'a>(
773791
// a. If varEnv is a Global Environment Record, then
774792
if let Environment::Global(var_env) = scoped_var_env.get(agent).bind(gc.nogc()) {
775793
// i. Perform ? varEnv.CreateGlobalVarBinding(vn, true).
794+
let cache = PropertyLookupCache::new(agent, vn.get(agent).to_property_key());
776795
var_env
777796
.unbind()
778-
.create_global_var_binding(agent, vn.get(agent), true, gc.reborrow())
797+
.create_global_var_binding(agent, vn.get(agent), cache, true, gc.reborrow())
779798
.unbind()?
780799
.bind(gc.nogc());
781800
} else {
@@ -797,7 +816,7 @@ fn eval_declaration_instantiation<'a>(
797816
// 3. Perform ! varEnv.InitializeBinding(vn, undefined).
798817
scoped_var_env
799818
.get(agent)
800-
.initialize_binding(agent, vn.get(agent), Value::Undefined, gc.reborrow())
819+
.initialize_binding(agent, vn.get(agent), None, Value::Undefined, gc.reborrow())
801820
.unwrap();
802821
}
803822
}

nova_vm/src/ecmascript/builtins/ordinary.rs

Lines changed: 109 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -617,24 +617,46 @@ fn validate_and_apply_property_descriptor(
617617
/// ### [10.1.7.1 OrdinaryHasProperty ( O, P )](https://tc39.es/ecma262/#sec-ordinaryhasproperty)
618618
pub(crate) fn ordinary_try_has_property(
619619
agent: &mut Agent,
620-
object: OrdinaryObject,
620+
object: Object,
621+
backing_object: OrdinaryObject,
621622
property_key: PropertyKey,
622623
gc: NoGcScope,
623624
) -> TryResult<bool> {
624625
// 1. Let hasOwn be ? O.[[GetOwnProperty]](P).
625-
// Note: ? means that if we'd call a Proxy's GetOwnProperty trap then we'll
626-
// instead return None.
627-
let has_own = object.try_get_own_property(agent, property_key, gc)?;
626+
let has_own = backing_object
627+
.object_shape(agent)
628+
.keys(&agent.heap.object_shapes, &agent.heap.elements)
629+
.iter()
630+
.enumerate()
631+
.find(|(_, p)| *p == &property_key)
632+
.map(|(i, _)| i as u32);
628633

629634
// 2. If hasOwn is not undefined, return true.
630-
if has_own.is_some() {
635+
if let Some(offset) = has_own {
636+
if let Some(CacheToPopulate {
637+
receiver,
638+
cache,
639+
key: _,
640+
shape,
641+
}) = agent
642+
.heap
643+
.caches
644+
.take_current_cache_to_populate(property_key)
645+
{
646+
let is_receiver = object.into_value() == receiver;
647+
if is_receiver {
648+
cache.insert_lookup_offset(agent, shape, offset);
649+
} else {
650+
cache.insert_prototype_lookup_offset(agent, shape, offset, object);
651+
}
652+
}
631653
return TryResult::Continue(true);
632-
}
654+
};
633655

634656
// 3. Let parent be ? O.[[GetPrototypeOf]]().
635657
// Note: ? means that if we'd call a Proxy's GetPrototypeOf trap then we'll
636658
// instead return None.
637-
let parent = object.try_get_prototype_of(agent, gc)?;
659+
let parent = backing_object.try_get_prototype_of(agent, gc)?;
638660

639661
// 4. If parent is not null, then
640662
if let Some(parent) = parent {
@@ -644,6 +666,19 @@ pub(crate) fn ordinary_try_has_property(
644666
return parent.try_has_property(agent, property_key, gc);
645667
}
646668

669+
if let Some(CacheToPopulate {
670+
receiver: _,
671+
cache,
672+
key: _,
673+
shape,
674+
}) = agent
675+
.heap
676+
.caches
677+
.take_current_cache_to_populate(property_key)
678+
{
679+
cache.insert_unset(agent, shape);
680+
}
681+
647682
// 5. Return false.
648683
TryResult::Continue(false)
649684
}
@@ -655,7 +690,13 @@ pub(crate) fn ordinary_try_has_property_entry<'a>(
655690
gc: NoGcScope,
656691
) -> TryResult<bool> {
657692
match object.get_backing_object(agent) {
658-
Some(backing_object) => ordinary_try_has_property(agent, backing_object, property_key, gc),
693+
Some(backing_object) => ordinary_try_has_property(
694+
agent,
695+
object.into_object(),
696+
backing_object,
697+
property_key,
698+
gc,
699+
),
659700
None => {
660701
// 3. Let parent be ? O.[[GetPrototypeOf]]().
661702
let parent = unwrap_try(object.try_get_prototype_of(agent, gc));
@@ -665,6 +706,18 @@ pub(crate) fn ordinary_try_has_property_entry<'a>(
665706
// a. Return ? parent.[[HasProperty]](P).
666707
parent.try_has_property(agent, property_key, gc)
667708
} else {
709+
if let Some(CacheToPopulate {
710+
receiver: _,
711+
cache,
712+
key: _,
713+
shape,
714+
}) = agent
715+
.heap
716+
.caches
717+
.take_current_cache_to_populate(property_key)
718+
{
719+
cache.insert_unset(agent, shape);
720+
}
668721
// 5. Return false.
669722
TryResult::Continue(false)
670723
}
@@ -675,26 +728,47 @@ pub(crate) fn ordinary_try_has_property_entry<'a>(
675728
/// ### [10.1.7.1 OrdinaryHasProperty ( O, P )](https://tc39.es/ecma262/#sec-ordinaryhasproperty)
676729
pub(crate) fn ordinary_has_property<'a>(
677730
agent: &mut Agent,
678-
object: OrdinaryObject,
731+
object: Object,
732+
backing_object: OrdinaryObject,
679733
property_key: PropertyKey,
680734
gc: GcScope<'a, '_>,
681735
) -> JsResult<'a, bool> {
682-
let object = object.bind(gc.nogc());
736+
let backing_object = backing_object.bind(gc.nogc());
683737
let property_key = property_key.bind(gc.nogc());
684738
// 1. Let hasOwn be ? O.[[GetOwnProperty]](P).
685739

686-
let has_own = object
740+
let has_own = backing_object
687741
.object_shape(agent)
688742
.keys(&agent.heap.object_shapes, &agent.heap.elements)
689-
.contains(&property_key);
743+
.iter()
744+
.enumerate()
745+
.find(|(_, p)| *p == &property_key)
746+
.map(|(i, _)| i as u32);
690747

691748
// 2. If hasOwn is not undefined, return true.
692-
if has_own {
749+
if let Some(offset) = has_own {
750+
if let Some(CacheToPopulate {
751+
receiver,
752+
cache,
753+
key: _,
754+
shape,
755+
}) = agent
756+
.heap
757+
.caches
758+
.take_current_cache_to_populate(property_key)
759+
{
760+
let is_receiver = object.into_value() == receiver;
761+
if is_receiver {
762+
cache.insert_lookup_offset(agent, shape, offset);
763+
} else {
764+
cache.insert_prototype_lookup_offset(agent, shape, offset, object);
765+
}
766+
}
693767
return Ok(true);
694-
}
768+
};
695769

696770
// 3. Let parent be ? O.[[GetPrototypeOf]]().
697-
let parent = object.internal_prototype(agent).bind(gc.nogc());
771+
let parent = backing_object.internal_prototype(agent).bind(gc.nogc());
698772

699773
// 4. If parent is not null, then
700774
if let Some(parent) = parent {
@@ -704,6 +778,19 @@ pub(crate) fn ordinary_has_property<'a>(
704778
.internal_has_property(agent, property_key.unbind(), gc);
705779
}
706780

781+
if let Some(CacheToPopulate {
782+
receiver: _,
783+
cache,
784+
key: _,
785+
shape,
786+
}) = agent
787+
.heap
788+
.caches
789+
.take_current_cache_to_populate(property_key)
790+
{
791+
cache.insert_unset(agent, shape);
792+
}
793+
707794
// 5. Return false.
708795
Ok(false)
709796
}
@@ -716,9 +803,13 @@ pub(crate) fn ordinary_has_property_entry<'a, 'gc>(
716803
) -> JsResult<'gc, bool> {
717804
let property_key = property_key.bind(gc.nogc());
718805
match object.get_backing_object(agent) {
719-
Some(backing_object) => {
720-
ordinary_has_property(agent, backing_object, property_key.unbind(), gc)
721-
}
806+
Some(backing_object) => ordinary_has_property(
807+
agent,
808+
object.into_object(),
809+
backing_object,
810+
property_key.unbind(),
811+
gc,
812+
),
722813
None => {
723814
// 3. Let parent be ? O.[[GetPrototypeOf]]().
724815
let parent = unwrap_try(object.try_get_prototype_of(agent, gc.nogc()));

nova_vm/src/ecmascript/builtins/ordinary/caches.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
engine::{
1515
TryResult,
1616
context::{Bindable, GcToken, NoGcScope},
17+
rootable::{HeapRootData, HeapRootRef, Rootable},
1718
},
1819
heap::{
1920
CompactionLists, HeapMarkAndSweep, HeapSweepWeakReference, PropertyKeyHeap, WeakReference,
@@ -715,6 +716,29 @@ unsafe impl Bindable for PropertyLookupCache<'_> {
715716
}
716717
}
717718

719+
impl<'a> Rootable for PropertyLookupCache<'a> {
720+
type RootRepr = HeapRootRef;
721+
722+
fn to_root_repr(value: Self) -> Result<Self::RootRepr, HeapRootData> {
723+
Err(HeapRootData::PropertyLookupCache(value.unbind()))
724+
}
725+
726+
fn from_root_repr(value: &Self::RootRepr) -> Result<Self, HeapRootRef> {
727+
Err(*value)
728+
}
729+
730+
fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr {
731+
heap_ref
732+
}
733+
734+
fn from_heap_data(heap_data: HeapRootData) -> Option<Self> {
735+
match heap_data {
736+
HeapRootData::PropertyLookupCache(object) => Some(object),
737+
_ => None,
738+
}
739+
}
740+
}
741+
718742
const N: usize = 4;
719743

720744
#[derive(Debug)]

nova_vm/src/ecmascript/builtins/ordinary/shape.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ impl<'a> ObjectShape<'a> {
185185
// A cached lookup result was found.
186186
if offset.is_unset() {
187187
// The property is unset.
188-
Value::Undefined.into()
188+
GetCachedResult::Unset.into()
189189
} else {
190190
let o = prototype.unwrap_or_else(|| Object::try_from(receiver).unwrap());
191191
o.get_own_property_at_offset(agent, offset, gc)

0 commit comments

Comments
 (0)