Browse Source

Implement Inline Caching (#2767)

pull/3500/head
Haled Odat 12 months ago committed by GitHub
parent
commit
a9c33cdff7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      boa_engine/src/builtins/array/mod.rs
  2. 5
      boa_engine/src/builtins/function/tests.rs
  3. 6
      boa_engine/src/builtins/json/mod.rs
  4. 12
      boa_engine/src/builtins/object/for_in_iterator.rs
  5. 52
      boa_engine/src/builtins/object/mod.rs
  6. 44
      boa_engine/src/builtins/reflect/mod.rs
  7. 2
      boa_engine/src/builtins/temporal/calendar/mod.rs
  8. 2
      boa_engine/src/builtins/temporal/calendar/object.rs
  9. 2
      boa_engine/src/builtins/temporal/mod.rs
  10. 2
      boa_engine/src/builtins/typed_array/builtin.rs
  11. 18
      boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
  12. 10
      boa_engine/src/bytecompiler/expression/assign.rs
  13. 3
      boa_engine/src/bytecompiler/expression/mod.rs
  14. 10
      boa_engine/src/bytecompiler/expression/update.rs
  15. 54
      boa_engine/src/bytecompiler/mod.rs
  16. 7
      boa_engine/src/context/mod.rs
  17. 7
      boa_engine/src/environments/runtime/mod.rs
  18. 14
      boa_engine/src/object/internal_methods/arguments.rs
  19. 8
      boa_engine/src/object/internal_methods/array.rs
  20. 16
      boa_engine/src/object/internal_methods/integer_indexed.rs
  21. 198
      boa_engine/src/object/internal_methods/mod.rs
  22. 14
      boa_engine/src/object/internal_methods/module_namespace.rs
  23. 30
      boa_engine/src/object/internal_methods/proxy.rs
  24. 6
      boa_engine/src/object/internal_methods/string.rs
  25. 4
      boa_engine/src/object/jsobject.rs
  26. 99
      boa_engine/src/object/operations.rs
  27. 40
      boa_engine/src/object/property_map.rs
  28. 38
      boa_engine/src/object/shape/mod.rs
  29. 32
      boa_engine/src/object/shape/shared_shape/mod.rs
  30. 33
      boa_engine/src/object/shape/slot.rs
  31. 30
      boa_engine/src/object/shape/unique_shape.rs
  32. 22
      boa_engine/src/value/tests.rs
  33. 65
      boa_engine/src/vm/code_block.rs
  34. 1
      boa_engine/src/vm/mod.rs
  35. 16
      boa_engine/src/vm/opcode/define/class/getter.rs
  36. 7
      boa_engine/src/vm/opcode/define/class/method.rs
  37. 15
      boa_engine/src/vm/opcode/define/class/setter.rs
  38. 5
      boa_engine/src/vm/opcode/define/own_property.rs
  39. 11
      boa_engine/src/vm/opcode/delete/mod.rs
  40. 7
      boa_engine/src/vm/opcode/environment/mod.rs
  41. 55
      boa_engine/src/vm/opcode/get/property.rs
  42. 8
      boa_engine/src/vm/opcode/push/class/private.rs
  43. 6
      boa_engine/src/vm/opcode/set/class_prototype.rs
  44. 2
      boa_engine/src/vm/opcode/set/private.rs
  45. 89
      boa_engine/src/vm/opcode/set/property.rs
  46. 3
      boa_engine/src/vm/opcode/set/prototype.rs

7
boa_engine/src/builtins/array/mod.rs

@ -20,7 +20,10 @@ use crate::{
context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
error::JsNativeError,
js_string,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, CONSTRUCTOR},
object::{
internal_methods::{get_prototype_from_constructor, InternalMethodContext},
JsObject, ObjectData, CONSTRUCTOR,
},
property::{Attribute, PropertyDescriptor, PropertyNameKind},
realm::Realm,
string::common::StaticJsStrings,
@ -349,7 +352,7 @@ impl Array {
.enumerable(false)
.configurable(false)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(array)

5
boa_engine/src/builtins/function/tests.rs

@ -153,7 +153,10 @@ fn closure_capture_clone() {
let hw = js_string!(
string,
&object
.__get_own_property__(&js_string!("key").into(), context)?
.__get_own_property__(
&js_string!("key").into(),
&mut context.into()
)?
.and_then(|prop| prop.value().cloned())
.and_then(|val| val.as_string().cloned())
.ok_or_else(

6
boa_engine/src/builtins/json/mod.rs

@ -23,7 +23,7 @@ use crate::{
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::JsObject,
object::{internal_methods::InternalMethodContext, JsObject},
property::{Attribute, PropertyNameKind},
realm::Realm,
string::{common::StaticJsStrings, utf16, CodePoint},
@ -197,7 +197,7 @@ impl Json {
// 3. If newElement is undefined, then
if new_element.is_undefined() {
// a. Perform ? val.[[Delete]](prop).
obj.__delete__(&i.into(), context)?;
obj.__delete__(&i.into(), &mut InternalMethodContext::new(context))?;
}
// 4. Else,
else {
@ -226,7 +226,7 @@ impl Json {
// 2. If newElement is undefined, then
if new_element.is_undefined() {
// a. Perform ? val.[[Delete]](P).
obj.__delete__(&p.into(), context)?;
obj.__delete__(&p.into(), &mut InternalMethodContext::new(context))?;
}
// 3. Else,
else {

12
boa_engine/src/builtins/object/for_in_iterator.rs

@ -13,7 +13,7 @@ use crate::{
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::{JsObject, ObjectData},
object::{internal_methods::InternalMethodContext, JsObject, ObjectData},
property::PropertyKey,
realm::Realm,
Context, JsResult, JsString, JsValue,
@ -106,7 +106,8 @@ impl ForInIterator {
let mut object = iterator.object.to_object(context)?;
loop {
if !iterator.object_was_visited {
let keys = object.__own_property_keys__(context)?;
let keys =
object.__own_property_keys__(&mut InternalMethodContext::new(context))?;
for k in keys {
match k {
PropertyKey::String(ref k) => {
@ -124,9 +125,10 @@ impl ForInIterator {
}
while let Some(r) = iterator.remaining_keys.pop_front() {
if !iterator.visited_keys.contains(&r) {
if let Some(desc) =
object.__get_own_property__(&PropertyKey::from(r.clone()), context)?
{
if let Some(desc) = object.__get_own_property__(
&PropertyKey::from(r.clone()),
&mut InternalMethodContext::new(context),
)? {
iterator.visited_keys.insert(r.clone());
if desc.expect_enumerable() {
return Ok(create_iter_result_object(JsValue::new(r), false, context));

52
boa_engine/src/builtins/object/mod.rs

@ -23,8 +23,8 @@ use crate::{
js_string,
native_function::NativeFunction,
object::{
internal_methods::get_prototype_from_constructor, FunctionObjectBuilder, IntegrityLevel,
JsObject, ObjectData, ObjectKind,
internal_methods::{get_prototype_from_constructor, InternalMethodContext},
FunctionObjectBuilder, IntegrityLevel, JsObject, ObjectData, ObjectKind,
},
property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
realm::Realm,
@ -211,7 +211,7 @@ impl Object {
let obj = this.to_object(context)?;
// 2. Return ? O.[[GetPrototypeOf]]().
let proto = obj.__get_prototype_of__(context)?;
let proto = obj.__get_prototype_of__(&mut InternalMethodContext::new(context))?;
Ok(proto.map_or(JsValue::Null, JsValue::new))
}
@ -248,7 +248,8 @@ impl Object {
};
// 4. Let status be ? O.[[SetPrototypeOf]](proto).
let status = object.__set_prototype_of__(proto, context)?;
let status =
object.__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?;
// 5. If status is false, throw a TypeError exception.
if !status {
@ -371,7 +372,8 @@ impl Object {
// 3. Repeat
loop {
// a. Let desc be ? O.[[GetOwnProperty]](key).
let desc = obj.__get_own_property__(&key, context)?;
let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
// b. If desc is not undefined, then
if let Some(current_desc) = desc {
@ -383,7 +385,7 @@ impl Object {
Ok(JsValue::undefined())
};
}
match obj.__get_prototype_of__(context)? {
match obj.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
// c. Set O to ? O.[[GetPrototypeOf]]().
Some(o) => obj = o,
// d. If O is null, return undefined.
@ -415,7 +417,8 @@ impl Object {
// 3. Repeat
loop {
// a. Let desc be ? O.[[GetOwnProperty]](key).
let desc = obj.__get_own_property__(&key, context)?;
let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
// b. If desc is not undefined, then
if let Some(current_desc) = desc {
@ -427,7 +430,7 @@ impl Object {
Ok(JsValue::undefined())
};
}
match obj.__get_prototype_of__(context)? {
match obj.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
// c. Set O to ? O.[[GetPrototypeOf]]().
Some(o) => obj = o,
// d. If O is null, return undefined.
@ -496,7 +499,8 @@ impl Object {
let key = args.get_or_undefined(1).to_property_key(context)?;
// 3. Let desc be ? obj.[[GetOwnProperty]](key).
let desc = obj.__get_own_property__(&key, context)?;
let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
// 4. Return FromPropertyDescriptor(desc).
Ok(Self::from_property_descriptor(desc, context))
@ -521,7 +525,7 @@ impl Object {
let obj = args.get_or_undefined(0).to_object(context)?;
// 2. Let ownKeys be ? obj.[[OwnPropertyKeys]]().
let own_keys = obj.__own_property_keys__(context)?;
let own_keys = obj.__own_property_keys__(&mut InternalMethodContext::new(context))?;
// 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%).
let descriptors = JsObject::with_object_proto(context.intrinsics());
@ -529,7 +533,8 @@ impl Object {
// 4. For each element key of ownKeys, do
for key in own_keys {
// a. Let desc be ? obj.[[GetOwnProperty]](key).
let desc = obj.__get_own_property__(&key, context)?;
let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
// b. Let descriptor be FromPropertyDescriptor(desc).
let descriptor = Self::from_property_descriptor(desc, context);
@ -642,7 +647,7 @@ impl Object {
// 2. Return ? obj.[[GetPrototypeOf]]().
Ok(obj
.__get_prototype_of__(context)?
.__get_prototype_of__(&mut InternalMethodContext::new(context))?
.map_or(JsValue::Null, JsValue::new))
}
@ -693,7 +698,7 @@ impl Object {
};
// 4. Let status be ? O.[[SetPrototypeOf]](proto).
let status = obj.__set_prototype_of__(proto, context)?;
let status = obj.__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?;
// 5. If status is false, throw a TypeError exception.
if !status {
@ -931,9 +936,10 @@ impl Object {
};
let key = key.to_property_key(context)?;
let own_prop = this
.to_object(context)?
.__get_own_property__(&key, context)?;
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
own_prop
.as_ref()
@ -972,11 +978,14 @@ impl Object {
.to_object(context)
.expect("this ToObject call must not fail");
// 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]]().
let keys = from.__own_property_keys__(context)?;
let keys = from.__own_property_keys__(&mut InternalMethodContext::new(context))?;
// 3.a.iii. For each element nextKey of keys, do
for key in keys {
// 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey).
if let Some(desc) = from.__get_own_property__(&key, context)? {
if let Some(desc) =
from.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
{
// 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then
if desc.expect_enumerable() {
// 3.a.iii.2.a. Let propValue be ? Get(from, nextKey).
@ -1187,7 +1196,7 @@ impl Object {
if let Some(o) = o.as_object() {
// 2. Let status be ? O.[[PreventExtensions]]().
let status = o.__prevent_extensions__(context)?;
let status = o.__prevent_extensions__(&mut InternalMethodContext::new(context))?;
// 3. If status is false, throw a TypeError exception.
if !status {
return Err(JsNativeError::typ()
@ -1444,7 +1453,7 @@ fn object_define_properties(
let props = &props.to_object(context)?;
// 3. Let keys be ? props.[[OwnPropertyKeys]]().
let keys = props.__own_property_keys__(context)?;
let keys = props.__own_property_keys__(&mut InternalMethodContext::new(context))?;
// 4. Let descriptors be a new empty List.
let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new();
@ -1453,7 +1462,10 @@ fn object_define_properties(
for next_key in keys {
// a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
// b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
if let Some(prop_desc) = props.__get_own_property__(&next_key, context)? {
if let Some(prop_desc) =
props.__get_own_property__(&next_key, &mut InternalMethodContext::new(context))?
{
if prop_desc.expect_enumerable() {
// i. Let descObj be ? Get(props, nextKey).
let desc_obj = props.get(next_key.clone(), context)?;
@ -1501,7 +1513,7 @@ fn get_own_property_keys(
let obj = o.to_object(context)?;
// 2. Let keys be ? obj.[[OwnPropertyKeys]]().
let keys = obj.__own_property_keys__(context)?;
let keys = obj.__own_property_keys__(&mut InternalMethodContext::new(context))?;
// 3. Let nameList be a new empty List.
// 4. For each element nextKey of keys, do

44
boa_engine/src/builtins/reflect/mod.rs

@ -16,7 +16,7 @@ use crate::{
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::JsObject,
object::{internal_methods::InternalMethodContext, JsObject},
property::Attribute,
realm::Realm,
string::common::StaticJsStrings,
@ -166,7 +166,11 @@ impl Reflect {
.into();
target
.__define_own_property__(&key, prop_desc.to_property_descriptor(context)?, context)
.__define_own_property__(
&key,
prop_desc.to_property_descriptor(context)?,
&mut InternalMethodContext::new(context),
)
.map(Into::into)
}
@ -189,7 +193,9 @@ impl Reflect {
.ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
let key = args.get_or_undefined(1).to_property_key(context)?;
Ok(target.__delete__(&key, context)?.into())
Ok(target
.__delete__(&key, &mut InternalMethodContext::new(context))?
.into())
}
/// Gets a property of an object.
@ -215,7 +221,8 @@ impl Reflect {
.cloned()
.unwrap_or_else(|| target.clone().into());
// 4. Return ? target.[[Get]](key, receiver).
target.__get__(&key, receiver, context)
target.__get__(&key, receiver, &mut InternalMethodContext::new(context))
}
/// Gets a property of an object.
@ -264,7 +271,7 @@ impl Reflect {
.and_then(JsValue::as_object)
.ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
Ok(target
.__get_prototype_of__(context)?
.__get_prototype_of__(&mut InternalMethodContext::new(context))?
.map_or(JsValue::Null, JsValue::new))
}
@ -285,7 +292,10 @@ impl Reflect {
.get(1)
.unwrap_or(&JsValue::undefined())
.to_property_key(context)?;
Ok(target.__has_property__(&key, context)?.into())
Ok(target
.__has_property__(&key, &mut InternalMethodContext::new(context))?
.into())
}
/// Returns `true` if the object is extensible, `false` otherwise.
@ -305,7 +315,9 @@ impl Reflect {
.get(0)
.and_then(JsValue::as_object)
.ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
Ok(target.__is_extensible__(context)?.into())
Ok(target
.__is_extensible__(&mut InternalMethodContext::new(context))?
.into())
}
/// Returns an array of object own property keys.
@ -327,7 +339,7 @@ impl Reflect {
.ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
let keys: Vec<JsValue> = target
.__own_property_keys__(context)?
.__own_property_keys__(&mut InternalMethodContext::new(context))?
.into_iter()
.map(Into::into)
.collect();
@ -353,7 +365,9 @@ impl Reflect {
.and_then(JsValue::as_object)
.ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
Ok(target.__prevent_extensions__(context)?.into())
Ok(target
.__prevent_extensions__(&mut InternalMethodContext::new(context))?
.into())
}
/// Sets a property of an object.
@ -375,8 +389,14 @@ impl Reflect {
.get(3)
.cloned()
.unwrap_or_else(|| target.clone().into());
Ok(target
.__set__(key, value.clone(), receiver, context)?
.__set__(
key,
value.clone(),
receiver,
&mut InternalMethodContext::new(context),
)?
.into())
}
@ -406,6 +426,8 @@ impl Reflect {
.into())
}
};
Ok(target.__set_prototype_of__(proto, context)?.into())
Ok(target
.__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?
.into())
}
}

2
boa_engine/src/builtins/temporal/calendar/mod.rs

@ -1181,7 +1181,7 @@ pub(crate) fn to_temporal_calendar_slot_value(
fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut Context) -> bool {
CALENDAR_PROTOCOL_METHODS.into_iter().all(|method| {
calendar_like
.__has_property__(&JsString::from(method).into(), context)
.__has_property__(&JsString::from(method).into(), &mut context.into())
.unwrap_or(false)
})
}

2
boa_engine/src/builtins/temporal/calendar/object.rs

@ -847,7 +847,7 @@ impl CalendarProtocol for CustomRuntimeCalendar {
.__get__(
&PropertyKey::from(utf16!("id")),
JsValue::undefined(),
context,
&mut context.into(),
)
.expect("method must exist on a object that implements the CalendarProtocol.");

2
boa_engine/src/builtins/temporal/mod.rs

@ -597,7 +597,7 @@ pub(crate) fn copy_data_properties(
// c. If excluded is false, then
if !excluded {
// i. Let desc be ? from.[[GetOwnProperty]](nextKey).
let desc = from.__get_own_property__(&next_key, context)?;
let desc = from.__get_own_property__(&next_key, &mut context.into())?;
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
match desc {
Some(d)

2
boa_engine/src/builtins/typed_array/builtin.rs

@ -2262,7 +2262,7 @@ impl BuiltinTypedArray {
let target_index = target_offset + k;
// d. Perform ? IntegerIndexedElementSet(target, targetIndex, value).
integer_indexed_element_set(target, target_index as f64, &value, context)?;
integer_indexed_element_set(target, target_index as f64, &value, &mut context.into())?;
// e. Set k to k + 1.
}

18
boa_engine/src/bytecompiler/declaration/declaration_pattern.rs

@ -39,11 +39,7 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit_with_varying_operand(
Opcode::GetPropertyByName,
index,
);
self.emit_get_property_by_name(*name);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true);
@ -123,11 +119,7 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit_with_varying_operand(
Opcode::GetPropertyByName,
index,
);
self.emit_get_property_by_name(*name);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true);
@ -166,11 +158,7 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit_with_varying_operand(
Opcode::GetPropertyByName,
index,
);
self.emit_get_property_by_name(*name);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true);

10
boa_engine/src/bytecompiler/expression/assign.rs

@ -99,13 +99,12 @@ impl ByteCompiler<'_> {
Access::Property { access } => match access {
PropertyAccess::Simple(access) => match access.field() {
PropertyAccessField::Const(name) => {
let index = self.get_or_insert_name((*name).into());
self.compile_expr(access.target(), true);
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::Dup);
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*name);
if short_circuit {
pop_count = 2;
early_exit = Some(self.emit_opcode_with_operand(opcode));
@ -115,7 +114,7 @@ impl ByteCompiler<'_> {
self.emit_opcode(opcode);
}
self.emit_with_varying_operand(Opcode::SetPropertyByName, index);
self.emit_set_property_by_name(*name);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
@ -165,14 +164,13 @@ impl ByteCompiler<'_> {
}
PropertyAccess::Super(access) => match access.field() {
PropertyAccessField::Const(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit_opcode(Opcode::Super);
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::This);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::This);
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*name);
if short_circuit {
pop_count = 2;
early_exit = Some(self.emit_opcode_with_operand(opcode));
@ -182,7 +180,7 @@ impl ByteCompiler<'_> {
self.emit_opcode(opcode);
}
self.emit_with_varying_operand(Opcode::SetPropertyByName, index);
self.emit_set_property_by_name(*name);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}

3
boa_engine/src/bytecompiler/expression/mod.rs

@ -239,8 +239,7 @@ impl ByteCompiler<'_> {
self.emit_opcode(Opcode::Dup);
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*field);
}
PropertyAccessField::Expr(field) => {
self.compile_expr(field, true);

10
boa_engine/src/bytecompiler/expression/update.rs

@ -63,19 +63,18 @@ impl ByteCompiler<'_> {
Access::Property { access } => match access {
PropertyAccess::Simple(access) => match access.field() {
PropertyAccessField::Const(name) => {
let index = self.get_or_insert_name((*name).into());
self.compile_expr(access.target(), true);
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::Dup);
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*name);
self.emit_opcode(opcode);
if post {
self.emit(Opcode::RotateRight, &[Operand::U8(4)]);
}
self.emit_with_varying_operand(Opcode::SetPropertyByName, index);
self.emit_set_property_by_name(*name);
if post {
self.emit_opcode(Opcode::Pop);
}
@ -117,20 +116,19 @@ impl ByteCompiler<'_> {
}
PropertyAccess::Super(access) => match access.field() {
PropertyAccessField::Const(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit_opcode(Opcode::Super);
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::This);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::This);
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*name);
self.emit_opcode(opcode);
if post {
self.emit(Opcode::RotateRight, &[Operand::U8(3)]);
}
self.emit_with_varying_operand(Opcode::SetPropertyByName, index);
self.emit_set_property_by_name(*name);
if post {
self.emit_opcode(Opcode::Pop);
}

54
boa_engine/src/bytecompiler/mod.rs

@ -18,8 +18,8 @@ use crate::{
environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment},
js_string,
vm::{
BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler, Opcode,
VaryingOperandKind,
BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler,
InlineCache, Opcode, VaryingOperandKind,
},
Context, JsBigInt, JsString,
};
@ -279,6 +279,7 @@ pub struct ByteCompiler<'ctx> {
current_stack_value_count: u32,
pub(crate) code_block_flags: CodeBlockFlags,
handlers: ThinVec<Handler>,
pub(crate) ic: Vec<InlineCache>,
literals_map: FxHashMap<Literal, u32>,
names_map: FxHashMap<Identifier, u32>,
bindings_map: FxHashMap<BindingLocator, u32>,
@ -330,6 +331,7 @@ impl<'ctx> ByteCompiler<'ctx> {
current_stack_value_count: 2,
code_block_flags,
handlers: ThinVec::default(),
ic: Vec::default(),
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
@ -540,6 +542,29 @@ impl<'ctx> ByteCompiler<'ctx> {
fn emit_i64(&mut self, value: i64) {
self.emit_u64(value as u64);
}
fn emit_get_property_by_name(&mut self, ident: Sym) {
let ic_index = self.ic.len() as u32;
let name_index = self.get_or_insert_name(Identifier::new(ident));
let Constant::String(ref name) = self.constants[name_index as usize].clone() else {
unreachable!("there should be a string at index")
};
self.ic.push(InlineCache::new(name.clone()));
self.emit_with_varying_operand(Opcode::GetPropertyByName, ic_index);
}
fn emit_set_property_by_name(&mut self, ident: Sym) {
let ic_index = self.ic.len() as u32;
let name_index = self.get_or_insert_name(Identifier::new(ident));
let Constant::String(ref name) = self.constants[name_index as usize].clone() else {
unreachable!("there should be a string at index")
};
self.ic.push(InlineCache::new(name.clone()));
self.emit_with_varying_operand(Opcode::SetPropertyByName, ic_index);
}
fn emit_u64(&mut self, value: u64) {
self.bytecode.extend(value.to_ne_bytes());
@ -698,7 +723,7 @@ impl<'ctx> ByteCompiler<'ctx> {
// This is done to avoid unneeded bounds checks.
assert!(self.bytecode.len() > index + U32_SIZE && usize::MAX - U32_SIZE >= index);
self.bytecode[index + 1..=index + U32_SIZE].copy_from_slice(bytes.as_slice());
self.bytecode[index + 1..=index + U32_SIZE].clone_from_slice(bytes.as_slice());
}
fn patch_jump(&mut self, label: Label) {
@ -721,10 +746,9 @@ impl<'ctx> ByteCompiler<'ctx> {
Access::Property { access } => match access {
PropertyAccess::Simple(access) => match access.field() {
PropertyAccessField::Const(name) => {
let index = self.get_or_insert_name((*name).into());
self.compile_expr(access.target(), true);
self.emit_opcode(Opcode::Dup);
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*name);
}
PropertyAccessField::Expr(expr) => {
self.compile_expr(access.target(), true);
@ -740,10 +764,10 @@ impl<'ctx> ByteCompiler<'ctx> {
}
PropertyAccess::Super(access) => match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit_opcode(Opcode::Super);
self.emit_opcode(Opcode::This);
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*field);
}
PropertyAccessField::Expr(expr) => {
self.emit_opcode(Opcode::Super);
@ -818,9 +842,8 @@ impl<'ctx> ByteCompiler<'ctx> {
self.compile_expr(access.target(), true);
self.emit_opcode(Opcode::Dup);
expr_fn(self, 2);
let index = self.get_or_insert_name((*name).into());
self.emit_with_varying_operand(Opcode::SetPropertyByName, index);
self.emit_set_property_by_name(*name);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
@ -850,8 +873,7 @@ impl<'ctx> ByteCompiler<'ctx> {
self.emit_opcode(Opcode::Super);
self.emit_opcode(Opcode::This);
expr_fn(self, 1);
let index = self.get_or_insert_name((*name).into());
self.emit_with_varying_operand(Opcode::SetPropertyByName, index);
self.emit_set_property_by_name(*name);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
@ -958,8 +980,7 @@ impl<'ctx> ByteCompiler<'ctx> {
self.emit_opcode(Opcode::Dup);
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*field);
}
PropertyAccessField::Expr(field) => {
self.compile_expr(field, true);
@ -979,8 +1000,7 @@ impl<'ctx> ByteCompiler<'ctx> {
self.emit_opcode(Opcode::This);
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*field);
}
PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true);
@ -1065,8 +1085,7 @@ impl<'ctx> ByteCompiler<'ctx> {
self.emit_opcode(Opcode::Dup);
match field {
PropertyAccessField::Const(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit_with_varying_operand(Opcode::GetPropertyByName, index);
self.emit_get_property_by_name(*name);
}
PropertyAccessField::Expr(expr) => {
self.compile_expr(expr, true);
@ -1512,6 +1531,7 @@ impl<'ctx> ByteCompiler<'ctx> {
bindings: self.bindings.into_boxed_slice(),
handlers: self.handlers,
flags: Cell::new(self.code_block_flags),
ic: self.ic.into_boxed_slice(),
}
}

7
boa_engine/src/context/mod.rs

@ -616,7 +616,7 @@ impl Context {
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
let name = name.clone().into();
let existing_prop = global_object.__get_own_property__(&name, self)?;
let existing_prop = global_object.__get_own_property__(&name, &mut self.into())?;
// 4. If existingProp is undefined, return ? IsExtensible(globalObject).
let Some(existing_prop) = existing_prop else {
@ -723,7 +723,8 @@ impl Context {
let global_object = self.realm().global_object().clone();
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
let existing_prop = global_object.__get_own_property__(&name.clone().into(), self)?;
let existing_prop =
global_object.__get_own_property__(&name.clone().into(), &mut self.into())?;
// 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then
let desc = if existing_prop.is_none()
@ -770,7 +771,7 @@ impl Context {
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
let name = name.clone().into();
let existing_prop = global_object.__get_own_property__(&name, self)?;
let existing_prop = global_object.__get_own_property__(&name, &mut self.into())?;
// 4. If existingProp is undefined, return false.
let Some(existing_prop) = existing_prop else {

7
boa_engine/src/environments/runtime/mod.rs

@ -2,7 +2,7 @@ use std::rc::Rc;
use crate::{
environments::CompileTimeEnvironment,
object::{JsObject, PrivateName},
object::{internal_methods::InternalMethodContext, JsObject, PrivateName},
Context, JsResult, JsString, JsSymbol, JsValue,
};
use boa_gc::{empty_trace, Finalize, Gc, Trace};
@ -630,7 +630,8 @@ impl Context {
pub(crate) fn delete_binding(&mut self, locator: &BindingLocator) -> JsResult<bool> {
if locator.is_global() {
let key = locator.name().clone();
self.global_object().__delete__(&key.into(), self)
self.global_object()
.__delete__(&key.into(), &mut self.into())
} else {
match self.environment_expect(locator.environment_index) {
Environment::Declarative(_) => Ok(false),
@ -638,7 +639,7 @@ impl Context {
let obj = obj.clone();
let key = locator.name().clone();
obj.__delete__(&key.into(), self)
obj.__delete__(&key.into(), &mut InternalMethodContext::new(self))
}
}
}

14
boa_engine/src/object/internal_methods/arguments.rs

@ -1,10 +1,10 @@
use crate::{
object::JsObject,
property::{DescriptorKind, PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue,
JsResult, JsValue,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
pub(crate) static ARGUMENTS_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
InternalObjectMethods {
@ -25,7 +25,7 @@ pub(crate) static ARGUMENTS_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
pub(crate) fn arguments_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
// 1. Let desc be OrdinaryGetOwnProperty(args, P).
// 2. If desc is undefined, return desc.
@ -70,7 +70,7 @@ pub(crate) fn arguments_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 2. Let isMapped be HasOwnProperty(map, P).
let mapped = if let &PropertyKey::Index(index) = &key {
@ -161,7 +161,7 @@ pub(crate) fn arguments_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
if let PropertyKey::Index(index) = key {
// 1. Let map be args.[[ParameterMap]].
@ -194,7 +194,7 @@ pub(crate) fn arguments_exotic_set(
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. If SameValue(args, Receiver) is false, then
// a. Let isMapped be false.
@ -226,7 +226,7 @@ pub(crate) fn arguments_exotic_set(
pub(crate) fn arguments_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 3. Let result be ? OrdinaryDelete(args, P).
let result = super::ordinary_delete(obj, key, context)?;

8
boa_engine/src/object/internal_methods/array.rs

@ -3,10 +3,10 @@ use crate::{
object::JsObject,
property::{PropertyDescriptor, PropertyKey},
string::utf16,
Context, JsResult,
JsResult,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for array exotic objects.
///
@ -29,7 +29,7 @@ pub(crate) fn array_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
match key {
@ -111,7 +111,7 @@ pub(crate) fn array_exotic_define_own_property(
fn array_set_length(
obj: &JsObject,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. If Desc.[[Value]] is absent, then
let Some(new_len_val) = desc.value() else {

16
boa_engine/src/object/internal_methods/integer_indexed.rs

@ -9,7 +9,7 @@ use crate::{
Context, JsResult, JsString, JsValue,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for integer-indexed exotic objects.
///
@ -62,7 +62,7 @@ fn canonical_numeric_index_string(argument: &JsString) -> Option<f64> {
pub(crate) fn integer_indexed_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let p = match key {
PropertyKey::String(key) => {
@ -104,7 +104,7 @@ pub(crate) fn integer_indexed_exotic_get_own_property(
pub(crate) fn integer_indexed_exotic_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let p = match key {
PropertyKey::String(key) => {
@ -135,7 +135,7 @@ pub(crate) fn integer_indexed_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let p = match key {
PropertyKey::String(key) => {
@ -197,7 +197,7 @@ pub(crate) fn integer_indexed_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
let p = match key {
PropertyKey::String(key) => {
@ -230,7 +230,7 @@ pub(crate) fn integer_indexed_exotic_set(
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let p = match &key {
PropertyKey::String(key) => {
@ -272,7 +272,7 @@ pub(crate) fn integer_indexed_exotic_set(
pub(crate) fn integer_indexed_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let p = match &key {
PropertyKey::String(key) => {
@ -388,7 +388,7 @@ pub(crate) fn integer_indexed_element_set(
obj: &JsObject,
index: f64,
value: &JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<()> {
let obj_borrow = obj.borrow();
let inner = obj_borrow.as_typed_array().expect(

198
boa_engine/src/object/internal_methods/mod.rs

@ -5,7 +5,12 @@
//!
//! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
use super::{JsPrototype, PROTOTYPE};
use std::ops::{Deref, DerefMut};
use super::{
shape::slot::{Slot, SlotAttributes},
JsPrototype, PROTOTYPE,
};
use crate::{
context::intrinsics::{StandardConstructor, StandardConstructors},
object::JsObject,
@ -28,6 +33,52 @@ pub(super) mod string;
pub(crate) use array::ARRAY_EXOTIC_INTERNAL_METHODS;
pub(crate) use integer_indexed::integer_indexed_element_set;
/// A lightweight wrapper around [`Context`] used in [`InternalObjectMethods`].
#[derive(Debug)]
pub(crate) struct InternalMethodContext<'ctx> {
context: &'ctx mut Context,
slot: Slot,
}
impl<'ctx> InternalMethodContext<'ctx> {
/// Create a new [`InternalMethodContext`].
pub(crate) fn new(context: &'ctx mut Context) -> Self {
Self {
context,
slot: Slot::new(),
}
}
/// Gets the [`Slot`] associated with this [`InternalMethodContext`].
#[inline]
pub(crate) fn slot(&mut self) -> &mut Slot {
&mut self.slot
}
}
impl Deref for InternalMethodContext<'_> {
type Target = Context;
#[inline]
fn deref(&self) -> &Self::Target {
self.context
}
}
impl DerefMut for InternalMethodContext<'_> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
self.context
}
}
impl<'context> From<&'context mut Context> for InternalMethodContext<'context> {
#[inline]
fn from(context: &'context mut Context) -> Self {
Self::new(context)
}
}
impl JsObject {
/// Internal method `[[GetPrototypeOf]]`
///
@ -97,7 +148,7 @@ impl JsObject {
pub(crate) fn __get_own_property__(
&self,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let _timer = Profiler::global().start_event("Object::__get_own_property__", "object");
(self.vtable().__get_own_property__)(self, key, context)
@ -115,7 +166,7 @@ impl JsObject {
&self,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__define_own_property__", "object");
(self.vtable().__define_own_property__)(self, key, desc, context)
@ -132,7 +183,7 @@ impl JsObject {
pub(crate) fn __has_property__(
&self,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__has_property__", "object");
(self.vtable().__has_property__)(self, key, context)
@ -150,7 +201,7 @@ impl JsObject {
&self,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__get__", "object");
(self.vtable().__get__)(self, key, receiver, context)
@ -169,7 +220,7 @@ impl JsObject {
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__set__", "object");
(self.vtable().__set__)(self, key, value, receiver, context)
@ -183,7 +234,11 @@ impl JsObject {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p
pub(crate) fn __delete__(&self, key: &PropertyKey, context: &mut Context) -> JsResult<bool> {
pub(crate) fn __delete__(
&self,
key: &PropertyKey,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::__delete__", "object");
(self.vtable().__delete__)(self, key, context)
}
@ -215,6 +270,7 @@ impl JsObject {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
#[track_caller]
pub(crate) fn __call__(&self, argument_count: usize) -> CallValue {
CallValue::Pending {
func: self.vtable().__call__,
@ -233,6 +289,7 @@ impl JsObject {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
#[track_caller]
pub(crate) fn __construct__(&self, argument_count: usize) -> CallValue {
CallValue::Pending {
func: self.vtable().__construct__,
@ -283,19 +340,36 @@ pub(crate) struct InternalObjectMethods {
pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &mut Context) -> JsResult<bool>,
pub(crate) __is_extensible__: fn(&JsObject, &mut Context) -> JsResult<bool>,
pub(crate) __prevent_extensions__: fn(&JsObject, &mut Context) -> JsResult<bool>,
pub(crate) __get_own_property__:
fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<Option<PropertyDescriptor>>,
pub(crate) __define_own_property__:
fn(&JsObject, &PropertyKey, PropertyDescriptor, &mut Context) -> JsResult<bool>,
pub(crate) __has_property__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>,
pub(crate) __get__: fn(&JsObject, &PropertyKey, JsValue, &mut Context) -> JsResult<JsValue>,
pub(crate) __set__:
fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context) -> JsResult<bool>,
pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult<bool>,
pub(crate) __own_property_keys__: fn(&JsObject, &mut Context) -> JsResult<Vec<PropertyKey>>,
pub(crate) __call__: fn(&JsObject, argument_count: usize, &mut Context) -> JsResult<CallValue>,
pub(crate) __get_own_property__: fn(
&JsObject,
&PropertyKey,
&mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>>,
pub(crate) __define_own_property__: fn(
&JsObject,
&PropertyKey,
PropertyDescriptor,
&mut InternalMethodContext<'_>,
) -> JsResult<bool>,
pub(crate) __has_property__:
fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_>) -> JsResult<bool>,
pub(crate) __get__:
fn(&JsObject, &PropertyKey, JsValue, &mut InternalMethodContext<'_>) -> JsResult<JsValue>,
pub(crate) __set__: fn(
&JsObject,
PropertyKey,
JsValue,
JsValue,
&mut InternalMethodContext<'_>,
) -> JsResult<bool>,
pub(crate) __delete__:
fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_>) -> JsResult<bool>,
pub(crate) __own_property_keys__:
fn(&JsObject, context: &mut Context) -> JsResult<Vec<PropertyKey>>,
pub(crate) __call__:
fn(&JsObject, argument_count: usize, context: &mut Context) -> JsResult<CallValue>,
pub(crate) __construct__:
fn(&JsObject, argument_count: usize, &mut Context) -> JsResult<CallValue>,
fn(&JsObject, argument_count: usize, context: &mut Context) -> JsResult<CallValue>,
}
/// The return value of an internal method (`[[Call]]` or `[[Construct]]`).
@ -447,7 +521,7 @@ pub(crate) fn ordinary_prevent_extensions(
pub(crate) fn ordinary_get_own_property(
obj: &JsObject,
key: &PropertyKey,
_context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
let _timer = Profiler::global().start_event("Object::ordinary_get_own_property", "object");
// 1. Assert: IsPropertyKey(P) is true.
@ -464,7 +538,7 @@ pub(crate) fn ordinary_get_own_property(
// 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
// 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
// 9. Return D.
Ok(obj.borrow().properties.get(key))
Ok(obj.borrow().properties.get_with_slot(key, context.slot()))
}
/// Abstract operation `OrdinaryDefineOwnProperty`.
@ -477,9 +551,10 @@ pub(crate) fn ordinary_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_define_own_property", "object");
// 1. Let current be ? O.[[GetOwnProperty]](P).
let current = obj.__get_own_property__(key, context)?;
@ -492,6 +567,7 @@ pub(crate) fn ordinary_define_own_property(
extensible,
desc,
current,
context.slot(),
))
}
@ -504,18 +580,23 @@ pub(crate) fn ordinary_define_own_property(
pub(crate) fn ordinary_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_has_property", "object");
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let hasOwn be ? O.[[GetOwnProperty]](P).
// 3. If hasOwn is not undefined, return true.
if obj.__get_own_property__(key, context)?.is_some() {
context.slot().attributes |= SlotAttributes::FOUND;
Ok(true)
} else {
// 4. Let parent be ? O.[[GetPrototypeOf]]().
let parent = obj.__get_prototype_of__(context)?;
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
parent
// 5. If parent is not null, then
// a. Return ? parent.[[HasProperty]](P).
@ -534,7 +615,7 @@ pub(crate) fn ordinary_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::ordinary_get", "object");
// 1. Assert: IsPropertyKey(P) is true.
@ -544,6 +625,9 @@ pub(crate) fn ordinary_get(
None => {
// a. Let parent be ? O.[[GetPrototypeOf]]().
if let Some(parent) = obj.__get_prototype_of__(context)? {
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
// c. Return ? parent.[[Get]](P, Receiver).
parent.__get__(key, receiver, context)
}
@ -552,20 +636,24 @@ pub(crate) fn ordinary_get(
Ok(JsValue::undefined())
}
}
Some(ref desc) => match desc.kind() {
// 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
DescriptorKind::Data {
value: Some(value), ..
} => Ok(value.clone()),
// 5. Assert: IsAccessorDescriptor(desc) is true.
// 6. Let getter be desc.[[Get]].
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
// 8. Return ? Call(getter, Receiver).
get.call(&receiver, &[], context)
Some(ref desc) => {
context.slot().attributes |= SlotAttributes::FOUND;
match desc.kind() {
// 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
DescriptorKind::Data {
value: Some(value), ..
} => Ok(value.clone()),
// 5. Assert: IsAccessorDescriptor(desc) is true.
// 6. Let getter be desc.[[Get]].
DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
// 8. Return ? Call(getter, Receiver).
get.call(&receiver, &[], context)
}
// 7. If getter is undefined, return undefined.
_ => Ok(JsValue::undefined()),
}
// 7. If getter is undefined, return undefined.
_ => Ok(JsValue::undefined()),
},
}
}
}
@ -580,7 +668,7 @@ pub(crate) fn ordinary_set(
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_set", "object");
@ -599,11 +687,20 @@ pub(crate) fn ordinary_set(
// a. Let parent be ? O.[[GetPrototypeOf]]().
// b. If parent is not null, then
else if let Some(parent) = obj.__get_prototype_of__(context)? {
context.slot().set_not_cachable_if_already_prototype();
context.slot().attributes |= SlotAttributes::PROTOTYPE;
// i. Return ? parent.[[Set]](P, V, Receiver).
return parent.__set__(key, value, receiver, context);
}
// c. Else,
else {
// It's not on prototype chain.
context
.slot()
.attributes
.remove(SlotAttributes::PROTOTYPE | SlotAttributes::NOT_CACHABLE);
// i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true,
// [[Enumerable]]: true, [[Configurable]]: true }.
PropertyDescriptor::builder()
@ -626,6 +723,12 @@ pub(crate) fn ordinary_set(
return Ok(false);
};
// NOTE(HaledOdat): If the object and receiver are not the same then it's not inline cachable for now.
context.slot().attributes.set(
SlotAttributes::NOT_CACHABLE,
!JsObject::equals(obj, receiver),
);
// c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
// d. If existingDescriptor is not undefined, then
if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? {
@ -647,12 +750,15 @@ pub(crate) fn ordinary_set(
context,
);
}
// e. Else
// i. Assert: Receiver does not currently have a property P.
// ii. Return ? CreateDataProperty(Receiver, P, V).
return receiver.create_data_property(key, value, context);
return receiver.create_data_property_with_slot(key, value, context);
}
context.slot().attributes |= SlotAttributes::FOUND;
// 4. Assert: IsAccessorDescriptor(ownDesc) is true.
debug_assert!(own_desc.is_accessor_descriptor());
@ -679,7 +785,7 @@ pub(crate) fn ordinary_set(
pub(crate) fn ordinary_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("Object::ordinary_delete", "object");
// 1. Assert: IsPropertyKey(P) is true.
@ -752,7 +858,7 @@ pub(crate) fn is_compatible_property_descriptor(
Profiler::global().start_event("Object::is_compatible_property_descriptor", "object");
// 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current).
validate_and_apply_property_descriptor(None, extensible, desc, current)
validate_and_apply_property_descriptor(None, extensible, desc, current, &mut Slot::new())
}
/// Abstract operation `ValidateAndApplyPropertyDescriptor`
@ -766,6 +872,7 @@ pub(crate) fn validate_and_apply_property_descriptor(
extensible: bool,
desc: PropertyDescriptor,
current: Option<PropertyDescriptor>,
slot: &mut Slot,
) -> bool {
let _timer =
Profiler::global().start_event("Object::validate_and_apply_property_descriptor", "object");
@ -781,7 +888,7 @@ pub(crate) fn validate_and_apply_property_descriptor(
// b. Assert: extensible is true.
if let Some((obj, key)) = obj_and_key {
obj.borrow_mut().properties.insert(
obj.borrow_mut().properties.insert_with_slot(
key,
// c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
if desc.is_generic_descriptor() || desc.is_data_descriptor() {
@ -803,7 +910,9 @@ pub(crate) fn validate_and_apply_property_descriptor(
// its default value.
desc.into_accessor_defaulted()
},
slot,
);
slot.attributes |= SlotAttributes::FOUND;
}
// e. Return true.
@ -899,7 +1008,10 @@ pub(crate) fn validate_and_apply_property_descriptor(
// a. For each field of Desc that is present, set the corresponding attribute of the
// property named P of object O to the value of the field.
current.fill_with(&desc);
obj.borrow_mut().properties.insert(key, current);
obj.borrow_mut()
.properties
.insert_with_slot(key, current, slot);
slot.attributes |= SlotAttributes::FOUND;
}
// 10. Return true.

14
boa_engine/src/object/internal_methods/module_namespace.rs

@ -11,7 +11,7 @@ use crate::{
use super::{
immutable_prototype, ordinary_define_own_property, ordinary_delete, ordinary_get,
ordinary_get_own_property, ordinary_has_property, ordinary_own_property_keys,
InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
};
/// Definitions of the internal object methods for [**Module Namespace Exotic Objects**][spec].
@ -84,7 +84,7 @@ fn module_namespace_exotic_prevent_extensions(_: &JsObject, _: &mut Context) ->
fn module_namespace_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
// 1. If P is a Symbol, return OrdinaryGetOwnProperty(O, P).
let key = match key {
@ -128,7 +128,7 @@ fn module_namespace_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. If P is a Symbol, return ! OrdinaryDefineOwnProperty(O, P, Desc).
if let PropertyKey::Symbol(_) = key {
@ -164,7 +164,7 @@ fn module_namespace_exotic_define_own_property(
fn module_namespace_exotic_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. If P is a Symbol, return ! OrdinaryHasProperty(O, P).
let key = match key {
@ -193,7 +193,7 @@ fn module_namespace_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
// 1. If P is a Symbol, then
// a. Return ! OrdinaryGet(O, P, Receiver).
@ -273,7 +273,7 @@ fn module_namespace_exotic_set(
_key: PropertyKey,
_value: JsValue,
_receiver: JsValue,
_context: &mut Context,
_context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. Return false.
Ok(false)
@ -285,7 +285,7 @@ fn module_namespace_exotic_set(
fn module_namespace_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. If P is a Symbol, then
// a. Return ! OrdinaryDelete(O, P).

30
boa_engine/src/object/internal_methods/proxy.rs

@ -1,7 +1,7 @@
use crate::{
builtins::{array, object::Object},
error::JsNativeError,
object::{InternalObjectMethods, JsObject, JsPrototype},
object::{shape::slot::SlotAttributes, InternalObjectMethods, JsObject, JsPrototype},
property::{PropertyDescriptor, PropertyKey},
string::utf16,
value::Type,
@ -9,7 +9,7 @@ use crate::{
};
use rustc_hash::FxHashSet;
use super::{CallValue, ORDINARY_INTERNAL_METHODS};
use super::{CallValue, InternalMethodContext, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for array exotic objects.
///
@ -268,8 +268,10 @@ pub(crate) fn proxy_exotic_prevent_extensions(
pub(crate) fn proxy_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
@ -284,6 +286,7 @@ pub(crate) fn proxy_exotic_get_own_property(
let Some(trap) = handler.get_method(utf16!("getOwnPropertyDescriptor"), context)? else {
// 6. If trap is undefined, then
// a. Return ? target.[[GetOwnProperty]](P).
return target.__get_own_property__(key, context);
};
@ -392,8 +395,10 @@ pub(crate) fn proxy_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
@ -503,8 +508,10 @@ pub(crate) fn proxy_exotic_define_own_property(
pub(crate) fn proxy_exotic_has_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
@ -569,8 +576,11 @@ pub(crate) fn proxy_exotic_get(
obj: &JsObject,
key: &PropertyKey,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<JsValue> {
// Proxy object can't be cached.
context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
@ -638,8 +648,10 @@ pub(crate) fn proxy_exotic_set(
key: PropertyKey,
value: JsValue,
receiver: JsValue,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
@ -719,7 +731,7 @@ pub(crate) fn proxy_exotic_set(
pub(crate) fn proxy_exotic_delete(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
@ -852,7 +864,7 @@ pub(crate) fn proxy_exotic_own_property_keys(
// 16. For each element key of targetKeys, do
for key in target_keys {
// a. Let desc be ? target.[[GetOwnProperty]](key).
match target.__get_own_property__(&key, context)? {
match target.__get_own_property__(&key, &mut context.into())? {
// b. If desc is not undefined and desc.[[Configurable]] is false, then
Some(desc) if !desc.expect_configurable() => {
// i. Append key as an element of targetNonconfigurableKeys.

6
boa_engine/src/object/internal_methods/string.rs

@ -5,7 +5,7 @@ use crate::{
Context, JsResult,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for string exotic objects.
///
@ -29,7 +29,7 @@ pub(crate) static STRING_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = Intern
pub(crate) fn string_exotic_get_own_property(
obj: &JsObject,
key: &PropertyKey,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<Option<PropertyDescriptor>> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let desc be OrdinaryGetOwnProperty(S, P).
@ -54,7 +54,7 @@ pub(crate) fn string_exotic_define_own_property(
obj: &JsObject,
key: &PropertyKey,
desc: PropertyDescriptor,
context: &mut Context,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool> {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let stringDesc be ! StringGetOwnProperty(S, P).

4
boa_engine/src/object/jsobject.rs

@ -4,7 +4,7 @@
use super::{
internal_methods::{
non_existant_call, non_existant_construct, InternalObjectMethods,
non_existant_call, non_existant_construct, InternalMethodContext, InternalObjectMethods,
ARRAY_EXOTIC_INTERNAL_METHODS,
},
shape::RootShape,
@ -1000,6 +1000,8 @@ Cannot both specify accessors and a value or writable attribute",
where
K: Into<PropertyKey>,
{
let context = &mut InternalMethodContext::new(context);
// 1. Assert: Type(target) is Object.
// 2. Assert: excludedItems is a List of property keys.
// 3. If source is undefined or null, return target.

99
boa_engine/src/object/operations.rs

@ -10,7 +10,7 @@ use crate::{
Context, JsResult, JsSymbol, JsValue,
};
use super::ObjectKind;
use super::{internal_methods::InternalMethodContext, ObjectKind};
/// Object integrity level.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -73,7 +73,11 @@ impl JsObject {
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Return ? O.[[Get]](P, O).
self.__get__(&key.into(), self.clone().into(), context)
self.__get__(
&key.into(),
self.clone().into(),
&mut InternalMethodContext::new(context),
)
}
/// set property of object or throw if bool flag is passed.
@ -92,7 +96,12 @@ impl JsObject {
// 2. Assert: IsPropertyKey(P) is true.
// 3. Assert: Type(Throw) is Boolean.
// 4. Let success be ? O.[[Set]](P, V, O).
let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?;
let success = self.__set__(
key.clone(),
value.into(),
self.clone().into(),
&mut InternalMethodContext::new(context),
)?;
// 5. If success is false and Throw is true, throw a TypeError exception.
if !success && throw {
return Err(JsNativeError::typ()
@ -115,6 +124,38 @@ impl JsObject {
value: V,
context: &mut Context,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
let new_desc = PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true);
// 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
self.__define_own_property__(
&key.into(),
new_desc.into(),
&mut InternalMethodContext::new(context),
)
}
/// Create data property
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
pub(crate) fn create_data_property_with_slot<K, V>(
&self,
key: K,
value: V,
context: &mut InternalMethodContext<'_>,
) -> JsResult<bool>
where
K: Into<PropertyKey>,
V: Into<JsValue>,
@ -221,7 +262,11 @@ impl JsObject {
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
let success = self.__define_own_property__(&key, desc.into(), context)?;
let success = self.__define_own_property__(
&key,
desc.into(),
&mut InternalMethodContext::new(context),
)?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(JsNativeError::typ()
@ -246,7 +291,7 @@ impl JsObject {
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let success be ? O.[[Delete]](P).
let success = self.__delete__(&key, context)?;
let success = self.__delete__(&key, &mut InternalMethodContext::new(context))?;
// 4. If success is false, throw a TypeError exception.
if !success {
return Err(JsNativeError::typ()
@ -270,7 +315,8 @@ impl JsObject {
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Return ? O.[[HasProperty]](P).
self.__has_property__(&key.into(), context)
self.__has_property__(&key.into(), &mut InternalMethodContext::new(context))
}
/// Check if object has an own property.
@ -287,7 +333,7 @@ impl JsObject {
// 1. Assert: Type(O) is Object.
// 2. Assert: IsPropertyKey(P) is true.
// 3. Let desc be ? O.[[GetOwnProperty]](P).
let desc = self.__get_own_property__(&key, context)?;
let desc = self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
// 4. If desc is undefined, return false.
// 5. Return true.
Ok(desc.is_some())
@ -403,14 +449,14 @@ impl JsObject {
// 2. Assert: level is either sealed or frozen.
// 3. Let status be ? O.[[PreventExtensions]]().
let status = self.__prevent_extensions__(context)?;
let status = self.__prevent_extensions__(&mut InternalMethodContext::new(context))?;
// 4. If status is false, return false.
if !status {
return Ok(false);
}
// 5. Let keys be ? O.[[OwnPropertyKeys]]().
let keys = self.__own_property_keys__(context)?;
let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
match level {
// 6. If level is sealed, then
@ -431,7 +477,8 @@ impl JsObject {
// b. For each element k of keys, do
for k in keys {
// i. Let currentDesc be ? O.[[GetOwnProperty]](k).
let current_desc = self.__get_own_property__(&k, context)?;
let current_desc =
self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
// ii. If currentDesc is not undefined, then
if let Some(current_desc) = current_desc {
// 1. If IsAccessorDescriptor(currentDesc) is true, then
@ -481,12 +528,13 @@ impl JsObject {
// 5. NOTE: If the object is extensible, none of its properties are examined.
// 6. Let keys be ? O.[[OwnPropertyKeys]]().
let keys = self.__own_property_keys__(context)?;
let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
// 7. For each element k of keys, do
for k in keys {
// a. Let currentDesc be ? O.[[GetOwnProperty]](k).
let current_desc = self.__get_own_property__(&k, context)?;
let current_desc =
self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
// b. If currentDesc is not undefined, then
if let Some(current_desc) = current_desc {
// i. If currentDesc.[[Configurable]] is true, return false.
@ -595,7 +643,7 @@ impl JsObject {
) -> JsResult<Vec<JsValue>> {
// 1. Assert: Type(O) is Object.
// 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
let own_keys = self.__own_property_keys__(context)?;
let own_keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
// 3. Let properties be a new empty List.
let mut properties = vec![];
@ -610,7 +658,8 @@ impl JsObject {
if let Some(key_str) = key_str {
// i. Let desc be ? O.[[GetOwnProperty]](key).
let desc = self.__get_own_property__(&key, context)?;
let desc =
self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
// ii. If desc is not undefined and desc.[[Enumerable]] is true, then
if let Some(desc) = desc {
if desc.expect_enumerable() {
@ -661,7 +710,12 @@ impl JsObject {
// 1. Assert: IsPropertyKey(P) is true.
// 2. Let func be ? GetV(V, P).
match &self.__get__(&key.into(), self.clone().into(), context)? {
match &self.__get__(
&key.into(),
self.clone().into(),
&mut InternalMethodContext::new(context),
)? {
// 3. If func is either undefined or null, return undefined.
JsValue::Undefined | JsValue::Null => Ok(None),
// 5. Return func.
@ -1066,7 +1120,11 @@ impl JsObject {
// 1. If argumentsList is not present, set argumentsList to a new empty List.
// 2. Let func be ? GetV(V, P).
let func = self.__get__(&key.into(), this_value.clone(), context)?;
let func = self.__get__(
&key.into(),
this_value.clone(),
&mut InternalMethodContext::new(context),
)?;
// 3. Return ? Call(func, V, argumentsList)
func.call(&this_value, args, context)
@ -1092,7 +1150,12 @@ impl JsValue {
let o = self.to_object(context)?;
// 2. Return ? O.[[Get]](P, V).
o.__get__(&key.into(), self.clone(), context)
o.__get__(
&key.into(),
self.clone(),
&mut InternalMethodContext::new(context),
)
}
/// Abstract operation `GetMethod ( V, P )`
@ -1260,7 +1323,7 @@ impl JsValue {
// 6. Repeat,
loop {
// a. Set O to ? O.[[GetPrototypeOf]]().
object = match object.__get_prototype_of__(context)? {
object = match object.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
Some(obj) => obj,
// b. If O is null, return false.
None => return Ok(false),

40
boa_engine/src/object/property_map.rs

@ -277,6 +277,28 @@ impl PropertyMap {
None
}
/// Get the property with the given key from the [`PropertyMap`].
#[must_use]
pub(crate) fn get_with_slot(
&self,
key: &PropertyKey,
out_slot: &mut Slot,
) -> Option<PropertyDescriptor> {
if let PropertyKey::Index(index) = key {
return self.indexed_properties.get(index.get());
}
if let Some(slot) = self.shape.lookup(key) {
out_slot.index = slot.index;
// Remove all descriptor attributes, but keep inline caching bits.
out_slot.attributes =
(out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | slot.attributes;
return Some(self.get_storage(slot));
}
None
}
/// Get the property with the given key from the [`PropertyMap`].
#[must_use]
pub(crate) fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor {
@ -300,6 +322,17 @@ impl PropertyMap {
/// Insert the given property descriptor with the given key [`PropertyMap`].
pub fn insert(&mut self, key: &PropertyKey, property: PropertyDescriptor) -> bool {
let mut dummy_slot = Slot::new();
self.insert_with_slot(key, property, &mut dummy_slot)
}
/// Insert the given property descriptor with the given key [`PropertyMap`].
pub(crate) fn insert_with_slot(
&mut self,
key: &PropertyKey,
property: PropertyDescriptor,
out_slot: &mut Slot,
) -> bool {
if let PropertyKey::Index(index) = key {
return self.indexed_properties.insert(index.get(), property);
}
@ -346,6 +379,9 @@ impl PropertyMap {
} else {
self.storage[index] = property.expect_value().clone();
}
out_slot.index = slot.index;
out_slot.attributes =
(out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes;
return true;
}
@ -364,6 +400,10 @@ impl PropertyMap {
})
);
out_slot.index = self.storage.len() as u32;
out_slot.attributes =
(out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes;
if attributes.is_accessor_descriptor() {
self.storage.push(
property

38
boa_engine/src/object/shape/mod.rs

@ -16,7 +16,11 @@ use boa_gc::{Finalize, Trace};
use crate::property::PropertyKey;
use self::{shared_shape::TransitionKey, slot::Slot};
use self::{
shared_shape::{TransitionKey, WeakSharedShape},
slot::Slot,
unique_shape::WeakUniqueShape,
};
use super::JsPrototype;
@ -225,3 +229,35 @@ impl From<SharedShape> for Shape {
}
}
}
/// Represents a weak reaference to an object's [`Shape`].
#[derive(Debug, Trace, Finalize, Clone)]
pub(crate) enum WeakShape {
Unique(WeakUniqueShape),
Shared(WeakSharedShape),
None,
}
impl WeakShape {
/// Return location in memory of the [`Shape`].
///
/// Returns `0` if the shape has been freed.
#[inline]
#[must_use]
pub(crate) fn to_addr_usize(&self) -> usize {
match self {
WeakShape::Shared(shape) => shape.to_addr_usize(),
WeakShape::Unique(shape) => shape.to_addr_usize(),
WeakShape::None => 0,
}
}
}
impl From<&Shape> for WeakShape {
fn from(value: &Shape) -> Self {
match &value.inner {
Inner::Shared(shape) => WeakShape::Shared(shape.into()),
Inner::Unique(shape) => WeakShape::Unique(shape.into()),
}
}
}

32
boa_engine/src/object/shape/shared_shape/mod.rs

@ -7,7 +7,7 @@ mod tests;
use std::{collections::hash_map::RandomState, hash::Hash};
use bitflags::bitflags;
use boa_gc::{empty_trace, Finalize, Gc, Trace};
use boa_gc::{empty_trace, Finalize, Gc, Trace, WeakGc};
use indexmap::IndexMap;
use crate::{object::JsPrototype, property::PropertyKey, JsObject};
@ -474,9 +474,37 @@ impl SharedShape {
)
}
/// Return location in memory of the [`UniqueShape`].
/// Return location in memory of the [`SharedShape`].
pub(crate) fn to_addr_usize(&self) -> usize {
let ptr: *const _ = self.inner.as_ref();
ptr as usize
}
}
/// Represents a weak reference to [`SharedShape`].
#[derive(Debug, Trace, Finalize, Clone)]
pub(crate) struct WeakSharedShape {
inner: WeakGc<Inner>,
}
impl WeakSharedShape {
/// Return location in memory of the [`WeakSharedShape`].
///
/// Returns `0` if the inner [`SharedShape`] has been freed.
#[inline]
#[must_use]
pub(crate) fn to_addr_usize(&self) -> usize {
self.inner.upgrade().map_or(0, |inner| {
let ptr: *const _ = inner.as_ref();
ptr as usize
})
}
}
impl From<&SharedShape> for WeakSharedShape {
fn from(value: &SharedShape) -> Self {
WeakSharedShape {
inner: WeakGc::new(&value.inner),
}
}
}

33
boa_engine/src/object/shape/slot.rs

@ -10,6 +10,11 @@ bitflags! {
const CONFIGURABLE = 0b0000_0100;
const GET = 0b0000_1000;
const SET = 0b0001_0000;
const INLINE_CACHE_BITS = 0b1110_0000;
const PROTOTYPE = 0b0010_0000;
const FOUND = 0b0100_0000;
const NOT_CACHABLE = 0b1000_0000;
}
}
@ -36,6 +41,10 @@ impl SlotAttributes {
// accessor take 2 positions in the storage to accomodate for the `get` and `set` fields.
1 + u32::from(self.is_accessor_descriptor())
}
pub(crate) const fn is_cachable(self) -> bool {
!self.contains(Self::NOT_CACHABLE) && self.contains(Self::FOUND)
}
}
/// Represents an [`u32`] index and it's slot attributes of an element in a object storage.
@ -49,6 +58,17 @@ pub(crate) struct Slot {
}
impl Slot {
pub(crate) const fn new() -> Self {
Self {
index: 0,
attributes: SlotAttributes::empty(),
}
}
pub(crate) const fn is_cachable(self) -> bool {
self.attributes.is_cachable()
}
/// Get the width of the slot.
pub(crate) fn width(self) -> u32 {
self.attributes.width()
@ -74,4 +94,17 @@ impl Slot {
attributes: new_attributes,
}
}
pub(crate) fn set_not_cachable_if_already_prototype(&mut self) {
// NOTE(HalidOdat): This is a bit hack to avoid conditional branches.
//
// Equivalent to:
// if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
// slot.attributes |= SlotAttributes::NOT_CACHABLE;
// }
//
self.attributes |= SlotAttributes::from_bits_retain(
(self.attributes.bits() & SlotAttributes::PROTOTYPE.bits()) << 2,
);
}
}

30
boa_engine/src/object/shape/unique_shape.rs

@ -1,6 +1,6 @@
use std::{cell::RefCell, fmt::Debug};
use boa_gc::{Finalize, Gc, GcRefCell, Trace};
use boa_gc::{Finalize, Gc, GcRefCell, Trace, WeakGc};
use crate::property::PropertyKey;
@ -239,3 +239,31 @@ impl UniqueShape {
ptr as usize
}
}
/// Represents a weak reference to [`UniqueShape`].
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct WeakUniqueShape {
inner: WeakGc<Inner>,
}
impl WeakUniqueShape {
/// Return location in memory of the [`WeakUniqueShape`].
///
/// Returns `0` if the inner [`UniqueShape`] has been freed.
#[inline]
#[must_use]
pub(crate) fn to_addr_usize(&self) -> usize {
self.inner.upgrade().map_or(0, |inner| {
let ptr: *const _ = inner.as_ref();
ptr as usize
})
}
}
impl From<&UniqueShape> for WeakUniqueShape {
fn from(value: &UniqueShape) -> Self {
WeakUniqueShape {
inner: WeakGc::new(&value.inner),
}
}
}

22
boa_engine/src/value/tests.rs

@ -2,6 +2,7 @@ use boa_macros::utf16;
use indoc::indoc;
use super::*;
use crate::object::internal_methods::InternalMethodContext;
use crate::{js_string, run_test_actions, TestAction};
use std::collections::hash_map::DefaultHasher;
@ -197,10 +198,13 @@ fn float_display() {
#[test]
fn string_length_is_not_enumerable() {
run_test_actions([TestAction::assert_context(|ctx| {
let object = JsValue::new(js_string!("foo")).to_object(ctx).unwrap();
run_test_actions([TestAction::assert_context(|context| {
let object = JsValue::new(js_string!("foo")).to_object(context).unwrap();
let length_desc = object
.__get_own_property__(&js_string!("length").into(), ctx)
.__get_own_property__(
&PropertyKey::from(js_string!("length")),
&mut InternalMethodContext::new(context),
)
.unwrap()
.unwrap();
!length_desc.expect_enumerable()
@ -209,16 +213,20 @@ fn string_length_is_not_enumerable() {
#[test]
fn string_length_is_in_utf16_codeunits() {
run_test_actions([TestAction::assert_context(|ctx| {
run_test_actions([TestAction::assert_context(|context| {
// 😀 is one Unicode code point, but 2 UTF-16 code units
let object = JsValue::new(js_string!("😀")).to_object(ctx).unwrap();
let object = JsValue::new(js_string!("😀")).to_object(context).unwrap();
let length_desc = object
.__get_own_property__(&js_string!("length").into(), ctx)
.__get_own_property__(
&PropertyKey::from(js_string!("length")),
&mut InternalMethodContext::new(context),
)
.unwrap()
.unwrap();
length_desc
.expect_value()
.to_integer_or_infinity(ctx)
.to_integer_or_infinity(context)
.unwrap()
== IntegerOrInfinity::Integer(2)
})]);

65
boa_engine/src/vm/code_block.rs

@ -5,12 +5,15 @@
use crate::{
builtins::function::{OrdinaryFunction, ThisMode},
environments::{BindingLocator, CompileTimeEnvironment},
object::{JsObject, ObjectData},
object::{
shape::{slot::Slot, Shape, WeakShape},
JsObject, ObjectData,
},
Context, JsBigInt, JsString, JsValue,
};
use bitflags::bitflags;
use boa_ast::function::FormalParameterList;
use boa_gc::{empty_trace, Finalize, Gc, Trace};
use boa_gc::{empty_trace, Finalize, Gc, GcRefCell, Trace};
use boa_profiler::Profiler;
use std::{cell::Cell, fmt::Display, mem::size_of, rc::Rc};
use thin_vec::ThinVec;
@ -117,6 +120,43 @@ pub(crate) enum Constant {
CompileTimeEnvironment(#[unsafe_ignore_trace] Rc<CompileTimeEnvironment>),
}
/// An inline cache entry for a property access.
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct InlineCache {
/// The property that is accessed.
pub(crate) name: JsString,
/// A pointer is kept to the shape to avoid the shape from being deallocated.
pub(crate) shape: GcRefCell<WeakShape>,
/// The [`Slot`] of the property.
#[unsafe_ignore_trace]
pub(crate) slot: Cell<Slot>,
}
impl InlineCache {
pub(crate) const fn new(name: JsString) -> Self {
Self {
name,
shape: GcRefCell::new(WeakShape::None),
slot: Cell::new(Slot::new()),
}
}
pub(crate) fn set(&self, shape: &Shape, slot: Slot) {
*self.shape.borrow_mut() = shape.into();
self.slot.set(slot);
}
pub(crate) fn slot(&self) -> Slot {
self.slot.get()
}
pub(crate) fn matches(&self, shape: &Shape) -> bool {
self.shape.borrow().to_addr_usize() == shape.to_addr_usize()
}
}
/// The internal representation of a JavaScript function.
///
/// A `CodeBlock` is generated for each function compiled by the
@ -142,6 +182,7 @@ pub struct CodeBlock {
pub(crate) params: FormalParameterList,
/// Bytecode
#[unsafe_ignore_trace]
pub(crate) bytecode: Box<[u8]>,
pub(crate) constants: ThinVec<Constant>,
@ -153,6 +194,9 @@ pub struct CodeBlock {
/// Exception [`Handler`]s.
#[unsafe_ignore_trace]
pub(crate) handlers: ThinVec<Handler>,
/// inline caching
pub(crate) ic: Box<[InlineCache]>,
}
/// ---- `CodeBlock` public API ----
@ -172,6 +216,7 @@ impl CodeBlock {
this_mode: ThisMode::Global,
params: FormalParameterList::default(),
handlers: ThinVec::default(),
ic: Box::default(),
}
}
@ -450,9 +495,7 @@ impl CodeBlock {
.to_std_string_escaped()
)
}
Instruction::GetPropertyByName { index }
| Instruction::SetPropertyByName { index }
| Instruction::DefineOwnPropertyByName { index }
Instruction::DefineOwnPropertyByName { index }
| Instruction::DefineClassStaticMethodByName { index }
| Instruction::DefineClassMethodByName { index }
| Instruction::SetPropertyGetterByName { index }
@ -481,6 +524,18 @@ impl CodeBlock {
.to_std_string_escaped(),
)
}
Instruction::GetPropertyByName { index } | Instruction::SetPropertyByName { index } => {
let ic = &self.ic[index.value() as usize];
let slot = ic.slot();
format!(
"{:04}: '{}', Shape: 0x{:x}, Slot: index: {}, attributes {:?}",
index.value(),
ic.name.to_std_string_escaped(),
ic.shape.borrow().to_addr_usize(),
slot.index,
slot.attributes,
)
}
Instruction::PushPrivateEnvironment { name_indices } => {
format!("{name_indices:?}")
}

1
boa_engine/src/vm/mod.rs

@ -39,6 +39,7 @@ pub(crate) use {
call_frame::CallFrameFlags,
code_block::{
create_function_object, create_function_object_fast, CodeBlockFlags, Constant, Handler,
InlineCache,
},
completion_record::CompletionRecord,
opcode::BindingOpcode,

16
boa_engine/src/vm/opcode/define/class/getter.rs

@ -1,5 +1,6 @@
use crate::{
builtins::function::set_function_name,
object::internal_methods::InternalMethodContext,
property::PropertyDescriptor,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsString,
@ -35,7 +36,7 @@ impl DefineClassStaticGetterByName {
function_mut.set_home_object(class.clone());
}
let set = class
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
@ -47,7 +48,7 @@ impl DefineClassStaticGetterByName {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -104,7 +105,7 @@ impl DefineClassGetterByName {
function_mut.set_home_object(class_proto.clone());
}
let set = class_proto
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
@ -116,7 +117,7 @@ impl DefineClassGetterByName {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -174,8 +175,9 @@ impl Operation for DefineClassStaticGetterByValue {
.expect("method must be function object");
function_mut.set_home_object(class.clone());
}
let set = class
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
@ -225,7 +227,7 @@ impl Operation for DefineClassGetterByValue {
function_mut.set_home_object(class_proto.clone());
}
let set = class_proto
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
@ -237,7 +239,7 @@ impl Operation for DefineClassGetterByValue {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}

7
boa_engine/src/vm/opcode/define/class/method.rs

@ -1,5 +1,6 @@
use crate::{
builtins::function::set_function_name,
object::internal_methods::InternalMethodContext,
property::PropertyDescriptor,
vm::{opcode::Operation, CompletionType},
Context, JsResult,
@ -43,7 +44,7 @@ impl DefineClassStaticMethodByName {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -108,7 +109,7 @@ impl DefineClassMethodByName {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -221,7 +222,7 @@ impl Operation for DefineClassMethodByValue {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}

15
boa_engine/src/vm/opcode/define/class/setter.rs

@ -1,5 +1,6 @@
use crate::{
builtins::function::set_function_name,
object::internal_methods::InternalMethodContext,
property::PropertyDescriptor,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsString,
@ -35,7 +36,7 @@ impl DefineClassStaticSetterByName {
function_mut.set_home_object(class.clone());
}
let get = class
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
@ -48,7 +49,7 @@ impl DefineClassStaticSetterByName {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -105,7 +106,7 @@ impl DefineClassSetterByName {
function_mut.set_home_object(class_proto.clone());
}
let get = class_proto
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
@ -118,7 +119,7 @@ impl DefineClassSetterByName {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
@ -178,7 +179,7 @@ impl Operation for DefineClassStaticSetterByValue {
function_mut.set_home_object(class.clone());
}
let get = class
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
@ -230,7 +231,7 @@ impl Operation for DefineClassSetterByValue {
function_mut.set_home_object(class_proto.clone());
}
let get = class_proto
.__get_own_property__(&key, context)?
.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
@ -243,7 +244,7 @@ impl Operation for DefineClassSetterByValue {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)

5
boa_engine/src/vm/opcode/define/own_property.rs

@ -1,4 +1,5 @@
use crate::{
object::internal_methods::InternalMethodContext,
property::PropertyDescriptor,
vm::{opcode::Operation, CompletionType},
Context, JsNativeError, JsResult,
@ -29,7 +30,7 @@ impl DefineOwnPropertyByName {
.enumerable(true)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -86,7 +87,7 @@ impl Operation for DefineOwnPropertyByValue {
.enumerable(true)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
if !success {
return Err(JsNativeError::typ()

11
boa_engine/src/vm/opcode/delete/mod.rs

@ -1,5 +1,6 @@
use crate::{
error::JsNativeError,
object::internal_methods::InternalMethodContext,
vm::{opcode::Operation, CompletionType},
Context, JsResult,
};
@ -21,8 +22,9 @@ impl DeletePropertyByName {
.code_block()
.constant_string(index)
.into();
let result = object.__delete__(&key, context)?;
if !result && context.vm.frame().code_block.strict() {
let result = object.__delete__(&key, &mut InternalMethodContext::new(context))?;
if !result && context.vm.frame().code_block().strict() {
return Err(JsNativeError::typ()
.with_message("Cannot delete property")
.into());
@ -70,8 +72,9 @@ impl Operation for DeletePropertyByValue {
let value = context.vm.pop();
let object = value.to_object(context)?;
let property_key = key_value.to_property_key(context)?;
let result = object.__delete__(&property_key, context)?;
if !result && context.vm.frame().code_block.strict() {
let result = object.__delete__(&property_key, &mut InternalMethodContext::new(context))?;
if !result && context.vm.frame().code_block().strict() {
return Err(JsNativeError::typ()
.with_message("Cannot delete property")
.into());

7
boa_engine/src/vm/opcode/environment/mod.rs

@ -1,5 +1,6 @@
use crate::{
error::JsNativeError,
object::internal_methods::InternalMethodContext,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue,
};
@ -54,7 +55,7 @@ impl Operation for Super {
};
let value = home_object
.map(|o| o.__get_prototype_of__(context))
.map(|o| o.__get_prototype_of__(&mut InternalMethodContext::new(context)))
.transpose()?
.flatten()
.map_or_else(JsValue::null, JsValue::from);
@ -85,7 +86,7 @@ impl Operation for SuperCallPrepare {
.expect("super call must be in function environment");
let active_function = this_env.slots().function_object().clone();
let super_constructor = active_function
.__get_prototype_of__(context)
.__get_prototype_of__(&mut InternalMethodContext::new(context))
.expect("function object must have prototype");
context
@ -242,7 +243,7 @@ impl Operation for SuperCallDerived {
.clone();
let active_function = this_env.slots().function_object().clone();
let super_constructor = active_function
.__get_prototype_of__(context)
.__get_prototype_of__(&mut InternalMethodContext::new(context))
.expect("function object must have prototype")
.expect("function object must have prototype");

55
boa_engine/src/vm/opcode/get/property.rs

@ -1,4 +1,5 @@
use crate::{
object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes},
property::PropertyKey,
vm::{opcode::Operation, CompletionType},
Context, JsResult,
@ -21,14 +22,52 @@ impl GetPropertyByName {
value.to_object(context)?
};
let key = context
.vm
.frame()
.code_block()
.constant_string(index)
.into();
let ic = &context.vm.frame().code_block().ic[index];
let mut slot = ic.slot();
if slot.is_cachable() {
let object_borrowed = object.borrow();
if ic.matches(object_borrowed.shape()) {
let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
let prototype = object
.borrow()
.properties()
.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(
&receiver,
&[],
context,
)?;
}
context.vm.push(result);
return Ok(CompletionType::Normal);
}
}
let key: PropertyKey = ic.name.clone().into();
let context = &mut InternalMethodContext::new(context);
let result = object.__get__(&key, receiver, context)?;
slot = *context.slot();
// Cache the property.
if slot.attributes.is_cachable() {
let ic = &context.vm.frame().code_block.ic[index];
let object_borrowed = object.borrow();
let shape = object_borrowed.shape();
ic.set(shape, slot);
}
context.vm.push(result);
Ok(CompletionType::Normal)
}
@ -95,7 +134,7 @@ impl Operation for GetPropertyByValue {
}
// Slow path:
let result = object.__get__(&key, receiver, context)?;
let result = object.__get__(&key, receiver, &mut InternalMethodContext::new(context))?;
context.vm.push(result);
Ok(CompletionType::Normal)
@ -143,7 +182,7 @@ impl Operation for GetPropertyByValuePush {
}
// Slow path:
let result = object.__get__(&key, receiver, context)?;
let result = object.__get__(&key, receiver, &mut InternalMethodContext::new(context))?;
context.vm.push(key);
context.vm.push(result);

8
boa_engine/src/vm/opcode/push/class/private.rs

@ -1,6 +1,6 @@
use crate::{
js_string,
object::PrivateElement,
object::{internal_methods::InternalMethodContext, PrivateElement},
property::PropertyDescriptor,
string::utf16,
vm::{opcode::Operation, CompletionType},
@ -29,7 +29,11 @@ impl PushClassPrivateMethod {
.configurable(true)
.build();
method_object
.__define_own_property__(&utf16!("name").into(), desc, context)
.__define_own_property__(
&utf16!("name").into(),
desc,
&mut InternalMethodContext::new(context),
)
.expect("failed to set name property on private method");
let class = context.vm.pop();

6
boa_engine/src/vm/opcode/set/class_prototype.rs

@ -1,5 +1,7 @@
use crate::{
object::{JsObject, ObjectData, CONSTRUCTOR, PROTOTYPE},
object::{
internal_methods::InternalMethodContext, JsObject, ObjectData, CONSTRUCTOR, PROTOTYPE,
},
property::PropertyDescriptorBuilder,
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue,
@ -63,7 +65,7 @@ impl Operation for SetClassPrototype {
.enumerable(false)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)
.expect("cannot fail per spec");

2
boa_engine/src/vm/opcode/set/private.rs

@ -122,7 +122,7 @@ impl SetPrivateMethod {
.configurable(true)
.build();
value
.__define_own_property__(&js_string!("name").into(), desc, context)
.__define_own_property__(&js_string!("name").into(), desc, &mut context.into())
.expect("failed to set name property on private method");
let object = context.vm.pop();

89
boa_engine/src/vm/opcode/set/property.rs

@ -2,6 +2,7 @@ use boa_macros::utf16;
use crate::{
builtins::function::set_function_name,
object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes},
property::{PropertyDescriptor, PropertyKey},
vm::{opcode::Operation, CompletionType},
Context, JsNativeError, JsResult, JsString, JsValue,
@ -25,19 +26,73 @@ impl SetPropertyByName {
object.to_object(context)?
};
let name: PropertyKey = context
.vm
.frame()
.code_block()
.constant_string(index)
.into();
let ic = &context.vm.frame().code_block().ic[index];
let mut slot = ic.slot();
if slot.is_cachable() {
let object_borrowed = object.borrow();
if ic.matches(object_borrowed.shape()) {
let slot_index = slot.index as usize;
if slot.attributes.is_accessor_descriptor() {
let result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
let prototype = object_borrowed
.properties()
.shape
.prototype()
.expect("prototype should have value");
let prototype = prototype.borrow();
prototype.properties().storage[slot_index + 1].clone()
} else {
object_borrowed.properties().storage[slot_index + 1].clone()
};
drop(object_borrowed);
if slot.attributes.has_set() && result.is_object() {
result.as_object().expect("should contain getter").call(
&receiver,
&[value.clone()],
context,
)?;
}
} else if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
let prototype = object_borrowed
.properties()
.shape
.prototype()
.expect("prototype should have value");
let mut prototype = prototype.borrow_mut();
prototype.properties_mut().storage[slot_index] = value.clone();
} else {
drop(object_borrowed);
let mut object_borrowed = object.borrow_mut();
object_borrowed.properties_mut().storage[slot_index] = value.clone();
}
context.vm.push(value);
return Ok(CompletionType::Normal);
}
}
let name: PropertyKey = ic.name.clone().into();
let context = &mut InternalMethodContext::new(context);
let succeeded = object.__set__(name.clone(), value.clone(), receiver, context)?;
if !succeeded && context.vm.frame().code_block.strict() {
return Err(JsNativeError::typ()
.with_message(format!("cannot set non-writable property: {name}"))
.into());
}
slot = *context.slot();
// Cache the property.
if succeeded && slot.is_cachable() {
let ic = &context.vm.frame().code_block.ic[index];
let object_borrowed = object.borrow();
let shape = object_borrowed.shape();
ic.set(shape, slot);
}
context.vm.stack.push(value);
Ok(CompletionType::Normal)
}
@ -158,7 +213,8 @@ impl Operation for SetPropertyByValue {
}
// Slow path:
let succeeded = object.__set__(key.clone(), value.clone(), receiver, context)?;
let succeeded =
object.__set__(key.clone(), value.clone(), receiver, &mut context.into())?;
if !succeeded && context.vm.frame().code_block.strict() {
return Err(JsNativeError::typ()
.with_message(format!("cannot set non-writable property: {key}"))
@ -188,7 +244,7 @@ impl SetPropertyGetterByName {
.constant_string(index)
.into();
let set = object
.__get_own_property__(&name, context)?
.__get_own_property__(&name, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
@ -200,7 +256,7 @@ impl SetPropertyGetterByName {
.enumerable(true)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -245,8 +301,9 @@ impl Operation for SetPropertyGetterByValue {
let object = context.vm.pop();
let object = object.to_object(context)?;
let name = key.to_property_key(context)?;
let set = object
.__get_own_property__(&name, context)?
.__get_own_property__(&name, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::set)
.cloned();
@ -258,7 +315,7 @@ impl Operation for SetPropertyGetterByValue {
.enumerable(true)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -282,8 +339,9 @@ impl SetPropertySetterByName {
.code_block()
.constant_string(index)
.into();
let get = object
.__get_own_property__(&name, context)?
.__get_own_property__(&name, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
@ -295,7 +353,7 @@ impl SetPropertySetterByName {
.enumerable(true)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}
@ -340,8 +398,9 @@ impl Operation for SetPropertySetterByValue {
let object = context.vm.pop();
let object = object.to_object(context)?;
let name = key.to_property_key(context)?;
let get = object
.__get_own_property__(&name, context)?
.__get_own_property__(&name, &mut InternalMethodContext::new(context))?
.as_ref()
.and_then(PropertyDescriptor::get)
.cloned();
@ -353,7 +412,7 @@ impl Operation for SetPropertySetterByValue {
.enumerable(true)
.configurable(true)
.build(),
context,
&mut InternalMethodContext::new(context),
)?;
Ok(CompletionType::Normal)
}

3
boa_engine/src/vm/opcode/set/prototype.rs

@ -1,4 +1,5 @@
use crate::{
object::internal_methods::InternalMethodContext,
vm::{opcode::Operation, CompletionType},
Context, JsResult,
};
@ -29,7 +30,7 @@ impl Operation for SetPrototype {
let object = object.as_object().expect("object is not an object");
object
.__set_prototype_of__(prototype, context)
.__set_prototype_of__(prototype, &mut InternalMethodContext::new(context))
.expect("cannot fail per spec");
Ok(CompletionType::Normal)

Loading…
Cancel
Save