Skip to content

Commit eef808d

Browse files
authored
refactor(ecmascript): cached_set install lookup on push (#820)
* refactor(ecmascript): set_cached install new lookup cache on push * perf(ecmascript): Optimise CreateUnmappedArgumentsObject * nit: dedupe PropertyStorage::add / PropertyStorage::push
1 parent 449c76c commit eef808d

File tree

26 files changed

+455
-574
lines changed

26 files changed

+455
-574
lines changed

nova_vm/src/ecmascript/builtins/arguments.rs

Lines changed: 81 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,21 @@
2626
//!
2727
//! ECMAScript implementations of arguments exotic objects have historically contained an accessor property named "caller". Prior to ECMAScript 2017, this specification included the definition of a throwing "caller" property on ordinary arguments objects. Since implementations do not contain this extension any longer, ECMAScript 2017 dropped the requirement for a throwing "caller" accessor.
2828
29+
use ahash::AHashMap;
30+
2931
use crate::{
3032
ecmascript::{
31-
abstract_operations::operations_on_objects::{
32-
try_create_data_property_or_throw, try_define_property_or_throw,
33-
},
34-
execution::{ProtoIntrinsics, agent::Agent},
33+
execution::agent::Agent,
3534
types::{
36-
BUILTIN_STRING_MEMORY, IntoFunction, IntoValue, Number, Object, PropertyDescriptor,
37-
PropertyKey,
35+
BUILTIN_STRING_MEMORY, IntoFunction, IntoObject, IntoValue, Number, Object,
36+
OrdinaryObject, Value,
3837
},
3938
},
40-
engine::{
41-
context::{Bindable, NoGcScope},
42-
unwrap_try,
43-
},
44-
heap::WellKnownSymbolIndexes,
39+
engine::context::{Bindable, NoGcScope},
40+
heap::{WellKnownSymbolIndexes, element_array::ElementDescriptor},
4541
};
4642

47-
use super::ScopedArgumentsList;
48-
use super::ordinary::ordinary_object_create_with_intrinsics;
43+
use super::{ScopedArgumentsList, ordinary::shape::ObjectShape};
4944

5045
// 10.4.4.1 [[GetOwnProperty]] ( P )
5146

@@ -132,99 +127,92 @@ pub(crate) fn create_unmapped_arguments_object<'a, 'b>(
132127
) -> Object<'a> {
133128
// 1. Let len be the number of elements in argumentsList.
134129
let len = arguments_list.len(agent);
135-
let len_value = Number::from_i64(agent, len as i64, gc)
136-
.into_value()
137-
.unbind();
130+
// SAFETY: GC is not allowed in this scope, and no other scoped values are
131+
// accessed during this call. The pointer is not held beyond the current call scope.
132+
let arguments_non_null_slice = unsafe { arguments_list.as_non_null_slice(agent) };
133+
debug_assert!(len < u32::MAX as usize);
134+
let len = len as u32;
135+
let len_value = Number::from(len).into_value();
138136
// 2. Let obj be OrdinaryObjectCreate(%Object.prototype%, « [[ParameterMap]] »).
139-
let obj =
140-
ordinary_object_create_with_intrinsics(agent, Some(ProtoIntrinsics::Object), None, gc);
141-
let Object::Object(obj) = obj else {
142-
unreachable!()
143-
};
137+
let prototype = agent.current_realm_record().intrinsics().object_prototype();
138+
let mut shape = ObjectShape::get_shape_for_prototype(agent, Some(prototype.into_object()));
139+
shape = shape.get_child_shape(agent, BUILTIN_STRING_MEMORY.length.to_property_key());
140+
shape = shape.get_child_shape(agent, BUILTIN_STRING_MEMORY.callee.into());
141+
shape = shape.get_child_shape(agent, WellKnownSymbolIndexes::Iterator.into());
142+
for index in 0..len {
143+
shape = shape.get_child_shape(agent, index.into());
144+
}
145+
let obj = OrdinaryObject::create_object_with_shape(agent, shape)
146+
.expect("Failed to create Arguments object storage");
147+
let array_prototype_values = agent
148+
.current_realm_record()
149+
.intrinsics()
150+
.array_prototype_values()
151+
.bind(gc)
152+
.into_value();
153+
let throw_type_error = agent
154+
.current_realm_record()
155+
.intrinsics()
156+
.throw_type_error()
157+
.into_function()
158+
.bind(gc);
159+
let storage = obj.get_elements_storage_uninit(agent);
160+
let values = storage.values;
161+
let descriptors = storage.descriptors.or_insert(AHashMap::with_capacity(3));
162+
144163
// 3. Set obj.[[ParameterMap]] to undefined.
145164
// 4. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor {
146-
let key = PropertyKey::from(BUILTIN_STRING_MEMORY.length);
147-
unwrap_try(try_define_property_or_throw(
148-
agent,
149-
obj,
150-
key,
151-
PropertyDescriptor {
152-
// [[Value]]: 𝔽(len),
153-
value: Some(len_value),
154-
// [[Writable]]: true,
155-
writable: Some(true),
156-
// [[Enumerable]]: false,
157-
enumerable: Some(false),
158-
// [[Configurable]]: true }).
159-
configurable: Some(true),
160-
..Default::default()
165+
// [[Value]]: 𝔽(len),
166+
// [[Writable]]: true,
167+
// [[Enumerable]]: false,
168+
// [[Configurable]]: true
169+
// }).
170+
171+
// "length"
172+
values[0] = Some(len_value.unbind());
173+
// "callee"
174+
values[1] = None;
175+
// Iterator
176+
values[2] = Some(array_prototype_values.unbind());
177+
// "length"
178+
descriptors.insert(0, ElementDescriptor::WritableUnenumerableConfigurableData);
179+
// "callee"
180+
descriptors.insert(
181+
1,
182+
ElementDescriptor::ReadWriteUnenumerableUnconfigurableAccessor {
183+
get: throw_type_error.unbind(),
184+
set: throw_type_error.unbind(),
161185
},
162-
gc,
163-
))
164-
.unwrap();
186+
);
187+
// Iterator
188+
descriptors.insert(2, ElementDescriptor::WritableUnenumerableConfigurableData);
165189
// 5. Let index be 0.
166190
// 6. Repeat, while index < len,
167191
for index in 0..len {
168192
// a. Let val be argumentsList[index].
169193
// b. Perform ! CreateDataPropertyOrThrow(obj, ! ToString(𝔽(index)), val).
170-
debug_assert!(index < u32::MAX as usize);
171-
let index = index as u32;
172-
let key = PropertyKey::Integer(index.into());
173-
let val = arguments_list.get(agent, index, gc);
174-
unwrap_try(try_create_data_property_or_throw(agent, obj, key, val, gc)).unwrap();
194+
// SAFETY: arguments slice valid in this call stack and we've not
195+
// performed GC or touched other scoped data.
196+
let val = unsafe { arguments_non_null_slice.as_ref() }
197+
.get(index as usize)
198+
.cloned()
199+
.unwrap_or(Value::Undefined);
200+
values[index as usize + 3] = Some(val);
175201
// c. Set index to index + 1.
176202
}
203+
agent[obj].set_len(len + 3);
177204
// 7. Perform ! DefinePropertyOrThrow(obj, @@iterator, PropertyDescriptor {
178-
let key = PropertyKey::Symbol(WellKnownSymbolIndexes::Iterator.into());
179-
unwrap_try(try_define_property_or_throw(
180-
agent,
181-
obj,
182-
key,
183-
PropertyDescriptor {
184-
// [[Value]]: %Array.prototype.values%,
185-
value: Some(
186-
agent
187-
.current_realm_record()
188-
.intrinsics()
189-
.array_prototype_values()
190-
.into_value(),
191-
),
192-
// [[Writable]]: true,
193-
writable: Some(true),
194-
// [[Enumerable]]: false,
195-
enumerable: Some(false),
196-
// [[Configurable]]: true }).
197-
configurable: Some(true),
198-
..Default::default()
199-
},
200-
gc,
201-
))
202-
.unwrap();
203-
let throw_type_error = agent
204-
.current_realm_record()
205-
.intrinsics()
206-
.throw_type_error()
207-
.bind(gc);
205+
// [[Value]]: %Array.prototype.values%,
206+
// [[Writable]]: true,
207+
// [[Enumerable]]: false,
208+
// [[Configurable]]: true
209+
// }).
208210
// 8. Perform ! DefinePropertyOrThrow(obj, "callee", PropertyDescriptor {
209-
let key = PropertyKey::from(BUILTIN_STRING_MEMORY.callee);
210-
unwrap_try(try_define_property_or_throw(
211-
agent,
212-
obj,
213-
key,
214-
PropertyDescriptor {
215-
// [[Get]]: %ThrowTypeError%,
216-
get: Some(Some(throw_type_error.into_function())),
217-
// [[Set]]: %ThrowTypeError%,
218-
set: Some(Some(throw_type_error.into_function())),
219-
// [[Enumerable]]: false,
220-
enumerable: Some(false),
221-
// [[Configurable]]: false }).
222-
configurable: Some(false),
223-
..Default::default()
224-
},
225-
gc,
226-
))
227-
.unwrap();
211+
// [[Get]]: %ThrowTypeError%,
212+
// [[Set]]: %ThrowTypeError%,
213+
// [[Enumerable]]: false,
214+
// [[Configurable]]: false
215+
// }).
228216
// 9. Return obj.
229217
Object::Arguments(obj)
230218
}

nova_vm/src/ecmascript/builtins/array.rs

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use crate::{
2626
types::{
2727
BUILTIN_STRING_MEMORY, Function, GetCachedResult, InternalMethods, InternalSlots,
2828
IntoFunction, IntoObject, IntoValue, NoCache, Object, OrdinaryObject,
29-
PropertyDescriptor, PropertyKey, SetCachedResult, Value,
29+
PropertyDescriptor, PropertyKey, SetCachedProps, SetCachedResult, Value,
3030
},
3131
},
3232
engine::{
@@ -53,7 +53,6 @@ use super::{
5353
ordinary::{
5454
caches::PropertyLookupCache, ordinary_delete, ordinary_get, ordinary_get_own_property,
5555
ordinary_has_property, ordinary_try_get, ordinary_try_has_property,
56-
shape::ShapeSetCachedProps,
5756
},
5857
};
5958

@@ -926,20 +925,17 @@ impl<'a> InternalMethods<'a> for Array<'a> {
926925
fn set_cached<'gc>(
927926
self,
928927
agent: &mut Agent,
929-
p: PropertyKey,
930-
value: Value,
931-
receiver: Value,
932-
cache: PropertyLookupCache,
928+
props: &SetCachedProps,
933929
gc: NoGcScope<'gc, '_>,
934930
) -> ControlFlow<SetCachedResult<'gc>, NoCache> {
935931
// Cached set of an Array should return directly mutate the Array's
936932
// internal memory if it can.
937-
if p == BUILTIN_STRING_MEMORY.length.to_property_key() {
933+
if props.p == BUILTIN_STRING_MEMORY.length.to_property_key() {
938934
// Length lookup: we find it always.
939935
if !self.length_writable(agent) {
940936
return SetCachedResult::Unwritable.into();
941937
}
942-
if let Value::Integer(value) = value
938+
if let Value::Integer(value) = props.value
943939
&& let Ok(value) = u32::try_from(value.into_i64())
944940
{
945941
let Ok(result) = array_set_length_handling(agent, self, value, None, None, None)
@@ -955,7 +951,7 @@ impl<'a> InternalMethods<'a> for Array<'a> {
955951
} else {
956952
return NoCache.into();
957953
}
958-
} else if let Some(index) = p.into_u32() {
954+
} else if let Some(index) = props.p.into_u32() {
959955
// Indexed lookup: check our slice. First bounds-check.
960956
if !(0..self.len(agent)).contains(&index) {
961957
// We're out of bounds; this need prototype lookups.
@@ -990,7 +986,7 @@ impl<'a> InternalMethods<'a> for Array<'a> {
990986
// dealing with.
991987
if let Some(slot) = &mut storage.values[index as usize] {
992988
// Writable data property it is! Set its value.
993-
*slot = value.unbind();
989+
*slot = props.value.unbind();
994990
return SetCachedResult::Done.into();
995991
}
996992
// Hole! We'll just return NoCache to signify that we can't be
@@ -1000,17 +996,7 @@ impl<'a> InternalMethods<'a> for Array<'a> {
1000996
// If this was a non-Array index or a named property on the Array then
1001997
// we want to perform a normal cached set with the Array's shape.
1002998
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-
)
999+
shape.set_cached(agent, self.into_object(), props, gc)
10141000
}
10151001
}
10161002

nova_vm/src/ecmascript/builtins/bound_function.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use crate::{
1515
types::{
1616
BoundFunctionHeapData, Function, FunctionInternalProperties, GetCachedResult,
1717
InternalMethods, InternalSlots, IntoFunction, IntoValue, NoCache, Object,
18-
OrdinaryObject, PropertyDescriptor, PropertyKey, SetCachedResult, String, Value,
19-
function_create_backing_object, function_get_cached,
18+
OrdinaryObject, PropertyDescriptor, PropertyKey, SetCachedProps, SetCachedResult,
19+
String, Value, function_create_backing_object, function_get_cached,
2020
function_internal_define_own_property, function_internal_delete, function_internal_get,
2121
function_internal_get_own_property, function_internal_has_property,
2222
function_internal_own_property_keys, function_internal_set, function_set_cached,
@@ -307,13 +307,10 @@ impl<'a> InternalMethods<'a> for BoundFunction<'a> {
307307
fn set_cached<'gc>(
308308
self,
309309
agent: &mut Agent,
310-
p: PropertyKey,
311-
value: Value,
312-
receiver: Value,
313-
cache: PropertyLookupCache,
310+
props: &SetCachedProps,
314311
gc: NoGcScope<'gc, '_>,
315312
) -> ControlFlow<SetCachedResult<'gc>, NoCache> {
316-
function_set_cached(self, agent, p, value, receiver, cache, gc)
313+
function_set_cached(self, agent, props, gc)
317314
}
318315

319316
/// ### [10.4.1.1 \[\[Call\]\] ( thisArgument, argumentsList )](https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist)

nova_vm/src/ecmascript/builtins/builtin_constructor.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::{
2121
BUILTIN_STRING_MEMORY, BuiltinConstructorHeapData, Function,
2222
FunctionInternalProperties, GetCachedResult, InternalMethods, InternalSlots,
2323
IntoFunction, IntoObject, IntoValue, NoCache, Object, OrdinaryObject,
24-
PropertyDescriptor, PropertyKey, SetCachedResult, String, Value,
24+
PropertyDescriptor, PropertyKey, SetCachedProps, SetCachedResult, String, Value,
2525
function_create_backing_object, function_get_cached,
2626
function_internal_define_own_property, function_internal_delete, function_internal_get,
2727
function_internal_get_own_property, function_internal_has_property,
@@ -316,13 +316,10 @@ impl<'a> InternalMethods<'a> for BuiltinConstructorFunction<'a> {
316316
fn set_cached<'gc>(
317317
self,
318318
agent: &mut Agent,
319-
p: PropertyKey,
320-
value: Value,
321-
receiver: Value,
322-
cache: PropertyLookupCache,
319+
props: &SetCachedProps,
323320
gc: NoGcScope<'gc, '_>,
324321
) -> ControlFlow<SetCachedResult<'gc>, NoCache> {
325-
function_set_cached(self, agent, p, value, receiver, cache, gc)
322+
function_set_cached(self, agent, props, gc)
326323
}
327324

328325
/// ### [10.3.1 \[\[Call\]\] ( thisArgument, argumentsList )](https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist)

nova_vm/src/ecmascript/builtins/builtin_function.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ use crate::{
1717
BUILTIN_STRING_MEMORY, BuiltinFunctionHeapData, Function, FunctionInternalProperties,
1818
GetCachedResult, InternalMethods, InternalSlots, IntoFunction, IntoObject, IntoValue,
1919
NoCache, Object, OrdinaryObject, PropertyDescriptor, PropertyKey, ScopedValuesIterator,
20-
SetCachedResult, String, Value, function_create_backing_object, function_get_cached,
21-
function_internal_define_own_property, function_internal_delete, function_internal_get,
22-
function_internal_get_own_property, function_internal_has_property,
23-
function_internal_own_property_keys, function_internal_set, function_set_cached,
24-
function_try_get, function_try_has_property, function_try_set,
20+
SetCachedProps, SetCachedResult, String, Value, function_create_backing_object,
21+
function_get_cached, function_internal_define_own_property, function_internal_delete,
22+
function_internal_get, function_internal_get_own_property,
23+
function_internal_has_property, function_internal_own_property_keys,
24+
function_internal_set, function_set_cached, function_try_get,
25+
function_try_has_property, function_try_set,
2526
},
2627
},
2728
engine::{
@@ -342,6 +343,26 @@ impl<'scope> ScopedArgumentsList<'scope> {
342343
unreachable!()
343344
}
344345
}
346+
347+
/// Get access to the backing reference slice as a pointer slice.
348+
///
349+
/// ## Safety
350+
///
351+
/// Garbage collection must not be called while this slice is exposed.
352+
///
353+
/// Stack values must not be accessed while this slice is exposed.
354+
pub(crate) unsafe fn as_non_null_slice(&self, agent: &Agent) -> NonNull<[Value<'static>]> {
355+
if let HeapRootCollectionData::ArgumentsList(args) = agent
356+
.stack_ref_collections
357+
.borrow()
358+
.get(self.index as usize)
359+
.unwrap()
360+
{
361+
*args
362+
} else {
363+
unreachable!()
364+
}
365+
}
345366
}
346367

347368
impl core::fmt::Debug for ScopedArgumentsList<'_> {
@@ -684,13 +705,10 @@ impl<'a> InternalMethods<'a> for BuiltinFunction<'a> {
684705
fn set_cached<'gc>(
685706
self,
686707
agent: &mut Agent,
687-
p: PropertyKey,
688-
value: Value,
689-
receiver: Value,
690-
cache: PropertyLookupCache,
708+
props: &SetCachedProps,
691709
gc: NoGcScope<'gc, '_>,
692710
) -> ControlFlow<SetCachedResult<'gc>, NoCache> {
693-
function_set_cached(self, agent, p, value, receiver, cache, gc)
711+
function_set_cached(self, agent, props, gc)
694712
}
695713

696714
/// ### [10.3.1 \[\[Call\]\] ( thisArgument, argumentsList )](https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist)

0 commit comments

Comments
 (0)