Skip to content

Commit 449c76c

Browse files
authored
feat(ecmascript): Non-Ordinary Object caches (#819)
1 parent 68e427c commit 449c76c

File tree

35 files changed

+3556
-1277
lines changed

35 files changed

+3556
-1277
lines changed

nova_vm/src/ecmascript/abstract_operations/type_conversion.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ use crate::{
4040

4141
use super::{
4242
operations_on_objects::{call_function, get, get_method},
43-
testing_and_comparison::is_callable,
43+
testing_and_comparison::{is_callable, require_object_coercible},
4444
};
4545

4646
#[derive(Debug, Clone, Copy)]
@@ -1149,12 +1149,9 @@ pub(crate) fn to_object<'a>(
11491149
gc: NoGcScope<'a, '_>,
11501150
) -> JsResult<'a, Object<'a>> {
11511151
let argument = argument.bind(gc);
1152+
require_object_coercible(agent, argument, gc)?;
11521153
match argument {
1153-
Value::Undefined | Value::Null => Err(agent.throw_exception_with_static_message(
1154-
ExceptionType::TypeError,
1155-
"Argument cannot be converted into an object",
1156-
gc,
1157-
)),
1154+
Value::Undefined | Value::Null => unreachable!(),
11581155
// Return a new Boolean object whose [[BooleanData]] internal slot is set to argument.
11591156
Value::Boolean(bool) => Ok(agent
11601157
.heap

nova_vm/src/ecmascript/builtins/array.rs

Lines changed: 200 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub(crate) mod abstract_operations;
1010
mod data;
1111

1212
use core::ops::{Index, IndexMut, RangeInclusive};
13-
use std::collections::hash_map::Entry;
13+
use std::{collections::hash_map::Entry, ops::ControlFlow};
1414

1515
use crate::{
1616
ecmascript::{
@@ -20,12 +20,13 @@ use crate::{
2020
},
2121
builtins::{
2222
array::abstract_operations::{array_set_length, array_try_set_length},
23-
ordinary::ordinary_define_own_property,
23+
ordinary::{caches::Caches, ordinary_define_own_property},
2424
},
2525
execution::{Agent, JsResult, ProtoIntrinsics},
2626
types::{
27-
BUILTIN_STRING_MEMORY, Function, InternalMethods, InternalSlots, IntoFunction,
28-
IntoObject, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, Value,
27+
BUILTIN_STRING_MEMORY, Function, GetCachedResult, InternalMethods, InternalSlots,
28+
IntoFunction, IntoObject, IntoValue, NoCache, Object, OrdinaryObject,
29+
PropertyDescriptor, PropertyKey, SetCachedResult, Value,
2930
},
3031
},
3132
engine::{
@@ -47,9 +48,13 @@ use crate::{
4748
use ahash::AHashMap;
4849
pub use data::ArrayHeapData;
4950

50-
use super::ordinary::{
51-
ordinary_delete, ordinary_get, ordinary_get_own_property, ordinary_has_property,
52-
ordinary_try_get, ordinary_try_has_property,
51+
use super::{
52+
array_set_length_handling,
53+
ordinary::{
54+
caches::PropertyLookupCache, ordinary_delete, ordinary_get, ordinary_get_own_property,
55+
ordinary_has_property, ordinary_try_get, ordinary_try_has_property,
56+
shape::ShapeSetCachedProps,
57+
},
5358
};
5459

5560
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -407,8 +412,14 @@ impl<'a> InternalMethods<'a> for Array<'a> {
407412
}))
408413
} else if let Some(backing_object) = array_data.object_index {
409414
TryResult::Continue(
410-
ordinary_get_own_property(agent, self.into_object(), backing_object, property_key)
411-
.bind(gc),
415+
ordinary_get_own_property(
416+
agent,
417+
self.into_object(),
418+
backing_object,
419+
property_key,
420+
gc,
421+
)
422+
.bind(gc),
412423
)
413424
} else {
414425
TryResult::Continue(None)
@@ -478,13 +489,28 @@ impl<'a> InternalMethods<'a> for Array<'a> {
478489
// j. If index ≥ length, then
479490
// i. Set lengthDesc.[[Value]] to index + 1𝔽.
480491
// This should've already been handled by the push.
481-
debug_assert_eq!(agent[self].elements.len(), index + 1);
492+
debug_assert_eq!(array_heap_data.elements.len(), index + 1);
493+
if let Some(shape) = array_heap_data.object_index.map(|o| o.object_shape(agent))
494+
&& shape.is_intrinsic(agent)
495+
{
496+
// We set a value on an intrinsic object, we have to
497+
// invalidate caches.
498+
Caches::invalidate_caches_on_intrinsic_shape_property_addition(
499+
agent,
500+
self.into_object(),
501+
shape,
502+
index.into(),
503+
u32::MAX,
504+
gc,
505+
);
506+
}
482507
// iii. Assert: succeeded is true.
483508
TryResult::Continue(true)
484509
} else {
485510
// h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc).
486511
TryResult::Continue(ordinary_define_own_property_for_array(
487512
agent,
513+
self,
488514
elements,
489515
index,
490516
property_descriptor,
@@ -847,6 +873,145 @@ impl<'a> InternalMethods<'a> for Array<'a> {
847873

848874
TryResult::Continue(keys)
849875
}
876+
877+
fn get_cached<'gc>(
878+
self,
879+
agent: &mut Agent,
880+
p: PropertyKey,
881+
cache: PropertyLookupCache,
882+
gc: NoGcScope<'gc, '_>,
883+
) -> ControlFlow<GetCachedResult<'gc>, NoCache> {
884+
// Cached lookup of an Array should return directly from the Array's
885+
// internal memory if it can.
886+
if p == BUILTIN_STRING_MEMORY.length.to_property_key() {
887+
// Length lookup: we find it always.
888+
return self.len(agent).into_value().into();
889+
} else if let Some(index) = p.into_u32() {
890+
// Indexed lookup: check our slice.
891+
if let Some(value) = self.as_slice(agent).get(index as usize) {
892+
// Found a slot in the slice, check if it contains a Value.
893+
if let Some(value) = value {
894+
// Slot contained value, return it.
895+
return value.bind(gc).into();
896+
}
897+
// Slot did not contain a value; this is either a hole or an
898+
// accessor property.
899+
let ElementStorageRef { descriptors, .. } = self.get_storage(agent);
900+
if let Some(desc) = descriptors.and_then(|d| d.get(&index)) {
901+
// This was an accessor property; if it has a getter,
902+
// return that. Otherwise, return undefined.
903+
debug_assert!(desc.is_accessor_descriptor());
904+
if let Some(getter) = desc.getter_function(gc) {
905+
return GetCachedResult::Get(getter.bind(gc)).into();
906+
} else {
907+
return Value::Undefined.into();
908+
}
909+
}
910+
// This was a hole, continue into the prototype chain.
911+
}
912+
}
913+
// If this was an over-indexing, a hole, or a named property on the
914+
// Array then we want to perform a normal cached lookup with the
915+
// Array's shape.
916+
let shape = self.object_shape(agent);
917+
shape.get_cached(
918+
agent,
919+
p.bind(gc),
920+
self.into_value().bind(gc),
921+
cache.bind(gc),
922+
gc,
923+
)
924+
}
925+
926+
fn set_cached<'gc>(
927+
self,
928+
agent: &mut Agent,
929+
p: PropertyKey,
930+
value: Value,
931+
receiver: Value,
932+
cache: PropertyLookupCache,
933+
gc: NoGcScope<'gc, '_>,
934+
) -> ControlFlow<SetCachedResult<'gc>, NoCache> {
935+
// Cached set of an Array should return directly mutate the Array's
936+
// internal memory if it can.
937+
if p == BUILTIN_STRING_MEMORY.length.to_property_key() {
938+
// Length lookup: we find it always.
939+
if !self.length_writable(agent) {
940+
return SetCachedResult::Unwritable.into();
941+
}
942+
if let Value::Integer(value) = value
943+
&& let Ok(value) = u32::try_from(value.into_i64())
944+
{
945+
let Ok(result) = array_set_length_handling(agent, self, value, None, None, None)
946+
else {
947+
// Let caller handle retry and error on TryReserveError.
948+
return NoCache.into();
949+
};
950+
return if result {
951+
SetCachedResult::Done.into()
952+
} else {
953+
SetCachedResult::Unwritable.into()
954+
};
955+
} else {
956+
return NoCache.into();
957+
}
958+
} else if let Some(index) = p.into_u32() {
959+
// Indexed lookup: check our slice. First bounds-check.
960+
if !(0..self.len(agent)).contains(&index) {
961+
// We're out of bounds; this need prototype lookups.
962+
return NoCache.into();
963+
}
964+
// Index within slice; let's look into that memory.
965+
let storage = self.get_storage_mut(agent);
966+
// First check if we have a descriptor at our index.
967+
let desc = match storage.descriptors {
968+
Entry::Occupied(e) => e.into_mut().get(&index),
969+
Entry::Vacant(_) => None,
970+
};
971+
if let Some(desc) = desc {
972+
// Found a descriptor; see if it's an accessor.
973+
if desc.is_accessor_descriptor() {
974+
// Found an accessor indeed; see if it has a setter,
975+
// and return that if so.
976+
if let Some(setter) = desc.setter_function(gc) {
977+
return SetCachedResult::Set(setter).into();
978+
}
979+
// No setter on this accessor; trying to set the value
980+
// fails.
981+
return SetCachedResult::Accessor.into();
982+
}
983+
// Data descriptor; see if it's not writable.
984+
if !desc.is_writable().unwrap() {
985+
// Not writable; return failure.
986+
return SetCachedResult::Unwritable.into();
987+
}
988+
}
989+
// Writable data property or hole; check which one we're
990+
// dealing with.
991+
if let Some(slot) = &mut storage.values[index as usize] {
992+
// Writable data property it is! Set its value.
993+
*slot = value.unbind();
994+
return SetCachedResult::Done.into();
995+
}
996+
// Hole! We'll just return NoCache to signify that we can't be
997+
// arsed to implement the entire prototype lookup logic here.
998+
return NoCache.into();
999+
}
1000+
// If this was a non-Array index or a named property on the Array then
1001+
// we want to perform a normal cached set with the Array's shape.
1002+
let shape = self.object_shape(agent);
1003+
shape.set_cached(
1004+
agent,
1005+
ShapeSetCachedProps {
1006+
o: self.into_object(),
1007+
p,
1008+
receiver,
1009+
},
1010+
value,
1011+
cache,
1012+
gc,
1013+
)
1014+
}
8501015
}
8511016

8521017
impl Index<Array<'_>> for Agent {
@@ -930,8 +1095,30 @@ impl HeapSweepWeakReference for Array<'static> {
9301095
}
9311096
}
9321097

1098+
/// Helper to invalidate property lookup caches associated with an index when
1099+
/// an intrinsic Array is mutated.
1100+
fn invalidate_array_index_caches(agent: &mut Agent, array: Array, index: u32, gc: NoGcScope) {
1101+
if let Some(shape) = array
1102+
.get_backing_object(agent)
1103+
.map(|o| o.object_shape(agent))
1104+
&& shape.is_intrinsic(agent)
1105+
{
1106+
// We set a value on an intrinsic object, we have to
1107+
// invalidate caches.
1108+
Caches::invalidate_caches_on_intrinsic_shape_property_addition(
1109+
agent,
1110+
array.into_object(),
1111+
shape,
1112+
index.into(),
1113+
u32::MAX,
1114+
gc,
1115+
);
1116+
}
1117+
}
1118+
9331119
fn ordinary_define_own_property_for_array(
9341120
agent: &mut Agent,
1121+
array: Array,
9351122
elements: ElementsVector,
9361123
index: u32,
9371124
descriptor: PropertyDescriptor,
@@ -970,6 +1157,7 @@ fn ordinary_define_own_property_for_array(
9701157
// value otherwise.
9711158
let elem_descriptor = ElementDescriptor::from_accessor_descriptor(descriptor);
9721159
insert_element_descriptor(agent, &elements, index, None, elem_descriptor);
1160+
invalidate_array_index_caches(agent, array, index, gc);
9731161
}
9741162
// d. Else,
9751163
else {
@@ -984,6 +1172,7 @@ fn ordinary_define_own_property_for_array(
9841172
Some(descriptor_value.unwrap_or(Value::Undefined)),
9851173
ElementDescriptor::from_data_descriptor(descriptor),
9861174
);
1175+
invalidate_array_index_caches(agent, array, index, gc);
9871176
}
9881177

9891178
// e. Return true.
@@ -1084,6 +1273,7 @@ fn ordinary_define_own_property_for_array(
10841273
configurable,
10851274
);
10861275
insert_element_descriptor(agent, &elements, index, None, elem_descriptor);
1276+
invalidate_array_index_caches(agent, array, index, gc);
10871277
}
10881278
// b. Else if IsAccessorDescriptor(current) is true and IsDataDescriptor(Desc) is true, then
10891279
else if current_is_accessor_descriptor && descriptor.is_data_descriptor() {

nova_vm/src/ecmascript/builtins/array/abstract_operations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ fn array_set_length_no_value_field(agent: &mut Agent, a: Array, desc: PropertyDe
306306
true
307307
}
308308

309-
fn array_set_length_handling(
309+
pub(crate) fn array_set_length_handling(
310310
agent: &mut Agent,
311311
a: Array,
312312
new_len: u32,

nova_vm/src/ecmascript/builtins/bound_function.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

55
use core::ops::{Index, IndexMut};
6+
use std::ops::ControlFlow;
67

78
use crate::{
89
ecmascript::{
@@ -12,13 +13,14 @@ use crate::{
1213
},
1314
execution::{Agent, JsResult, ProtoIntrinsics, agent::ExceptionType},
1415
types::{
15-
BoundFunctionHeapData, Function, FunctionInternalProperties, InternalMethods,
16-
InternalSlots, IntoFunction, IntoValue, Object, OrdinaryObject, PropertyDescriptor,
17-
PropertyKey, String, Value, function_create_backing_object,
16+
BoundFunctionHeapData, Function, FunctionInternalProperties, GetCachedResult,
17+
InternalMethods, InternalSlots, IntoFunction, IntoValue, NoCache, Object,
18+
OrdinaryObject, PropertyDescriptor, PropertyKey, SetCachedResult, String, Value,
19+
function_create_backing_object, function_get_cached,
1820
function_internal_define_own_property, function_internal_delete, function_internal_get,
1921
function_internal_get_own_property, function_internal_has_property,
20-
function_internal_own_property_keys, function_internal_set, function_try_get,
21-
function_try_has_property, function_try_set,
22+
function_internal_own_property_keys, function_internal_set, function_set_cached,
23+
function_try_get, function_try_has_property, function_try_set,
2224
},
2325
},
2426
engine::{
@@ -33,7 +35,7 @@ use crate::{
3335
},
3436
};
3537

36-
use super::ArgumentsList;
38+
use super::{ArgumentsList, ordinary::caches::PropertyLookupCache};
3739

3840
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
3941
#[repr(transparent)]
@@ -292,6 +294,28 @@ impl<'a> InternalMethods<'a> for BoundFunction<'a> {
292294
TryResult::Continue(function_internal_own_property_keys(self, agent, gc))
293295
}
294296

297+
fn get_cached<'gc>(
298+
self,
299+
agent: &mut Agent,
300+
p: PropertyKey,
301+
cache: PropertyLookupCache,
302+
gc: NoGcScope<'gc, '_>,
303+
) -> ControlFlow<GetCachedResult<'gc>, NoCache> {
304+
function_get_cached(self, agent, p, cache, gc)
305+
}
306+
307+
fn set_cached<'gc>(
308+
self,
309+
agent: &mut Agent,
310+
p: PropertyKey,
311+
value: Value,
312+
receiver: Value,
313+
cache: PropertyLookupCache,
314+
gc: NoGcScope<'gc, '_>,
315+
) -> ControlFlow<SetCachedResult<'gc>, NoCache> {
316+
function_set_cached(self, agent, p, value, receiver, cache, gc)
317+
}
318+
295319
/// ### [10.4.1.1 \[\[Call\]\] ( thisArgument, argumentsList )](https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist)
296320
///
297321
/// The \[\[Call]] internal method of a bound function exotic object F

0 commit comments

Comments
 (0)