From e7f8005b7eed6d49a76ff66f6a13fb9d9f6e1cc9 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Fri, 12 May 2023 01:38:00 +0200 Subject: [PATCH] Implement Inline Caching - Implement InternalMethodContext context wrapper --- boa_engine/src/builtins/array/mod.rs | 7 +- boa_engine/src/builtins/function/mod.rs | 5 +- boa_engine/src/builtins/function/tests.rs | 7 +- boa_engine/src/builtins/json/mod.rs | 6 +- .../src/builtins/object/for_in_iterator.rs | 12 +- boa_engine/src/builtins/object/mod.rs | 52 ++-- boa_engine/src/builtins/reflect/mod.rs | 44 ++- .../declaration/declaration_pattern.rs | 9 +- .../src/bytecompiler/expression/assign.rs | 10 +- boa_engine/src/bytecompiler/expression/mod.rs | 3 +- .../src/bytecompiler/expression/update.rs | 10 +- boa_engine/src/bytecompiler/function.rs | 2 +- boa_engine/src/bytecompiler/mod.rs | 62 +++-- boa_engine/src/context/mod.rs | 16 +- boa_engine/src/environments/runtime/mod.rs | 7 +- .../src/object/internal_methods/arguments.rs | 14 +- .../src/object/internal_methods/array.rs | 8 +- .../object/internal_methods/bound_function.rs | 8 +- .../src/object/internal_methods/function.rs | 8 +- .../internal_methods/immutable_prototype.rs | 6 +- .../internal_methods/integer_indexed.rs | 20 +- boa_engine/src/object/internal_methods/mod.rs | 261 ++++++++++++++---- .../internal_methods/module_namespace.rs | 32 ++- .../src/object/internal_methods/proxy.rs | 52 ++-- .../src/object/internal_methods/string.rs | 10 +- boa_engine/src/object/jsobject.rs | 6 +- boa_engine/src/object/operations.rs | 107 +++++-- boa_engine/src/object/property_map.rs | 40 +++ boa_engine/src/object/shape/slot.rs | 33 +++ boa_engine/src/value/tests.rs | 22 +- boa_engine/src/vm/code_block.rs | 88 +++++- boa_engine/src/vm/flowgraph/mod.rs | 14 +- boa_engine/src/vm/mod.rs | 8 +- boa_engine/src/vm/opcode/call/mod.rs | 14 +- .../src/vm/opcode/define/class/getter.rs | 17 +- .../src/vm/opcode/define/class/method.rs | 8 +- .../src/vm/opcode/define/class/setter.rs | 16 +- .../src/vm/opcode/define/own_property.rs | 5 +- boa_engine/src/vm/opcode/delete/mod.rs | 11 +- boa_engine/src/vm/opcode/environment/mod.rs | 25 +- boa_engine/src/vm/opcode/get/property.rs | 68 ++++- boa_engine/src/vm/opcode/mod.rs | 4 +- boa_engine/src/vm/opcode/new/mod.rs | 9 +- .../src/vm/opcode/push/class/private.rs | 8 +- .../src/vm/opcode/set/class_prototype.rs | 6 +- boa_engine/src/vm/opcode/set/private.rs | 8 +- boa_engine/src/vm/opcode/set/property.rs | 96 ++++++- boa_engine/src/vm/opcode/set/prototype.rs | 3 +- 48 files changed, 952 insertions(+), 335 deletions(-) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 245b30a28e1..7f4c57197a2 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/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, symbol::JsSymbol, @@ -323,7 +326,7 @@ impl Array { .enumerable(false) .configurable(false) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(array) diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 6e32a459fc1..cf0a3f2ecf9 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -20,7 +20,7 @@ use crate::{ js_string, native_function::NativeFunction, object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData}, - object::{JsFunction, PrivateElement, PrivateName}, + object::{internal_methods::InternalMethodContext, JsFunction, PrivateElement, PrivateName}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, string::utf16, @@ -1100,7 +1100,8 @@ impl BoundFunction { context: &mut Context<'_>, ) -> JsResult<JsObject> { // 1. Let proto be ? targetFunction.[[GetPrototypeOf]](). - let proto = target_function.__get_prototype_of__(context)?; + let proto = + target_function.__get_prototype_of__(&mut InternalMethodContext::new(context))?; let is_constructor = target_function.is_constructor(); // 2. Let internalSlotsList be the internal slots listed in Table 35, plus [[Prototype]] and [[Extensible]]. diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index d5d87131fbd..baa6cd0a8c2 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -2,7 +2,7 @@ use crate::{ error::JsNativeError, js_string, native_function::NativeFunction, - object::{FunctionObjectBuilder, JsObject}, + object::{internal_methods::InternalMethodContext, FunctionObjectBuilder, JsObject}, property::{Attribute, PropertyDescriptor}, run_test_actions, JsNativeErrorKind, JsValue, TestAction, }; @@ -153,7 +153,10 @@ fn closure_capture_clone() { let hw = js_string!( string, &object - .__get_own_property__(&"key".into(), context)? + .__get_own_property__( + &"key".into(), + &mut InternalMethodContext::new(context) + )? .and_then(|prop| prop.value().cloned()) .and_then(|val| val.as_string().cloned()) .ok_or_else( diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 20b80fef102..75be7a62d7a 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/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::{utf16, CodePoint}, @@ -191,7 +191,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 { @@ -220,7 +220,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 { diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index 44af9f42f1c..0f2ef4d611b 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -12,7 +12,7 @@ use crate::{ builtins::{iterable::create_iter_result_object, BuiltInBuilder, IntrinsicObject}, context::intrinsics::Intrinsics, error::JsNativeError, - object::{JsObject, ObjectData}, + object::{internal_methods::InternalMethodContext, JsObject, ObjectData}, property::PropertyKey, realm::Realm, Context, JsResult, JsString, JsValue, @@ -109,7 +109,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) => { @@ -125,9 +126,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)); diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 4c4fc5202a1..5a08aa7346a 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/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, @@ -181,7 +181,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)) } @@ -218,7 +218,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 { @@ -341,7 +342,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 { @@ -353,7 +355,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. @@ -385,7 +387,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 { @@ -397,7 +400,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. @@ -466,7 +469,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)) @@ -491,7 +495,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()); @@ -499,7 +503,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); @@ -612,7 +617,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)) } @@ -663,7 +668,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 { @@ -905,9 +910,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() @@ -946,11 +952,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). @@ -1169,7 +1178,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() @@ -1326,7 +1335,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(); @@ -1335,7 +1344,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)?; @@ -1383,7 +1395,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 diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index a0b2807fe0d..1067c60e35a 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -15,7 +15,7 @@ use crate::{ builtins::{self, BuiltInObject}, context::intrinsics::Intrinsics, error::JsNativeError, - object::JsObject, + object::{internal_methods::InternalMethodContext, JsObject}, property::Attribute, realm::Realm, symbol::JsSymbol, @@ -168,7 +168,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) } @@ -191,7 +195,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. @@ -221,7 +227,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. @@ -270,7 +277,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)) } @@ -295,7 +302,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. @@ -315,7 +325,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. @@ -337,7 +349,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(); @@ -363,7 +375,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. @@ -389,8 +403,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()) } @@ -420,6 +440,8 @@ impl Reflect { .into()) } }; - Ok(target.__set_prototype_of__(proto, context)?.into()) + Ok(target + .__set_prototype_of__(proto, &mut InternalMethodContext::new(context))? + .into()) } } diff --git a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs index 9801c63bd3d..a72bd5fb6cf 100644 --- a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs +++ b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs @@ -39,8 +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(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*name); } PropertyName::Computed(node) => { self.compile_expr(node, true); @@ -111,8 +110,7 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*name); } PropertyName::Computed(node) => { self.compile_expr(node, true); @@ -151,8 +149,7 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*name); } PropertyName::Computed(node) => { self.compile_expr(node, true); diff --git a/boa_engine/src/bytecompiler/expression/assign.rs b/boa_engine/src/bytecompiler/expression/assign.rs index 4d8d0bc99c3..e4b85a6b7bf 100644 --- a/boa_engine/src/bytecompiler/expression/assign.rs +++ b/boa_engine/src/bytecompiler/expression/assign.rs @@ -96,13 +96,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(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)); @@ -112,7 +111,7 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(opcode); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit_set_property_by_name(*name); if !use_expr { self.emit_opcode(Opcode::Pop); } @@ -162,14 +161,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(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)); @@ -179,7 +177,7 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(opcode); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit_set_property_by_name(*name); if !use_expr { self.emit_opcode(Opcode::Pop); } diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index ef118a5cfa0..9df154de4aa 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -217,8 +217,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(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(field) => { self.compile_expr(field, true); diff --git a/boa_engine/src/bytecompiler/expression/update.rs b/boa_engine/src/bytecompiler/expression/update.rs index 9e2ab13b2b8..42f8eed5ed0 100644 --- a/boa_engine/src/bytecompiler/expression/update.rs +++ b/boa_engine/src/bytecompiler/expression/update.rs @@ -61,20 +61,19 @@ 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(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*name); self.emit_opcode(opcode); if post { self.emit_opcode(Opcode::RotateRight); self.emit_u8(4); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit_set_property_by_name(*name); if post { self.emit_opcode(Opcode::Pop); } @@ -118,21 +117,20 @@ 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(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*name); self.emit_opcode(opcode); if post { self.emit_opcode(Opcode::RotateRight); self.emit_u8(3); } - self.emit(Opcode::SetPropertyByName, &[index]); + self.emit_set_property_by_name(*name); if post { self.emit_opcode(Opcode::Pop); } diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index b64e963e6fd..417e2f663b5 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -154,7 +154,7 @@ impl FunctionCompiler { if compiler .bytecode .last() - .filter(|last| **last == Opcode::Return as u8) + .filter(|last| **last == (Opcode::Return as u8).into()) .is_none() { compiler.emit_opcode(Opcode::Return); diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index b372fb0a55c..ecb7ef8e28c 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -20,7 +20,7 @@ use crate::{ builtins::function::ThisMode, environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment}, js_string, - vm::{BindingOpcode, CodeBlock, CodeBlockFlags, Opcode}, + vm::{BindingOpcode, CodeBlock, CodeBlockFlags, InlineCache, Opcode}, Context, JsBigInt, JsString, JsValue, }; use boa_ast::{ @@ -229,7 +229,7 @@ pub struct ByteCompiler<'ctx, 'host> { pub(crate) params: FormalParameterList, /// Bytecode - pub(crate) bytecode: Vec<u8>, + pub(crate) bytecode: Vec<Cell<u8>>, /// Literals pub(crate) literals: Vec<JsValue>, @@ -251,6 +251,8 @@ pub struct ByteCompiler<'ctx, 'host> { pub(crate) code_block_flags: CodeBlockFlags, + pub(crate) ic: Vec<InlineCache>, + literals_map: FxHashMap<Literal, u32>, names_map: FxHashMap<Identifier, u32>, bindings_map: FxHashMap<BindingLocator, u32>, @@ -294,6 +296,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { params: FormalParameterList::default(), compile_environments: Vec::default(), code_block_flags, + ic: Vec::default(), literals_map: FxHashMap::default(), names_map: FxHashMap::default(), @@ -428,16 +431,36 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } } + 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 name = self.names[name_index as usize].clone(); + self.ic.push(InlineCache::new(name)); + + self.emit(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 name = self.names[name_index as usize].clone(); + self.ic.push(InlineCache::new(name)); + + self.emit(Opcode::SetPropertyByName, &[ic_index]); + } + fn emit_u64(&mut self, value: u64) { - self.bytecode.extend(value.to_ne_bytes()); + self.bytecode.extend(value.to_ne_bytes().map(Cell::new)); } fn emit_u32(&mut self, value: u32) { - self.bytecode.extend(value.to_ne_bytes()); + self.bytecode.extend(value.to_ne_bytes().map(Cell::new)); } fn emit_u16(&mut self, value: u16) { - self.bytecode.extend(value.to_ne_bytes()); + self.bytecode.extend(value.to_ne_bytes().map(Cell::new)); } pub(crate) fn emit_opcode(&mut self, opcode: Opcode) { @@ -445,7 +468,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } fn emit_u8(&mut self, value: u8) { - self.bytecode.push(value); + self.bytecode.push(Cell::new(value)); } fn emit_push_integer(&mut self, value: i32) { @@ -550,11 +573,11 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { let Label { index } = label; let index = index as usize; - let bytes = target.to_ne_bytes(); + let bytes = target.to_ne_bytes().map(Cell::new); // 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) { @@ -572,10 +595,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { 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::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*name); } PropertyAccessField::Expr(expr) => { self.compile_expr(access.target(), true); @@ -591,10 +613,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } 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(Opcode::GetPropertyByName, &[index]); + + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(expr) => { self.emit_opcode(Opcode::Super); @@ -668,9 +690,8 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { 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(Opcode::SetPropertyByName, &[index]); + self.emit_set_property_by_name(*name); if !use_expr { self.emit(Opcode::Pop, &[]); } @@ -700,8 +721,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { 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(Opcode::SetPropertyByName, &[index]); + self.emit_set_property_by_name(*name); if !use_expr { self.emit(Opcode::Pop, &[]); } @@ -818,8 +838,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { self.emit_opcode(Opcode::Dup); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(field) => { self.compile_expr(field, true); @@ -839,8 +858,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { self.emit_opcode(Opcode::This); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(expr) => { self.compile_expr(expr, true); @@ -925,8 +943,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { self.emit_opcode(Opcode::Dup); match field { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit(Opcode::GetPropertyByName, &[index]); + self.emit_get_property_by_name(*name); } PropertyAccessField::Expr(expr) => { self.compile_expr(expr, true); @@ -1402,6 +1419,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { functions: self.functions.into_boxed_slice(), compile_environments: self.compile_environments.into_boxed_slice(), flags: Cell::new(self.code_block_flags), + ic: self.ic.into_boxed_slice(), } } diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index 5c4ea4fc760..cbda51ecbfb 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -22,7 +22,9 @@ use crate::{ job::{JobQueue, NativeJob, SimpleJobQueue}, module::{IdleModuleLoader, ModuleLoader, SimpleModuleLoader}, native_function::NativeFunction, - object::{shape::RootShape, FunctionObjectBuilder, JsObject}, + object::{ + internal_methods::InternalMethodContext, shape::RootShape, FunctionObjectBuilder, JsObject, + }, optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, @@ -519,7 +521,9 @@ impl Context<'_> { // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). let name = self.interner().resolve_expect(name.sym()).utf16().into(); - let existing_prop = global_object.__get_own_property__(&name, self)?; + + let existing_prop = + global_object.__get_own_property__(&name, &mut InternalMethodContext::new(self))?; // 4. If existingProp is undefined, return ? IsExtensible(globalObject). let Some(existing_prop) = existing_prop else { @@ -629,7 +633,9 @@ impl Context<'_> { // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16()); - let existing_prop = global_object.__get_own_property__(&name, self)?; + + let existing_prop = + global_object.__get_own_property__(&name, &mut InternalMethodContext::new(self))?; // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then let desc = if existing_prop.is_none() @@ -676,7 +682,9 @@ impl Context<'_> { // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16()); - let existing_prop = global_object.__get_own_property__(&name, self)?; + + let existing_prop = + global_object.__get_own_property__(&name, &mut InternalMethodContext::new(self))?; // 4. If existingProp is undefined, return false. let Some(existing_prop) = existing_prop else { diff --git a/boa_engine/src/environments/runtime/mod.rs b/boa_engine/src/environments/runtime/mod.rs index 0b09a4f06e8..9ab0ccf4569 100644 --- a/boa_engine/src/environments/runtime/mod.rs +++ b/boa_engine/src/environments/runtime/mod.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ environments::CompileTimeEnvironment, - object::{JsObject, PrivateName}, + object::{internal_methods::InternalMethodContext, JsObject, PrivateName}, Context, JsResult, JsString, JsSymbol, JsValue, }; use boa_ast::expression::Identifier; @@ -754,7 +754,8 @@ impl Context<'_> { .interner() .resolve_expect(locator.name().sym()) .into_common::<JsString>(false); - self.global_object().__delete__(&key.into(), self) + self.global_object() + .__delete__(&key.into(), &mut InternalMethodContext::new(self)) } else { match self.environment_expect(locator.environment_index) { Environment::Declarative(_) => Ok(false), @@ -765,7 +766,7 @@ impl Context<'_> { .resolve_expect(locator.name.sym()) .into_common(false); - obj.__delete__(&key.into(), self) + obj.__delete__(&key.into(), &mut InternalMethodContext::new(self)) } } } diff --git a/boa_engine/src/object/internal_methods/arguments.rs b/boa_engine/src/object/internal_methods/arguments.rs index bb919def58a..9c42e6d2b9d 100644 --- a/boa_engine/src/object/internal_methods/arguments.rs +++ b/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)?; diff --git a/boa_engine/src/object/internal_methods/array.rs b/boa_engine/src/object/internal_methods/array.rs index 6e095174f51..af7eee0b2b3 100644 --- a/boa_engine/src/object/internal_methods/array.rs +++ b/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 { @@ -109,7 +109,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 { diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs index 8fe52973561..6bd3ec9de39 100644 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ b/boa_engine/src/object/internal_methods/bound_function.rs @@ -1,6 +1,6 @@ -use crate::{object::JsObject, Context, JsResult, JsValue}; +use crate::{object::JsObject, JsResult, JsValue}; -use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; +use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; /// Definitions of the internal object methods for function objects. /// @@ -32,7 +32,7 @@ fn bound_function_exotic_call( obj: &JsObject, _: &JsValue, arguments_list: &[JsValue], - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsValue> { let obj = obj.borrow(); let bound_function = obj @@ -67,7 +67,7 @@ fn bound_function_exotic_construct( obj: &JsObject, arguments_list: &[JsValue], new_target: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsObject> { let object = obj.borrow(); let bound_function = object diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs index 2b32875a841..57b30d9e42a 100644 --- a/boa_engine/src/object/internal_methods/function.rs +++ b/boa_engine/src/object/internal_methods/function.rs @@ -3,9 +3,11 @@ use crate::{ internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, JsObject, }, - Context, JsResult, JsValue, + JsResult, JsValue, }; +use super::InternalMethodContext; + /// Definitions of the internal object methods for function objects. /// /// More information: @@ -36,7 +38,7 @@ fn function_call( obj: &JsObject, this: &JsValue, args: &[JsValue], - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsValue> { obj.call_internal(this, args, context) } @@ -52,7 +54,7 @@ fn function_construct( obj: &JsObject, args: &[JsValue], new_target: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsObject> { obj.construct_internal(args, &new_target.clone().into(), context) } diff --git a/boa_engine/src/object/internal_methods/immutable_prototype.rs b/boa_engine/src/object/internal_methods/immutable_prototype.rs index f9cf56c7410..e7bca73421e 100644 --- a/boa_engine/src/object/internal_methods/immutable_prototype.rs +++ b/boa_engine/src/object/internal_methods/immutable_prototype.rs @@ -1,9 +1,9 @@ use crate::{ object::{JsObject, JsPrototype}, - Context, JsResult, + JsResult, }; -use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; +use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; /// Definitions of the internal object methods for [**Immutable Prototype Exotic Objects**][spec]. /// @@ -21,7 +21,7 @@ pub(crate) static IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS: InternalObjectMet pub(crate) fn immutable_prototype_exotic_set_prototype_of( obj: &JsObject, val: JsPrototype, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. Return ? SetImmutablePrototype(O, V). diff --git a/boa_engine/src/object/internal_methods/integer_indexed.rs b/boa_engine/src/object/internal_methods/integer_indexed.rs index e5fb7909db9..374af9d50ff 100644 --- a/boa_engine/src/object/internal_methods/integer_indexed.rs +++ b/boa_engine/src/object/internal_methods/integer_indexed.rs @@ -4,10 +4,10 @@ use crate::{ builtins::{array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType}, object::JsObject, property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, JsValue, + JsResult, 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. /// @@ -36,7 +36,7 @@ pub(crate) static INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS: InternalObjectMethods pub(crate) fn integer_indexed_exotic_get_own_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Option<PropertyDescriptor>> { // 1. If Type(P) is String, then // a. Let numericIndex be ! CanonicalNumericIndexString(P). @@ -76,7 +76,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> { // 1. If Type(P) is String, then // a. Let numericIndex be ! CanonicalNumericIndexString(P). @@ -105,7 +105,7 @@ pub(crate) fn integer_indexed_exotic_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. If Type(P) is String, then // a. Let numericIndex be ! CanonicalNumericIndexString(P). @@ -154,7 +154,7 @@ pub(crate) fn integer_indexed_exotic_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsValue> { // 1. If Type(P) is String, then // a. Let numericIndex be ! CanonicalNumericIndexString(P). @@ -185,7 +185,7 @@ pub(crate) fn integer_indexed_exotic_set( key: PropertyKey, value: JsValue, receiver: JsValue, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. If Type(P) is String, then // a. Let numericIndex be ! CanonicalNumericIndexString(P). @@ -217,7 +217,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> { // 1. If Type(P) is String, then // a. Let numericIndex be ! CanonicalNumericIndexString(P). @@ -261,7 +261,7 @@ pub(crate) fn integer_indexed_exotic_delete( #[allow(clippy::unnecessary_wraps)] pub(crate) fn integer_indexed_exotic_own_property_keys( obj: &JsObject, - _context: &mut Context<'_>, + _context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Vec<PropertyKey>> { let obj = obj.borrow(); let inner = obj.as_typed_array().expect( @@ -375,7 +375,7 @@ fn integer_indexed_element_set( obj: &JsObject, index: usize, value: &JsValue, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<()> { let obj_borrow = obj.borrow(); let inner = obj_borrow.as_typed_array().expect( diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 976f2824226..b63ac3e1a4f 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/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, @@ -27,6 +32,54 @@ pub(super) mod string; pub(crate) use array::ARRAY_EXOTIC_INTERNAL_METHODS; +/// A lightweight wrapper around [`Context`] used in [`InternalObjectMethods`]. +#[derive(Debug)] +pub(crate) struct InternalMethodContext<'ctx, 'host> { + context: &'ctx mut Context<'host>, + slot: Slot, +} + +impl<'ctx, 'host> InternalMethodContext<'ctx, 'host> { + /// Create a new [`InternalMethodContext`]. + pub(crate) fn new(context: &'ctx mut Context<'host>) -> 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<'host> Deref for InternalMethodContext<'_, 'host> { + type Target = Context<'host>; + + #[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, 'host> From<&'context mut Context<'host>> + for InternalMethodContext<'context, 'host> +{ + #[inline] + fn from(context: &'context mut Context<'host>) -> Self { + Self::new(context) + } +} + impl JsObject { /// Internal method `[[GetPrototypeOf]]` /// @@ -37,7 +90,10 @@ impl JsObject { /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof #[track_caller] - pub(crate) fn __get_prototype_of__(&self, context: &mut Context<'_>) -> JsResult<JsPrototype> { + pub(crate) fn __get_prototype_of__( + &self, + context: &mut InternalMethodContext<'_, '_>, + ) -> JsResult<JsPrototype> { let _timer = Profiler::global().start_event("Object::__get_prototype_of__", "object"); (self.vtable().__get_prototype_of__)(self, context) } @@ -53,7 +109,7 @@ impl JsObject { pub(crate) fn __set_prototype_of__( &self, val: JsPrototype, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { let _timer = Profiler::global().start_event("Object::__set_prototype_of__", "object"); (self.vtable().__set_prototype_of__)(self, val, context) @@ -67,7 +123,10 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible - pub(crate) fn __is_extensible__(&self, context: &mut Context<'_>) -> JsResult<bool> { + pub(crate) fn __is_extensible__( + &self, + context: &mut InternalMethodContext<'_, '_>, + ) -> JsResult<bool> { let _timer = Profiler::global().start_event("Object::__is_extensible__", "object"); (self.vtable().__is_extensible__)(self, context) } @@ -80,7 +139,10 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions - pub(crate) fn __prevent_extensions__(&self, context: &mut Context<'_>) -> JsResult<bool> { + pub(crate) fn __prevent_extensions__( + &self, + context: &mut InternalMethodContext<'_, '_>, + ) -> JsResult<bool> { let _timer = Profiler::global().start_event("Object::__prevent_extensions__", "object"); (self.vtable().__prevent_extensions__)(self, context) } @@ -96,7 +158,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) @@ -114,7 +176,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) @@ -131,7 +193,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) @@ -149,7 +211,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) @@ -168,7 +230,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) @@ -185,7 +247,7 @@ impl JsObject { pub(crate) fn __delete__( &self, key: &PropertyKey, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { let _timer = Profiler::global().start_event("Object::__delete__", "object"); (self.vtable().__delete__)(self, key, context) @@ -202,7 +264,7 @@ impl JsObject { #[track_caller] pub(crate) fn __own_property_keys__( &self, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Vec<PropertyKey>> { let _timer = Profiler::global().start_event("Object::__own_property_keys__", "object"); (self.vtable().__own_property_keys__)(self, context) @@ -221,7 +283,7 @@ impl JsObject { &self, this: &JsValue, args: &[JsValue], - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsValue> { let _timer = Profiler::global().start_event("Object::__call__", "object"); self.vtable() @@ -244,7 +306,7 @@ impl JsObject { &self, args: &[JsValue], new_target: &Self, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Self> { let _timer = Profiler::global().start_event("Object::__construct__", "object"); self.vtable() @@ -292,24 +354,60 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj #[derive(Clone, Copy)] #[allow(clippy::type_complexity)] pub(crate) struct InternalObjectMethods { - pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context<'_>) -> JsResult<JsPrototype>, - 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__: - Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>>, - pub(crate) __construct__: - Option<fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult<JsObject>>, + pub(crate) __get_prototype_of__: + fn(&JsObject, &mut InternalMethodContext<'_, '_>) -> JsResult<JsPrototype>, + pub(crate) __set_prototype_of__: + fn(&JsObject, JsPrototype, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>, + pub(crate) __is_extensible__: + fn(&JsObject, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>, + pub(crate) __prevent_extensions__: + fn(&JsObject, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>, + 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, &mut InternalMethodContext<'_, '_>) -> JsResult<Vec<PropertyKey>>, + pub(crate) __call__: Option< + fn( + &JsObject, + &JsValue, + &[JsValue], + &mut InternalMethodContext<'_, '_>, + ) -> JsResult<JsValue>, + >, + pub(crate) __construct__: Option< + fn( + &JsObject, + &[JsValue], + &JsObject, + &mut InternalMethodContext<'_, '_>, + ) -> JsResult<JsObject>, + >, } /// Abstract operation `OrdinaryGetPrototypeOf`. @@ -321,7 +419,7 @@ pub(crate) struct InternalObjectMethods { #[allow(clippy::unnecessary_wraps)] pub(crate) fn ordinary_get_prototype_of( obj: &JsObject, - _context: &mut Context<'_>, + _context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsPrototype> { let _timer = Profiler::global().start_event("Object::ordinary_get_prototype_of", "object"); @@ -339,7 +437,7 @@ pub(crate) fn ordinary_get_prototype_of( pub(crate) fn ordinary_set_prototype_of( obj: &JsObject, val: JsPrototype, - _: &mut Context<'_>, + _: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. Assert: Either Type(V) is Object or Type(V) is Null. // 2. Let current be O.[[Prototype]]. @@ -392,7 +490,10 @@ pub(crate) fn ordinary_set_prototype_of( /// /// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible #[allow(clippy::unnecessary_wraps)] -pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context<'_>) -> JsResult<bool> { +pub(crate) fn ordinary_is_extensible( + obj: &JsObject, + _context: &mut InternalMethodContext<'_, '_>, +) -> JsResult<bool> { // 1. Return O.[[Extensible]]. Ok(obj.borrow().extensible) } @@ -406,7 +507,7 @@ pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context<'_>) #[allow(clippy::unnecessary_wraps)] pub(crate) fn ordinary_prevent_extensions( obj: &JsObject, - _context: &mut Context<'_>, + _context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. Set O.[[Extensible]] to false. obj.borrow_mut().extensible = false; @@ -425,7 +526,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. @@ -442,7 +543,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`. @@ -455,9 +556,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)?; @@ -470,6 +572,7 @@ pub(crate) fn ordinary_define_own_property( extensible, desc, current, + context.slot(), )) } @@ -482,18 +585,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). @@ -512,7 +620,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. @@ -522,6 +630,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) } @@ -530,20 +641,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()), - }, + } } } @@ -558,7 +673,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"); @@ -577,11 +692,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() @@ -604,6 +728,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)? { @@ -625,12 +755,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()); @@ -657,7 +790,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. @@ -688,7 +821,7 @@ pub(crate) fn ordinary_delete( #[allow(clippy::unnecessary_wraps)] pub(crate) fn ordinary_own_property_keys( obj: &JsObject, - _context: &mut Context<'_>, + _context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Vec<PropertyKey>> { let _timer = Profiler::global().start_event("Object::ordinary_own_property_keys", "object"); // 1. Let keys be a new empty List. @@ -730,7 +863,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` @@ -744,6 +877,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"); @@ -759,7 +893,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() { @@ -781,7 +915,9 @@ pub(crate) fn validate_and_apply_property_descriptor( // its default value. desc.into_accessor_defaulted() }, + slot, ); + slot.attributes |= SlotAttributes::FOUND; } // e. Return true. @@ -877,7 +1013,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. diff --git a/boa_engine/src/object/internal_methods/module_namespace.rs b/boa_engine/src/object/internal_methods/module_namespace.rs index 1575c8c10be..1ee0ab4450d 100644 --- a/boa_engine/src/object/internal_methods/module_namespace.rs +++ b/boa_engine/src/object/internal_methods/module_namespace.rs @@ -5,13 +5,13 @@ use crate::{ module::BindingName, object::{JsObject, JsPrototype}, property::{PropertyDescriptor, PropertyKey}, - Context, JsNativeError, JsResult, JsValue, + JsNativeError, JsResult, JsValue, }; 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]. @@ -39,7 +39,7 @@ pub(crate) static MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS: InternalObjectMethod #[allow(clippy::unnecessary_wraps)] fn module_namespace_exotic_get_prototype_of( _: &JsObject, - _: &mut Context<'_>, + _: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsPrototype> { // 1. Return null. Ok(None) @@ -52,7 +52,7 @@ fn module_namespace_exotic_get_prototype_of( fn module_namespace_exotic_set_prototype_of( obj: &JsObject, val: JsPrototype, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. Return ! SetImmutablePrototype(O, V). Ok( @@ -65,7 +65,10 @@ fn module_namespace_exotic_set_prototype_of( /// /// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-isextensible #[allow(clippy::unnecessary_wraps)] -fn module_namespace_exotic_is_extensible(_: &JsObject, _: &mut Context<'_>) -> JsResult<bool> { +fn module_namespace_exotic_is_extensible( + _: &JsObject, + _: &mut InternalMethodContext<'_, '_>, +) -> JsResult<bool> { // 1. Return false. Ok(false) } @@ -74,7 +77,10 @@ fn module_namespace_exotic_is_extensible(_: &JsObject, _: &mut Context<'_>) -> J /// /// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-preventextensions #[allow(clippy::unnecessary_wraps)] -fn module_namespace_exotic_prevent_extensions(_: &JsObject, _: &mut Context<'_>) -> JsResult<bool> { +fn module_namespace_exotic_prevent_extensions( + _: &JsObject, + _: &mut InternalMethodContext<'_, '_>, +) -> JsResult<bool> { Ok(true) } @@ -84,7 +90,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 +134,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 +170,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 +199,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). @@ -268,7 +274,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) @@ -280,7 +286,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). @@ -308,7 +314,7 @@ fn module_namespace_exotic_delete( /// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-ownpropertykeys fn module_namespace_exotic_own_property_keys( obj: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Vec<PropertyKey>> { // 2. Let symbolKeys be OrdinaryOwnPropertyKeys(O). let symbol_keys = ordinary_own_property_keys(obj, context)?; diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 6874d8d5d9d..2bad3e295ee 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -1,14 +1,16 @@ 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, - Context, JsResult, JsValue, + JsResult, JsValue, }; use rustc_hash::FxHashSet; +use super::InternalMethodContext; + /// Definitions of the internal object methods for array exotic objects. /// /// More information: @@ -53,7 +55,7 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods = /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof pub(crate) fn proxy_exotic_get_prototype_of( obj: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsPrototype> { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -115,7 +117,7 @@ pub(crate) fn proxy_exotic_get_prototype_of( pub(crate) fn proxy_exotic_set_prototype_of( obj: &JsObject, val: JsPrototype, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -178,7 +180,7 @@ pub(crate) fn proxy_exotic_set_prototype_of( /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible pub(crate) fn proxy_exotic_is_extensible( obj: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -224,7 +226,7 @@ pub(crate) fn proxy_exotic_is_extensible( /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions pub(crate) fn proxy_exotic_prevent_extensions( obj: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<bool> { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -270,8 +272,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. @@ -286,6 +290,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); }; @@ -394,8 +399,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. @@ -505,8 +512,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. @@ -571,8 +580,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. @@ -640,8 +652,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. @@ -721,7 +735,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. @@ -787,7 +801,7 @@ pub(crate) fn proxy_exotic_delete( /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys pub(crate) fn proxy_exotic_own_property_keys( obj: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Vec<PropertyKey>> { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -923,7 +937,7 @@ fn proxy_exotic_call( obj: &JsObject, this: &JsValue, args: &[JsValue], - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsValue> { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -948,7 +962,7 @@ fn proxy_exotic_call( // 8. Return ? Call(trap, handler, ยซ target, thisArgument, argArray ยป). trap.call( &handler.into(), - &[target.clone().into(), this.clone(), arg_array.into()], + &[target.into(), this.clone(), arg_array.into()], context, ) } @@ -963,7 +977,7 @@ fn proxy_exotic_construct( obj: &JsObject, args: &[JsValue], new_target: &JsObject, - context: &mut Context<'_>, + context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<JsObject> { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -991,11 +1005,7 @@ fn proxy_exotic_construct( // 9. Let newObj be ? Call(trap, handler, ยซ target, argArray, newTarget ยป). let new_obj = trap.call( &handler.into(), - &[ - target.clone().into(), - arg_array.into(), - new_target.clone().into(), - ], + &[target.into(), arg_array.into(), new_target.clone().into()], context, )?; diff --git a/boa_engine/src/object/internal_methods/string.rs b/boa_engine/src/object/internal_methods/string.rs index 1a99da753bc..4f1dd9c5f30 100644 --- a/boa_engine/src/object/internal_methods/string.rs +++ b/boa_engine/src/object/internal_methods/string.rs @@ -2,10 +2,10 @@ use crate::{ js_string, object::JsObject, property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, + 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). @@ -85,7 +85,7 @@ pub(crate) fn string_exotic_define_own_property( #[allow(clippy::unnecessary_wraps)] pub(crate) fn string_exotic_own_property_keys( obj: &JsObject, - _context: &mut Context<'_>, + _context: &mut InternalMethodContext<'_, '_>, ) -> JsResult<Vec<PropertyKey>> { let obj = obj.borrow(); diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 2b583a4e758..3c444441054 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -3,7 +3,9 @@ //! The `JsObject` is a garbage collected Object. use super::{ - internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS}, + internal_methods::{ + InternalMethodContext, InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS, + }, shape::RootShape, JsPrototype, NativeObject, Object, PrivateName, PropertyMap, }; @@ -819,6 +821,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. diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index a2861c47016..1770fe8ca62 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -10,6 +10,8 @@ use crate::{ Context, JsResult, JsSymbol, JsValue, }; +use super::internal_methods::InternalMethodContext; + /// Object integrity level. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum IntegrityLevel { @@ -55,7 +57,7 @@ impl JsObject { #[inline] pub fn is_extensible(&self, context: &mut Context<'_>) -> JsResult<bool> { // 1. Return ? O.[[IsExtensible]](). - self.__is_extensible__(context) + self.__is_extensible__(&mut InternalMethodContext::new(context)) } /// Get property from object or throw. @@ -71,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. @@ -96,7 +102,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() @@ -119,6 +130,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>, @@ -225,7 +268,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() @@ -250,7 +297,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() @@ -274,7 +321,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. @@ -291,7 +339,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()) @@ -322,7 +370,7 @@ impl JsObject { })?; // 3. Return ? F.[[Call]](V, argumentsList). - function.__call__(this, args, context) + function.__call__(this, args, &mut InternalMethodContext::new(context)) } /// `Construct ( F [ , argumentsList [ , newTarget ] ] )` @@ -349,7 +397,7 @@ impl JsObject { let new_target = new_target.unwrap_or(self); // 2. If argumentsList is not present, set argumentsList to a new empty List. // 3. Return ? F.[[Construct]](argumentsList, newTarget). - self.__construct__(args, new_target, context) + self.__construct__(args, new_target, &mut InternalMethodContext::new(context)) } /// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen]. @@ -367,14 +415,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 @@ -395,7 +443,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 @@ -445,12 +494,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. @@ -559,7 +609,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![]; @@ -574,7 +624,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() { @@ -625,7 +676,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. @@ -1026,7 +1082,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) @@ -1052,7 +1112,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 )` @@ -1160,7 +1225,7 @@ impl JsValue { self.type_of() )) })? - .__call__(this, args, context) + .__call__(this, args, &mut InternalMethodContext::new(context)) } /// Abstract operation `( V, P [ , argumentsList ] )` @@ -1232,7 +1297,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), diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index 9a06080a130..6ef4a5c0871 100644 --- a/boa_engine/src/object/property_map.rs +++ b/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); + } + 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, 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 diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs index 64fb3c8f43f..b5c01daddfb 100644 --- a/boa_engine/src/object/shape/slot.rs +++ b/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, + ); + } } diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index ff7d5f9a266..c25d096614c 100644 --- a/boa_engine/src/value/tests.rs +++ b/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::{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("foo").to_object(ctx).unwrap(); + run_test_actions([TestAction::assert_context(|context| { + let object = JsValue::new("foo").to_object(context).unwrap(); let length_desc = object - .__get_own_property__(&PropertyKey::from("length"), ctx) + .__get_own_property__( + &PropertyKey::from("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("๐").to_object(ctx).unwrap(); + let object = JsValue::new("๐").to_object(context).unwrap(); + let length_desc = object - .__get_own_property__(&PropertyKey::from("length"), ctx) + .__get_own_property__( + &PropertyKey::from("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) })]); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index c2cf7193c9b..d03d04d8363 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -12,7 +12,11 @@ use crate::{ context::intrinsics::StandardConstructors, environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus}, error::JsNativeError, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PROTOTYPE}, + object::{ + internal_methods::get_prototype_from_constructor, + shape::{slot::Slot, Shape}, + JsObject, ObjectData, PROTOTYPE, + }, property::PropertyDescriptor, string::utf16, vm::CallFrame, @@ -20,7 +24,7 @@ use crate::{ }; 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_interner::Sym; use boa_profiler::Profiler; use std::{ @@ -93,6 +97,56 @@ unsafe impl Trace for CodeBlockFlags { empty_trace!(); } +/// An inline cache entry for a property access. +#[derive(Clone, Debug, Trace, Finalize)] +#[allow(dead_code)] +pub 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<Option<Shape>>, + + /// Cached shape ptr used to used to check if two shapes are the same. + /// + /// This is done to avoid calling `.borrow()` on [`GcRefCell`] and [`Option`] unwrapping. + #[unsafe_ignore_trace] + pub(crate) shape_ptr: Cell<usize>, + + /// 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(None), + shape_ptr: Cell::new(0), + slot: Cell::new(Slot::new()), + } + } + + pub(crate) fn set(&self, shape: Shape, slot: Slot) { + let shape_ptr = shape.to_addr_usize(); + { + let mut shape_borrowed = self.shape.borrow_mut(); + *shape_borrowed = Some(shape); + } + self.shape_ptr.set(shape_ptr); + self.slot.set(slot); + } + + pub(crate) fn slot(&self) -> Slot { + self.slot.get() + } + + pub(crate) fn matches(&self, shape: &Shape) -> bool { + self.shape_ptr.get() == shape.to_addr_usize() + } +} + /// The internal representation of a JavaScript function. /// /// A `CodeBlock` is generated for each function compiled by the @@ -118,7 +172,8 @@ pub struct CodeBlock { pub(crate) params: FormalParameterList, /// Bytecode - pub(crate) bytecode: Box<[u8]>, + #[unsafe_ignore_trace] + pub(crate) bytecode: Box<[Cell<u8>]>, /// Literals pub(crate) literals: Box<[JsValue]>, @@ -140,6 +195,9 @@ pub struct CodeBlock { // TODO(#3034): Maybe changing this to Gc after garbage collection would be better than Rc. #[unsafe_ignore_trace] pub(crate) compile_environments: Box<[Rc<RefCell<CompileTimeEnvironment>>]>, + + /// inline caching + pub(crate) ic: Box<[InlineCache]>, } /// ---- `CodeBlock` public API ---- @@ -161,6 +219,7 @@ impl CodeBlock { this_mode: ThisMode::Global, params: FormalParameterList::default(), compile_environments: Box::default(), + ic: Box::default(), } } @@ -269,7 +328,7 @@ impl CodeBlock { /// Returns an empty `String` if no operands are present. #[cfg(any(feature = "trace", feature = "flowgraph"))] pub(crate) fn instruction_operands(&self, pc: &mut usize, interner: &Interner) -> String { - let opcode: Opcode = self.bytecode[*pc].into(); + let opcode: Opcode = self.bytecode[*pc].get().into(); *pc += size_of::<Opcode>(); match opcode { Opcode::SetFunctionName => { @@ -395,9 +454,22 @@ impl CodeBlock { interner.resolve_expect(self.bindings[operand as usize].name().sym()), ) } - Opcode::GetPropertyByName - | Opcode::GetMethod - | Opcode::SetPropertyByName + Opcode::GetPropertyByName | Opcode::SetPropertyByName => { + let ic_index = self.read::<u32>(*pc); + *pc += size_of::<u32>(); + + let ic = &self.ic[ic_index as usize]; + let slot = ic.slot(); + format!( + "{:04}: '{}', Shape: 0x{:x}, Slot: index: {}, attributes {:?}", + ic_index, + ic.name.to_std_string_escaped(), + ic.shape_ptr.get(), + slot.index, + slot.attributes, + ) + } + Opcode::GetMethod | Opcode::DefineOwnPropertyByName | Opcode::DefineClassStaticMethodByName | Opcode::DefineClassMethodByName @@ -643,7 +715,7 @@ impl ToInternedString for CodeBlock { let mut pc = 0; let mut count = 0; while pc < self.bytecode.len() { - let opcode: Opcode = self.bytecode[pc].into(); + let opcode: Opcode = self.bytecode[pc].get().into(); let opcode = opcode.as_str(); let previous_pc = pc; let operands = self.instruction_operands(&mut pc, interner); diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index 6e632d66bb1..24c942cb2c5 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -32,7 +32,7 @@ impl CodeBlock { let mut pc = 0; while pc < self.bytecode.len() { - let opcode: Opcode = self.bytecode[pc].into(); + let opcode: Opcode = self.bytecode[pc].get().into(); let opcode_str = opcode.as_str(); let previous_pc = pc; @@ -447,8 +447,16 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Opcode::GetPropertyByName - | Opcode::GetMethod + Opcode::GetPropertyByName => { + let ic_index = self.read::<u32>(pc); + pc += size_of::<u32>(); + + let ic = &self.ic[ic_index as usize]; + let label = format!("{opcode_str} '{}'", ic.name.to_std_string_escaped()); + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); + graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); + } + Opcode::GetMethod | Opcode::SetPropertyByName | Opcode::DefineOwnPropertyByName | Opcode::DefineClassStaticMethodByName diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 27f81aaced3..ffc2305dc11 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -34,7 +34,11 @@ mod runtime_limits; pub mod flowgraph; pub use runtime_limits::RuntimeLimits; -pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; +pub use { + call_frame::CallFrame, + code_block::{CodeBlock, InlineCache}, + opcode::Opcode, +}; pub(crate) use { call_frame::GeneratorResumeKind, @@ -164,7 +168,7 @@ impl Context<'_> { let frame = self.vm.frame_mut(); let pc = frame.pc; - let opcode = Opcode::from(frame.code_block.bytecode[pc as usize]); + let opcode = Opcode::from(frame.code_block.bytecode[pc as usize].get()); frame.pc += 1; opcode }; diff --git a/boa_engine/src/vm/opcode/call/mod.rs b/boa_engine/src/vm/opcode/call/mod.rs index 4bdf52d7d94..70782af549f 100644 --- a/boa_engine/src/vm/opcode/call/mod.rs +++ b/boa_engine/src/vm/opcode/call/mod.rs @@ -2,7 +2,7 @@ use crate::{ builtins::{function::FunctionKind, promise::PromiseCapability, Promise}, error::JsNativeError, module::{ModuleKind, Referrer}, - object::FunctionObjectBuilder, + object::{internal_methods::InternalMethodContext, FunctionObjectBuilder}, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, NativeFunction, }; @@ -68,7 +68,8 @@ impl Operation for CallEval { context.vm.push(JsValue::Undefined); } } else { - let result = object.__call__(&this, &arguments, context)?; + let result = + object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?; context.vm.push(result); } Ok(CompletionType::Normal) @@ -142,7 +143,8 @@ impl Operation for CallEvalSpread { context.vm.push(JsValue::Undefined); } } else { - let result = object.__call__(&this, &arguments, context)?; + let result = + object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?; context.vm.push(result); } Ok(CompletionType::Normal) @@ -193,7 +195,8 @@ impl Operation for Call { } }; - let result = object.__call__(&this, &arguments, context)?; + let result = + object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?; context.vm.push(result); Ok(CompletionType::Normal) @@ -246,7 +249,8 @@ impl Operation for CallSpread { } }; - let result = object.__call__(&this, &arguments, context)?; + let result = + object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?; context.vm.push(result); Ok(CompletionType::Normal) diff --git a/boa_engine/src/vm/opcode/define/class/getter.rs b/boa_engine/src/vm/opcode/define/class/getter.rs index 0766d7f4aff..7a9265baa77 100644 --- a/boa_engine/src/vm/opcode/define/class/getter.rs +++ b/boa_engine/src/vm/opcode/define/class/getter.rs @@ -1,6 +1,6 @@ use crate::{ builtins::function::set_function_name, - object::CONSTRUCTOR, + object::{internal_methods::InternalMethodContext, CONSTRUCTOR}, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, @@ -38,7 +38,7 @@ impl Operation for DefineClassStaticGetterByName { function_mut.set_class_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(); @@ -50,7 +50,7 @@ impl Operation for DefineClassStaticGetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -94,7 +94,7 @@ impl Operation for DefineClassGetterByName { function_mut.set_class_object(class); } let set = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -106,7 +106,7 @@ impl Operation for DefineClassGetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -143,8 +143,9 @@ impl Operation for DefineClassStaticGetterByValue { function_mut.set_home_object(class.clone()); function_mut.set_class_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(); @@ -200,7 +201,7 @@ impl Operation for DefineClassGetterByValue { function_mut.set_class_object(class); } let set = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -212,7 +213,7 @@ impl Operation for DefineClassGetterByValue { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/define/class/method.rs b/boa_engine/src/vm/opcode/define/class/method.rs index 7601892cef0..a951f2e8af4 100644 --- a/boa_engine/src/vm/opcode/define/class/method.rs +++ b/boa_engine/src/vm/opcode/define/class/method.rs @@ -1,6 +1,6 @@ use crate::{ builtins::function::set_function_name, - object::CONSTRUCTOR, + object::{internal_methods::InternalMethodContext, CONSTRUCTOR}, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, @@ -46,7 +46,7 @@ impl Operation for DefineClassStaticMethodByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -98,7 +98,7 @@ impl Operation for DefineClassMethodByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -196,7 +196,7 @@ impl Operation for DefineClassMethodByValue { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/define/class/setter.rs b/boa_engine/src/vm/opcode/define/class/setter.rs index 43f605a9c64..5a4a39ec0c6 100644 --- a/boa_engine/src/vm/opcode/define/class/setter.rs +++ b/boa_engine/src/vm/opcode/define/class/setter.rs @@ -1,6 +1,6 @@ use crate::{ builtins::function::set_function_name, - object::CONSTRUCTOR, + object::{internal_methods::InternalMethodContext, CONSTRUCTOR}, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, @@ -38,7 +38,7 @@ impl Operation for DefineClassStaticSetterByName { function_mut.set_class_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(); @@ -51,7 +51,7 @@ impl Operation for DefineClassStaticSetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -95,7 +95,7 @@ impl Operation for DefineClassSetterByName { function_mut.set_class_object(class); } let get = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -108,7 +108,7 @@ impl Operation for DefineClassSetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) @@ -147,7 +147,7 @@ impl Operation for DefineClassStaticSetterByValue { function_mut.set_class_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(); @@ -205,7 +205,7 @@ impl Operation for DefineClassSetterByValue { function_mut.set_class_object(class); } let get = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -218,7 +218,7 @@ impl Operation for DefineClassSetterByValue { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) diff --git a/boa_engine/src/vm/opcode/define/own_property.rs b/boa_engine/src/vm/opcode/define/own_property.rs index 30de98a2a48..d1981ad7264 100644 --- a/boa_engine/src/vm/opcode/define/own_property.rs +++ b/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, @@ -33,7 +34,7 @@ impl Operation for DefineOwnPropertyByName { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -68,7 +69,7 @@ impl Operation for DefineOwnPropertyByValue { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; if !success { return Err(JsNativeError::typ() diff --git a/boa_engine/src/vm/opcode/delete/mod.rs b/boa_engine/src/vm/opcode/delete/mod.rs index 22129fb35ba..fcac98b8d6a 100644 --- a/boa_engine/src/vm/opcode/delete/mod.rs +++ b/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, }; @@ -22,8 +23,9 @@ impl Operation for DeletePropertyByName { let key = context.vm.frame().code_block.names[index as usize] .clone() .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()); @@ -49,8 +51,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()); diff --git a/boa_engine/src/vm/opcode/environment/mod.rs b/boa_engine/src/vm/opcode/environment/mod.rs index a0873ea91c2..f562b120f39 100644 --- a/boa_engine/src/vm/opcode/environment/mod.rs +++ b/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, }; @@ -52,7 +53,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); @@ -87,7 +88,7 @@ impl Operation for SuperCallPrepare { .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"); if let Some(constructor) = super_constructor { @@ -133,7 +134,11 @@ impl Operation for SuperCall { .into()); }; - let result = super_constructor.__construct__(&arguments, new_target, context)?; + let result = super_constructor.__construct__( + &arguments, + new_target, + &mut InternalMethodContext::new(context), + )?; let this_env = context .vm @@ -189,7 +194,11 @@ impl Operation for SuperCallSpread { .into()); }; - let result = super_constructor.__construct__(&arguments, new_target, context)?; + let result = super_constructor.__construct__( + &arguments, + new_target, + &mut InternalMethodContext::new(context), + )?; let this_env = context .vm @@ -239,7 +248,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"); @@ -249,7 +258,11 @@ impl Operation for SuperCallDerived { .into()); } - let result = super_constructor.__construct__(&arguments, &new_target, context)?; + let result = super_constructor.__construct__( + &arguments, + &new_target, + &mut InternalMethodContext::new(context), + )?; let this_env = context .vm diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs index 5028b343cfb..70f410b9147 100644 --- a/boa_engine/src/vm/opcode/get/property.rs +++ b/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, JsValue, @@ -16,7 +17,7 @@ impl Operation for GetPropertyByName { const INSTRUCTION: &'static str = "INST - GetPropertyByName"; fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { - let index = context.vm.read::<u32>(); + let ic_index = context.vm.read::<u32>() as usize; let receiver = context.vm.pop(); let value = context.vm.pop(); @@ -26,11 +27,66 @@ impl Operation for GetPropertyByName { value.to_object(context)? }; - let key = context.vm.frame().code_block.names[index as usize] - .clone() - .into(); + let ic = &context.vm.frame().code_block().ic[ic_index]; + let mut slot = ic.slot(); + if slot.is_cachable() { + let object_borrowed = object.borrow(); + if ic.matches(object_borrowed.shape()) { + // println!( + // "GET: T: \"{}\" {}: {:?}", + // ic.name.to_std_string_escaped(), + // slot.index, + // slot.attributes + // ); + 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(); + + // println!( + // "GET: F: \"{}\" {}: {:?} - {}", + // key, + // slot.index, + // slot.attributes, + // result.type_of() + // ); + + // Cache the property. + if slot.attributes.is_cachable() { + let ic = &context.vm.frame().code_block.ic[ic_index]; + let object_borrowed = object.borrow(); + let shape = object_borrowed.shape().clone(); + ic.set(shape, slot); + } + context.vm.push(result); Ok(CompletionType::Normal) } @@ -75,7 +131,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) @@ -147,7 +203,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); diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index 44622af89ea..fe8e8768743 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -727,7 +727,7 @@ generate_impl! { /// /// Like `object.name` /// - /// Operands: name_index: `u32` + /// Operands: ic_index: `u32` /// /// Stack: object, receiver **=>** value GetPropertyByName, @@ -762,7 +762,7 @@ generate_impl! { /// /// Like `object.name = value` /// - /// Operands: name_index: `u32` + /// Operands: ic_index: `u32` /// /// Stack: object, receiver, value **=>** value SetPropertyByName, diff --git a/boa_engine/src/vm/opcode/new/mod.rs b/boa_engine/src/vm/opcode/new/mod.rs index 8e6c78a69a4..9cf9dfbc925 100644 --- a/boa_engine/src/vm/opcode/new/mod.rs +++ b/boa_engine/src/vm/opcode/new/mod.rs @@ -1,5 +1,6 @@ use crate::{ error::JsNativeError, + object::internal_methods::InternalMethodContext, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -44,7 +45,9 @@ impl Operation for New { .with_message("not a constructor") .into() }) - .and_then(|cons| cons.__construct__(&arguments, cons, context))?; + .and_then(|cons| { + cons.__construct__(&arguments, cons, &mut InternalMethodContext::new(context)) + })?; context.vm.push(result); Ok(CompletionType::Normal) @@ -97,7 +100,9 @@ impl Operation for NewSpread { .with_message("not a constructor") .into() }) - .and_then(|cons| cons.__construct__(&arguments, cons, context))?; + .and_then(|cons| { + cons.__construct__(&arguments, cons, &mut InternalMethodContext::new(context)) + })?; context.vm.push(result); Ok(CompletionType::Normal) diff --git a/boa_engine/src/vm/opcode/push/class/private.rs b/boa_engine/src/vm/opcode/push/class/private.rs index 31797f39fcb..474481295be 100644 --- a/boa_engine/src/vm/opcode/push/class/private.rs +++ b/boa_engine/src/vm/opcode/push/class/private.rs @@ -1,5 +1,5 @@ use crate::{ - object::PrivateElement, + object::{internal_methods::InternalMethodContext, PrivateElement}, property::PropertyDescriptor, string::utf16, vm::{opcode::Operation, CompletionType}, @@ -31,7 +31,11 @@ impl Operation for 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(); diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index d647e7e8ae8..ce7a29b00bd 100644 --- a/boa_engine/src/vm/opcode/set/class_prototype.rs +++ b/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, @@ -61,7 +63,7 @@ impl Operation for SetClassPrototype { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), ) .expect("cannot fail per spec"); diff --git a/boa_engine/src/vm/opcode/set/private.rs b/boa_engine/src/vm/opcode/set/private.rs index b5bfe61d0c8..749a3b85fc2 100644 --- a/boa_engine/src/vm/opcode/set/private.rs +++ b/boa_engine/src/vm/opcode/set/private.rs @@ -1,5 +1,5 @@ use crate::{ - object::PrivateElement, + object::{internal_methods::InternalMethodContext, PrivateElement}, property::PropertyDescriptor, string::utf16, vm::{opcode::Operation, CompletionType}, @@ -89,7 +89,11 @@ impl Operation for SetPrivateMethod { .configurable(true) .build(); value - .__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 object = context.vm.pop(); diff --git a/boa_engine/src/vm/opcode/set/property.rs b/boa_engine/src/vm/opcode/set/property.rs index 245f3184619..606c9bca131 100644 --- a/boa_engine/src/vm/opcode/set/property.rs +++ b/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, @@ -19,7 +20,7 @@ impl Operation for SetPropertyByName { const INSTRUCTION: &'static str = "INST - SetPropertyByName"; fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { - let index = context.vm.read::<u32>(); + let ic_index = context.vm.read::<u32>() as usize; let value = context.vm.pop(); let receiver = context.vm.pop(); @@ -30,16 +31,81 @@ impl Operation for SetPropertyByName { object.to_object(context)? }; - let name: PropertyKey = context.vm.frame().code_block.names[index as usize] - .clone() - .into(); + let ic = &context.vm.frame().code_block().ic[ic_index]; + let mut slot = ic.slot(); + if slot.is_cachable() { + let object_borrowed = object.borrow(); + if ic.matches(object_borrowed.shape()) { + // println!( + // "SET: T: \"{}\" {}: {:?}", + // ic.name.to_std_string_escaped(), + // slot.index, + // slot.attributes + // ); + 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(); + + // println!("SET: F: \"{}\" {}: {:?}", name, slot.index, slot.attributes); + + // Cache the property. + if succeeded && slot.is_cachable() { + let ic = &context.vm.frame().code_block.ic[ic_index]; + let object_borrowed = object.borrow(); + let shape = object_borrowed.shape().clone(); + ic.set(shape, slot); + } context.vm.stack.push(value); Ok(CompletionType::Normal) } @@ -138,7 +204,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}")) @@ -169,7 +236,7 @@ impl Operation for SetPropertyGetterByName { .clone() .into(); let set = object - .__get_own_property__(&name, context)? + .__get_own_property__(&name, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -181,7 +248,7 @@ impl Operation for SetPropertyGetterByName { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -204,8 +271,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(); @@ -217,7 +285,7 @@ impl Operation for SetPropertyGetterByValue { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -242,8 +310,9 @@ impl Operation for SetPropertySetterByName { let name = context.vm.frame().code_block.names[index as usize] .clone() .into(); + let get = object - .__get_own_property__(&name, context)? + .__get_own_property__(&name, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -255,7 +324,7 @@ impl Operation for SetPropertySetterByName { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -278,8 +347,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(); @@ -291,7 +361,7 @@ impl Operation for SetPropertySetterByValue { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/set/prototype.rs b/boa_engine/src/vm/opcode/set/prototype.rs index 733410e9e3e..4360834b39b 100644 --- a/boa_engine/src/vm/opcode/set/prototype.rs +++ b/boa_engine/src/vm/opcode/set/prototype.rs @@ -1,4 +1,5 @@ use crate::{ + object::internal_methods::InternalMethodContext, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -28,7 +29,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)