diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index c54ef3e4488..d212f8e2136 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, string::common::StaticJsStrings, @@ -349,7 +352,7 @@ impl Array { .enumerable(false) .configurable(false) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(array) diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs index 7a4cf494fed..d28f663a052 100644 --- a/boa_engine/src/builtins/function/tests.rs +++ b/boa_engine/src/builtins/function/tests.rs @@ -153,7 +153,10 @@ fn closure_capture_clone() { let hw = js_string!( string, &object - .__get_own_property__(&js_string!("key").into(), context)? + .__get_own_property__( + &js_string!("key").into(), + &mut context.into() + )? .and_then(|prop| prop.value().cloned()) .and_then(|val| val.as_string().cloned()) .ok_or_else( diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 5902987a6e5..26b4cd3fae4 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::{common::StaticJsStrings, utf16, CodePoint}, @@ -198,7 +198,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 { @@ -227,7 +227,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 f0098d19feb..8e36baf05b9 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -13,7 +13,7 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::{internal_methods::InternalMethodContext, JsObject, ObjectData}, property::PropertyKey, realm::Realm, Context, JsResult, JsString, JsValue, @@ -106,7 +106,8 @@ impl ForInIterator { let mut object = iterator.object.to_object(context)?; loop { if !iterator.object_was_visited { - let keys = object.__own_property_keys__(context)?; + let keys = + object.__own_property_keys__(&mut InternalMethodContext::new(context))?; for k in keys { match k { PropertyKey::String(ref k) => { @@ -124,9 +125,10 @@ impl ForInIterator { } while let Some(r) = iterator.remaining_keys.pop_front() { if !iterator.visited_keys.contains(&r) { - if let Some(desc) = - object.__get_own_property__(&PropertyKey::from(r.clone()), context)? - { + if let Some(desc) = object.__get_own_property__( + &PropertyKey::from(r.clone()), + &mut InternalMethodContext::new(context), + )? { iterator.visited_keys.insert(r.clone()); if desc.expect_enumerable() { return Ok(create_iter_result_object(JsValue::new(r), false, context)); diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 86560efa7e5..a4951a4a4ab 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, @@ -211,7 +211,7 @@ impl Object { let obj = this.to_object(context)?; // 2. Return ? O.[[GetPrototypeOf]](). - let proto = obj.__get_prototype_of__(context)?; + let proto = obj.__get_prototype_of__(&mut InternalMethodContext::new(context))?; Ok(proto.map_or(JsValue::Null, JsValue::new)) } @@ -248,7 +248,8 @@ impl Object { }; // 4. Let status be ? O.[[SetPrototypeOf]](proto). - let status = object.__set_prototype_of__(proto, context)?; + let status = + object.__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?; // 5. If status is false, throw a TypeError exception. if !status { @@ -371,7 +372,8 @@ impl Object { // 3. Repeat loop { // a. Let desc be ? O.[[GetOwnProperty]](key). - let desc = obj.__get_own_property__(&key, context)?; + + let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?; // b. If desc is not undefined, then if let Some(current_desc) = desc { @@ -383,7 +385,7 @@ impl Object { Ok(JsValue::undefined()) }; } - match obj.__get_prototype_of__(context)? { + match obj.__get_prototype_of__(&mut InternalMethodContext::new(context))? { // c. Set O to ? O.[[GetPrototypeOf]](). Some(o) => obj = o, // d. If O is null, return undefined. @@ -415,7 +417,8 @@ impl Object { // 3. Repeat loop { // a. Let desc be ? O.[[GetOwnProperty]](key). - let desc = obj.__get_own_property__(&key, context)?; + + let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?; // b. If desc is not undefined, then if let Some(current_desc) = desc { @@ -427,7 +430,7 @@ impl Object { Ok(JsValue::undefined()) }; } - match obj.__get_prototype_of__(context)? { + match obj.__get_prototype_of__(&mut InternalMethodContext::new(context))? { // c. Set O to ? O.[[GetPrototypeOf]](). Some(o) => obj = o, // d. If O is null, return undefined. @@ -496,7 +499,8 @@ impl Object { let key = args.get_or_undefined(1).to_property_key(context)?; // 3. Let desc be ? obj.[[GetOwnProperty]](key). - let desc = obj.__get_own_property__(&key, context)?; + + let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?; // 4. Return FromPropertyDescriptor(desc). Ok(Self::from_property_descriptor(desc, context)) @@ -521,7 +525,7 @@ impl Object { let obj = args.get_or_undefined(0).to_object(context)?; // 2. Let ownKeys be ? obj.[[OwnPropertyKeys]](). - let own_keys = obj.__own_property_keys__(context)?; + let own_keys = obj.__own_property_keys__(&mut InternalMethodContext::new(context))?; // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%). let descriptors = JsObject::with_object_proto(context.intrinsics()); @@ -529,7 +533,8 @@ impl Object { // 4. For each element key of ownKeys, do for key in own_keys { // a. Let desc be ? obj.[[GetOwnProperty]](key). - let desc = obj.__get_own_property__(&key, context)?; + + let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?; // b. Let descriptor be FromPropertyDescriptor(desc). let descriptor = Self::from_property_descriptor(desc, context); @@ -642,7 +647,7 @@ impl Object { // 2. Return ? obj.[[GetPrototypeOf]](). Ok(obj - .__get_prototype_of__(context)? + .__get_prototype_of__(&mut InternalMethodContext::new(context))? .map_or(JsValue::Null, JsValue::new)) } @@ -693,7 +698,7 @@ impl Object { }; // 4. Let status be ? O.[[SetPrototypeOf]](proto). - let status = obj.__set_prototype_of__(proto, context)?; + let status = obj.__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?; // 5. If status is false, throw a TypeError exception. if !status { @@ -931,9 +936,10 @@ impl Object { }; let key = key.to_property_key(context)?; + let own_prop = this .to_object(context)? - .__get_own_property__(&key, context)?; + .__get_own_property__(&key, &mut InternalMethodContext::new(context))?; own_prop .as_ref() @@ -972,11 +978,14 @@ impl Object { .to_object(context) .expect("this ToObject call must not fail"); // 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]](). - let keys = from.__own_property_keys__(context)?; + let keys = from.__own_property_keys__(&mut InternalMethodContext::new(context))?; // 3.a.iii. For each element nextKey of keys, do for key in keys { // 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey). - if let Some(desc) = from.__get_own_property__(&key, context)? { + + if let Some(desc) = + from.__get_own_property__(&key, &mut InternalMethodContext::new(context))? + { // 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then if desc.expect_enumerable() { // 3.a.iii.2.a. Let propValue be ? Get(from, nextKey). @@ -1187,7 +1196,7 @@ impl Object { if let Some(o) = o.as_object() { // 2. Let status be ? O.[[PreventExtensions]](). - let status = o.__prevent_extensions__(context)?; + let status = o.__prevent_extensions__(&mut InternalMethodContext::new(context))?; // 3. If status is false, throw a TypeError exception. if !status { return Err(JsNativeError::typ() @@ -1444,7 +1453,7 @@ fn object_define_properties( let props = &props.to_object(context)?; // 3. Let keys be ? props.[[OwnPropertyKeys]](). - let keys = props.__own_property_keys__(context)?; + let keys = props.__own_property_keys__(&mut InternalMethodContext::new(context))?; // 4. Let descriptors be a new empty List. let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new(); @@ -1453,7 +1462,10 @@ fn object_define_properties( for next_key in keys { // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey). // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then - if let Some(prop_desc) = props.__get_own_property__(&next_key, context)? { + + if let Some(prop_desc) = + props.__get_own_property__(&next_key, &mut InternalMethodContext::new(context))? + { if prop_desc.expect_enumerable() { // i. Let descObj be ? Get(props, nextKey). let desc_obj = props.get(next_key.clone(), context)?; @@ -1501,7 +1513,7 @@ fn get_own_property_keys( let obj = o.to_object(context)?; // 2. Let keys be ? obj.[[OwnPropertyKeys]](). - let keys = obj.__own_property_keys__(context)?; + let keys = obj.__own_property_keys__(&mut InternalMethodContext::new(context))?; // 3. Let nameList be a new empty List. // 4. For each element nextKey of keys, do diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index 50e9e8196fb..cc470ef1a00 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -16,7 +16,7 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::JsObject, + object::{internal_methods::InternalMethodContext, JsObject}, property::Attribute, realm::Realm, string::common::StaticJsStrings, @@ -166,7 +166,11 @@ impl Reflect { .into(); target - .__define_own_property__(&key, prop_desc.to_property_descriptor(context)?, context) + .__define_own_property__( + &key, + prop_desc.to_property_descriptor(context)?, + &mut InternalMethodContext::new(context), + ) .map(Into::into) } @@ -189,7 +193,9 @@ impl Reflect { .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let key = args.get_or_undefined(1).to_property_key(context)?; - Ok(target.__delete__(&key, context)?.into()) + Ok(target + .__delete__(&key, &mut InternalMethodContext::new(context))? + .into()) } /// Gets a property of an object. @@ -215,7 +221,8 @@ impl Reflect { .cloned() .unwrap_or_else(|| target.clone().into()); // 4. Return ? target.[[Get]](key, receiver). - target.__get__(&key, receiver, context) + + target.__get__(&key, receiver, &mut InternalMethodContext::new(context)) } /// Gets a property of an object. @@ -264,7 +271,7 @@ impl Reflect { .and_then(JsValue::as_object) .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; Ok(target - .__get_prototype_of__(context)? + .__get_prototype_of__(&mut InternalMethodContext::new(context))? .map_or(JsValue::Null, JsValue::new)) } @@ -285,7 +292,10 @@ impl Reflect { .get(1) .unwrap_or(&JsValue::undefined()) .to_property_key(context)?; - Ok(target.__has_property__(&key, context)?.into()) + + Ok(target + .__has_property__(&key, &mut InternalMethodContext::new(context))? + .into()) } /// Returns `true` if the object is extensible, `false` otherwise. @@ -305,7 +315,9 @@ impl Reflect { .get(0) .and_then(JsValue::as_object) .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; - Ok(target.__is_extensible__(context)?.into()) + Ok(target + .__is_extensible__(&mut InternalMethodContext::new(context))? + .into()) } /// Returns an array of object own property keys. @@ -327,7 +339,7 @@ impl Reflect { .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; let keys: Vec = target - .__own_property_keys__(context)? + .__own_property_keys__(&mut InternalMethodContext::new(context))? .into_iter() .map(Into::into) .collect(); @@ -353,7 +365,9 @@ impl Reflect { .and_then(JsValue::as_object) .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?; - Ok(target.__prevent_extensions__(context)?.into()) + Ok(target + .__prevent_extensions__(&mut InternalMethodContext::new(context))? + .into()) } /// Sets a property of an object. @@ -375,8 +389,14 @@ impl Reflect { .get(3) .cloned() .unwrap_or_else(|| target.clone().into()); + Ok(target - .__set__(key, value.clone(), receiver, context)? + .__set__( + key, + value.clone(), + receiver, + &mut InternalMethodContext::new(context), + )? .into()) } @@ -406,6 +426,8 @@ impl Reflect { .into()) } }; - Ok(target.__set_prototype_of__(proto, context)?.into()) + Ok(target + .__set_prototype_of__(proto, &mut InternalMethodContext::new(context))? + .into()) } } diff --git a/boa_engine/src/builtins/temporal/mod.rs b/boa_engine/src/builtins/temporal/mod.rs index ecc28d60199..8e1cd41a16d 100644 --- a/boa_engine/src/builtins/temporal/mod.rs +++ b/boa_engine/src/builtins/temporal/mod.rs @@ -600,7 +600,7 @@ pub(crate) fn copy_data_properties( // c. If excluded is false, then if !excluded { // i. Let desc be ? from.[[GetOwnProperty]](nextKey). - let desc = from.__get_own_property__(&next_key, context)?; + let desc = from.__get_own_property__(&next_key, &mut context.into())?; // ii. If desc is not undefined and desc.[[Enumerable]] is true, then match desc { Some(d) diff --git a/boa_engine/src/builtins/typed_array/builtin.rs b/boa_engine/src/builtins/typed_array/builtin.rs index 27b93867459..1999af2d93e 100644 --- a/boa_engine/src/builtins/typed_array/builtin.rs +++ b/boa_engine/src/builtins/typed_array/builtin.rs @@ -2262,7 +2262,7 @@ impl BuiltinTypedArray { let target_index = target_offset + k; // d. Perform ? IntegerIndexedElementSet(target, targetIndex, value). - integer_indexed_element_set(target, target_index as f64, &value, context)?; + integer_indexed_element_set(target, target_index as f64, &value, &mut context.into())?; // e. Set k to k + 1. } diff --git a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs index 5200f3982d3..f0b058ceeec 100644 --- a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs +++ b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs @@ -39,11 +39,7 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit_with_varying_operand( - Opcode::GetPropertyByName, - index, - ); + self.emit_get_property_by_name(*name); } PropertyName::Computed(node) => { self.compile_expr(node, true); @@ -123,11 +119,7 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit_with_varying_operand( - Opcode::GetPropertyByName, - index, - ); + self.emit_get_property_by_name(*name); } PropertyName::Computed(node) => { self.compile_expr(node, true); @@ -166,11 +158,7 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Dup); match name { PropertyName::Literal(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit_with_varying_operand( - Opcode::GetPropertyByName, - index, - ); + self.emit_get_property_by_name(*name); } PropertyName::Computed(node) => { self.compile_expr(node, true); diff --git a/boa_engine/src/bytecompiler/expression/assign.rs b/boa_engine/src/bytecompiler/expression/assign.rs index 6b45d824bfe..8b29747bfac 100644 --- a/boa_engine/src/bytecompiler/expression/assign.rs +++ b/boa_engine/src/bytecompiler/expression/assign.rs @@ -95,13 +95,12 @@ impl ByteCompiler<'_> { Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); self.compile_expr(access.target(), true); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*name); if short_circuit { pop_count = 2; early_exit = Some(self.emit_opcode_with_operand(opcode)); @@ -111,7 +110,7 @@ impl ByteCompiler<'_> { self.emit_opcode(opcode); } - self.emit_with_varying_operand(Opcode::SetPropertyByName, index); + self.emit_set_property_by_name(*name); if !use_expr { self.emit_opcode(Opcode::Pop); } @@ -161,14 +160,13 @@ impl ByteCompiler<'_> { } PropertyAccess::Super(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); self.emit_opcode(Opcode::Super); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::This); self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::This); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*name); if short_circuit { pop_count = 2; early_exit = Some(self.emit_opcode_with_operand(opcode)); @@ -178,7 +176,7 @@ impl ByteCompiler<'_> { self.emit_opcode(opcode); } - self.emit_with_varying_operand(Opcode::SetPropertyByName, index); + self.emit_set_property_by_name(*name); if !use_expr { self.emit_opcode(Opcode::Pop); } diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index dff1fe07af0..27a226874f1 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -239,8 +239,7 @@ impl ByteCompiler<'_> { self.emit_opcode(Opcode::Dup); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(field) => { self.compile_expr(field, true); diff --git a/boa_engine/src/bytecompiler/expression/update.rs b/boa_engine/src/bytecompiler/expression/update.rs index d71f2ddaaa3..6d62de8f257 100644 --- a/boa_engine/src/bytecompiler/expression/update.rs +++ b/boa_engine/src/bytecompiler/expression/update.rs @@ -60,19 +60,18 @@ impl ByteCompiler<'_> { Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); self.compile_expr(access.target(), true); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*name); self.emit_opcode(opcode); if post { self.emit(Opcode::RotateRight, &[Operand::U8(4)]); } - self.emit_with_varying_operand(Opcode::SetPropertyByName, index); + self.emit_set_property_by_name(*name); if post { self.emit_opcode(Opcode::Pop); } @@ -114,20 +113,19 @@ impl ByteCompiler<'_> { } PropertyAccess::Super(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); self.emit_opcode(Opcode::Super); self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::This); self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::This); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*name); self.emit_opcode(opcode); if post { self.emit(Opcode::RotateRight, &[Operand::U8(3)]); } - self.emit_with_varying_operand(Opcode::SetPropertyByName, index); + self.emit_set_property_by_name(*name); if post { self.emit_opcode(Opcode::Pop); } diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 9851852f4e8..834ce5481bd 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -18,8 +18,8 @@ use crate::{ environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment}, js_string, vm::{ - BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler, Opcode, - VaryingOperandKind, + BindingOpcode, CodeBlock, CodeBlockFlags, Constant, GeneratorResumeKind, Handler, + InlineCache, Opcode, VaryingOperandKind, }, Context, JsBigInt, JsString, }; @@ -263,6 +263,7 @@ pub struct ByteCompiler<'ctx> { current_stack_value_count: u32, pub(crate) code_block_flags: CodeBlockFlags, handlers: ThinVec, + pub(crate) ic: Vec, literals_map: FxHashMap, names_map: FxHashMap, bindings_map: FxHashMap, @@ -314,6 +315,7 @@ impl<'ctx> ByteCompiler<'ctx> { current_stack_value_count: 2, code_block_flags, handlers: ThinVec::default(), + ic: Vec::default(), literals_map: FxHashMap::default(), names_map: FxHashMap::default(), @@ -516,6 +518,29 @@ impl<'ctx> ByteCompiler<'ctx> { fn emit_i64(&mut self, value: i64) { self.emit_u64(value as u64); } + fn emit_get_property_by_name(&mut self, ident: Sym) { + let ic_index = self.ic.len() as u32; + + let name_index = self.get_or_insert_name(Identifier::new(ident)); + let Constant::String(ref name) = self.constants[name_index as usize].clone() else { + unreachable!("there should be a string at index") + }; + self.ic.push(InlineCache::new(name.clone())); + + self.emit_with_varying_operand(Opcode::GetPropertyByName, ic_index); + } + + fn emit_set_property_by_name(&mut self, ident: Sym) { + let ic_index = self.ic.len() as u32; + + let name_index = self.get_or_insert_name(Identifier::new(ident)); + let Constant::String(ref name) = self.constants[name_index as usize].clone() else { + unreachable!("there should be a string at index") + }; + self.ic.push(InlineCache::new(name.clone())); + + self.emit_with_varying_operand(Opcode::SetPropertyByName, ic_index); + } fn emit_u64(&mut self, value: u64) { self.bytecode.extend(value.to_ne_bytes()); @@ -674,7 +699,7 @@ impl<'ctx> ByteCompiler<'ctx> { // This is done to avoid unneeded bounds checks. assert!(self.bytecode.len() > index + U32_SIZE && usize::MAX - U32_SIZE >= index); - self.bytecode[index + 1..=index + U32_SIZE].copy_from_slice(bytes.as_slice()); + self.bytecode[index + 1..=index + U32_SIZE].clone_from_slice(bytes.as_slice()); } fn patch_jump(&mut self, label: Label) { @@ -692,10 +717,9 @@ impl<'ctx> ByteCompiler<'ctx> { Access::Property { access } => match access { PropertyAccess::Simple(access) => match access.field() { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); self.compile_expr(access.target(), true); self.emit_opcode(Opcode::Dup); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*name); } PropertyAccessField::Expr(expr) => { self.compile_expr(access.target(), true); @@ -711,10 +735,10 @@ impl<'ctx> ByteCompiler<'ctx> { } PropertyAccess::Super(access) => match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); self.emit_opcode(Opcode::Super); self.emit_opcode(Opcode::This); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(expr) => { self.emit_opcode(Opcode::Super); @@ -786,9 +810,8 @@ impl<'ctx> ByteCompiler<'ctx> { self.compile_expr(access.target(), true); self.emit_opcode(Opcode::Dup); expr_fn(self, 2); - let index = self.get_or_insert_name((*name).into()); - self.emit_with_varying_operand(Opcode::SetPropertyByName, index); + self.emit_set_property_by_name(*name); if !use_expr { self.emit_opcode(Opcode::Pop); } @@ -818,8 +841,7 @@ impl<'ctx> ByteCompiler<'ctx> { self.emit_opcode(Opcode::Super); self.emit_opcode(Opcode::This); expr_fn(self, 1); - let index = self.get_or_insert_name((*name).into()); - self.emit_with_varying_operand(Opcode::SetPropertyByName, index); + self.emit_set_property_by_name(*name); if !use_expr { self.emit_opcode(Opcode::Pop); } @@ -925,8 +947,7 @@ impl<'ctx> ByteCompiler<'ctx> { self.emit_opcode(Opcode::Dup); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(field) => { self.compile_expr(field, true); @@ -946,8 +967,7 @@ impl<'ctx> ByteCompiler<'ctx> { self.emit_opcode(Opcode::This); match access.field() { PropertyAccessField::Const(field) => { - let index = self.get_or_insert_name((*field).into()); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*field); } PropertyAccessField::Expr(expr) => { self.compile_expr(expr, true); @@ -1032,8 +1052,7 @@ impl<'ctx> ByteCompiler<'ctx> { self.emit_opcode(Opcode::Dup); match field { PropertyAccessField::Const(name) => { - let index = self.get_or_insert_name((*name).into()); - self.emit_with_varying_operand(Opcode::GetPropertyByName, index); + self.emit_get_property_by_name(*name); } PropertyAccessField::Expr(expr) => { self.compile_expr(expr, true); @@ -1468,6 +1487,7 @@ impl<'ctx> ByteCompiler<'ctx> { bindings: self.bindings.into_boxed_slice(), handlers: self.handlers, flags: Cell::new(self.code_block_flags), + ic: self.ic.into_boxed_slice(), } } diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index c27418c4cf4..832eea0cc1c 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -21,7 +21,9 @@ use crate::{ js_string, 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, @@ -616,7 +618,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 { @@ -725,7 +729,8 @@ impl Context { let global_object = self.realm().global_object().clone(); // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). - let existing_prop = global_object.__get_own_property__(&name.clone().into(), self)?; + let existing_prop = + global_object.__get_own_property__(&name.clone().into(), &mut self.into())?; // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then let desc = if existing_prop.is_none() @@ -772,7 +777,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 d7b5c53412f..893c69852fd 100644 --- a/boa_engine/src/environments/runtime/mod.rs +++ b/boa_engine/src/environments/runtime/mod.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use crate::{ environments::CompileTimeEnvironment, - object::{JsObject, PrivateName}, + object::{internal_methods::InternalMethodContext, JsObject, PrivateName}, Context, JsResult, JsString, JsSymbol, JsValue, }; use boa_ast::expression::Identifier; @@ -655,7 +655,8 @@ impl Context { .interner() .resolve_expect(locator.name().sym()) .into_common::(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), @@ -666,7 +667,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 7082e65a69c..252e80c45e3 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> { // 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 { // 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 { 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 { // 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 { // 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 b0759dc1dbe..8b588743a9f 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 { // 1. Assert: IsPropertyKey(P) is true. match key { @@ -111,7 +111,7 @@ pub(crate) fn array_exotic_define_own_property( fn array_set_length( obj: &JsObject, desc: PropertyDescriptor, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { // 1. If Desc.[[Value]] is absent, then let Some(new_len_val) = desc.value() else { diff --git a/boa_engine/src/object/internal_methods/integer_indexed.rs b/boa_engine/src/object/internal_methods/integer_indexed.rs index 10353c9b566..467087ace75 100644 --- a/boa_engine/src/object/internal_methods/integer_indexed.rs +++ b/boa_engine/src/object/internal_methods/integer_indexed.rs @@ -9,7 +9,7 @@ use crate::{ Context, JsResult, JsString, JsValue, }; -use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; +use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; /// Definitions of the internal object methods for integer-indexed exotic objects. /// @@ -62,7 +62,7 @@ fn canonical_numeric_index_string(argument: &JsString) -> Option { pub(crate) fn integer_indexed_exotic_get_own_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult> { let p = match key { PropertyKey::String(key) => { @@ -104,7 +104,7 @@ pub(crate) fn integer_indexed_exotic_get_own_property( pub(crate) fn integer_indexed_exotic_has_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let p = match key { PropertyKey::String(key) => { @@ -135,7 +135,7 @@ pub(crate) fn integer_indexed_exotic_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let p = match key { PropertyKey::String(key) => { @@ -197,7 +197,7 @@ pub(crate) fn integer_indexed_exotic_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let p = match key { PropertyKey::String(key) => { @@ -230,7 +230,7 @@ pub(crate) fn integer_indexed_exotic_set( key: PropertyKey, value: JsValue, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let p = match &key { PropertyKey::String(key) => { @@ -272,7 +272,7 @@ pub(crate) fn integer_indexed_exotic_set( pub(crate) fn integer_indexed_exotic_delete( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let p = match &key { PropertyKey::String(key) => { @@ -388,7 +388,7 @@ pub(crate) fn integer_indexed_element_set( obj: &JsObject, index: f64, value: &JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult<()> { let obj_borrow = obj.borrow(); let inner = obj_borrow.as_typed_array().expect( diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index fa718a6025b..2c527b1a755 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, @@ -28,6 +33,52 @@ pub(super) mod string; pub(crate) use array::ARRAY_EXOTIC_INTERNAL_METHODS; pub(crate) use integer_indexed::integer_indexed_element_set; +/// A lightweight wrapper around [`Context`] used in [`InternalObjectMethods`]. +#[derive(Debug)] +pub(crate) struct InternalMethodContext<'ctx> { + context: &'ctx mut Context, + slot: Slot, +} + +impl<'ctx> InternalMethodContext<'ctx> { + /// Create a new [`InternalMethodContext`]. + pub(crate) fn new(context: &'ctx mut Context) -> Self { + Self { + context, + slot: Slot::new(), + } + } + + /// Gets the [`Slot`] associated with this [`InternalMethodContext`]. + #[inline] + pub(crate) fn slot(&mut self) -> &mut Slot { + &mut self.slot + } +} + +impl Deref for InternalMethodContext<'_> { + type Target = Context; + + #[inline] + fn deref(&self) -> &Self::Target { + self.context + } +} + +impl DerefMut for InternalMethodContext<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + self.context + } +} + +impl<'context> From<&'context mut Context> for InternalMethodContext<'context> { + #[inline] + fn from(context: &'context mut Context) -> Self { + Self::new(context) + } +} + impl JsObject { /// Internal method `[[GetPrototypeOf]]` /// @@ -97,7 +148,7 @@ impl JsObject { pub(crate) fn __get_own_property__( &self, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult> { let _timer = Profiler::global().start_event("Object::__get_own_property__", "object"); (self.vtable().__get_own_property__)(self, key, context) @@ -115,7 +166,7 @@ impl JsObject { &self, key: &PropertyKey, desc: PropertyDescriptor, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__define_own_property__", "object"); (self.vtable().__define_own_property__)(self, key, desc, context) @@ -132,7 +183,7 @@ impl JsObject { pub(crate) fn __has_property__( &self, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__has_property__", "object"); (self.vtable().__has_property__)(self, key, context) @@ -150,7 +201,7 @@ impl JsObject { &self, key: &PropertyKey, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__get__", "object"); (self.vtable().__get__)(self, key, receiver, context) @@ -169,7 +220,7 @@ impl JsObject { key: PropertyKey, value: JsValue, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::__set__", "object"); (self.vtable().__set__)(self, key, value, receiver, context) @@ -183,7 +234,11 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p - pub(crate) fn __delete__(&self, key: &PropertyKey, context: &mut Context) -> JsResult { + pub(crate) fn __delete__( + &self, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, + ) -> JsResult { let _timer = Profiler::global().start_event("Object::__delete__", "object"); (self.vtable().__delete__)(self, key, context) } @@ -215,6 +270,7 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist + #[track_caller] pub(crate) fn __call__(&self, argument_count: usize) -> CallValue { CallValue::Pending { func: self.vtable().__call__, @@ -233,6 +289,7 @@ impl JsObject { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget + #[track_caller] pub(crate) fn __construct__(&self, argument_count: usize) -> CallValue { CallValue::Pending { func: self.vtable().__construct__, @@ -283,19 +340,36 @@ pub(crate) struct InternalObjectMethods { pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &mut Context) -> JsResult, pub(crate) __is_extensible__: fn(&JsObject, &mut Context) -> JsResult, pub(crate) __prevent_extensions__: fn(&JsObject, &mut Context) -> JsResult, - pub(crate) __get_own_property__: - fn(&JsObject, &PropertyKey, &mut Context) -> JsResult>, - pub(crate) __define_own_property__: - fn(&JsObject, &PropertyKey, PropertyDescriptor, &mut Context) -> JsResult, - pub(crate) __has_property__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult, - pub(crate) __get__: fn(&JsObject, &PropertyKey, JsValue, &mut Context) -> JsResult, - pub(crate) __set__: - fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context) -> JsResult, - pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context) -> JsResult, - pub(crate) __own_property_keys__: fn(&JsObject, &mut Context) -> JsResult>, - pub(crate) __call__: fn(&JsObject, argument_count: usize, &mut Context) -> JsResult, + pub(crate) __get_own_property__: fn( + &JsObject, + &PropertyKey, + &mut InternalMethodContext<'_>, + ) -> JsResult>, + pub(crate) __define_own_property__: fn( + &JsObject, + &PropertyKey, + PropertyDescriptor, + &mut InternalMethodContext<'_>, + ) -> JsResult, + pub(crate) __has_property__: + fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_>) -> JsResult, + pub(crate) __get__: + fn(&JsObject, &PropertyKey, JsValue, &mut InternalMethodContext<'_>) -> JsResult, + pub(crate) __set__: fn( + &JsObject, + PropertyKey, + JsValue, + JsValue, + &mut InternalMethodContext<'_>, + ) -> JsResult, + pub(crate) __delete__: + fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_>) -> JsResult, + pub(crate) __own_property_keys__: + fn(&JsObject, context: &mut Context) -> JsResult>, + pub(crate) __call__: + fn(&JsObject, argument_count: usize, context: &mut Context) -> JsResult, pub(crate) __construct__: - fn(&JsObject, argument_count: usize, &mut Context) -> JsResult, + fn(&JsObject, argument_count: usize, context: &mut Context) -> JsResult, } /// The return value of an internal method (`[[Call]]` or `[[Construct]]`). @@ -447,7 +521,7 @@ pub(crate) fn ordinary_prevent_extensions( pub(crate) fn ordinary_get_own_property( obj: &JsObject, key: &PropertyKey, - _context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult> { let _timer = Profiler::global().start_event("Object::ordinary_get_own_property", "object"); // 1. Assert: IsPropertyKey(P) is true. @@ -464,7 +538,7 @@ pub(crate) fn ordinary_get_own_property( // 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. // 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute. // 9. Return D. - Ok(obj.borrow().properties.get(key)) + Ok(obj.borrow().properties.get_with_slot(key, context.slot())) } /// Abstract operation `OrdinaryDefineOwnProperty`. @@ -477,9 +551,10 @@ pub(crate) fn ordinary_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::ordinary_define_own_property", "object"); + // 1. Let current be ? O.[[GetOwnProperty]](P). let current = obj.__get_own_property__(key, context)?; @@ -492,6 +567,7 @@ pub(crate) fn ordinary_define_own_property( extensible, desc, current, + context.slot(), )) } @@ -504,18 +580,23 @@ pub(crate) fn ordinary_define_own_property( pub(crate) fn ordinary_has_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::ordinary_has_property", "object"); // 1. Assert: IsPropertyKey(P) is true. // 2. Let hasOwn be ? O.[[GetOwnProperty]](P). // 3. If hasOwn is not undefined, return true. if obj.__get_own_property__(key, context)?.is_some() { + context.slot().attributes |= SlotAttributes::FOUND; + Ok(true) } else { // 4. Let parent be ? O.[[GetPrototypeOf]](). let parent = obj.__get_prototype_of__(context)?; + context.slot().set_not_cachable_if_already_prototype(); + context.slot().attributes |= SlotAttributes::PROTOTYPE; + parent // 5. If parent is not null, then // a. Return ? parent.[[HasProperty]](P). @@ -534,7 +615,7 @@ pub(crate) fn ordinary_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::ordinary_get", "object"); // 1. Assert: IsPropertyKey(P) is true. @@ -544,6 +625,9 @@ pub(crate) fn ordinary_get( None => { // a. Let parent be ? O.[[GetPrototypeOf]](). if let Some(parent) = obj.__get_prototype_of__(context)? { + context.slot().set_not_cachable_if_already_prototype(); + context.slot().attributes |= SlotAttributes::PROTOTYPE; + // c. Return ? parent.[[Get]](P, Receiver). parent.__get__(key, receiver, context) } @@ -552,20 +636,24 @@ pub(crate) fn ordinary_get( Ok(JsValue::undefined()) } } - Some(ref desc) => match desc.kind() { - // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]]. - DescriptorKind::Data { - value: Some(value), .. - } => Ok(value.clone()), - // 5. Assert: IsAccessorDescriptor(desc) is true. - // 6. Let getter be desc.[[Get]]. - DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { - // 8. Return ? Call(getter, Receiver). - get.call(&receiver, &[], context) + Some(ref desc) => { + context.slot().attributes |= SlotAttributes::FOUND; + + match desc.kind() { + // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]]. + DescriptorKind::Data { + value: Some(value), .. + } => Ok(value.clone()), + // 5. Assert: IsAccessorDescriptor(desc) is true. + // 6. Let getter be desc.[[Get]]. + DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { + // 8. Return ? Call(getter, Receiver). + get.call(&receiver, &[], context) + } + // 7. If getter is undefined, return undefined. + _ => Ok(JsValue::undefined()), } - // 7. If getter is undefined, return undefined. - _ => Ok(JsValue::undefined()), - }, + } } } @@ -580,7 +668,7 @@ pub(crate) fn ordinary_set( key: PropertyKey, value: JsValue, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::ordinary_set", "object"); @@ -599,11 +687,20 @@ pub(crate) fn ordinary_set( // a. Let parent be ? O.[[GetPrototypeOf]](). // b. If parent is not null, then else if let Some(parent) = obj.__get_prototype_of__(context)? { + context.slot().set_not_cachable_if_already_prototype(); + context.slot().attributes |= SlotAttributes::PROTOTYPE; + // i. Return ? parent.[[Set]](P, V, Receiver). return parent.__set__(key, value, receiver, context); } // c. Else, else { + // It's not on prototype chain. + context + .slot() + .attributes + .remove(SlotAttributes::PROTOTYPE | SlotAttributes::NOT_CACHABLE); + // i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, // [[Enumerable]]: true, [[Configurable]]: true }. PropertyDescriptor::builder() @@ -626,6 +723,12 @@ pub(crate) fn ordinary_set( return Ok(false); }; + // NOTE(HaledOdat): If the object and receiver are not the same then it's not inline cachable for now. + context.slot().attributes.set( + SlotAttributes::NOT_CACHABLE, + !JsObject::equals(obj, receiver), + ); + // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). // d. If existingDescriptor is not undefined, then if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? { @@ -647,12 +750,15 @@ pub(crate) fn ordinary_set( context, ); } + // e. Else // i. Assert: Receiver does not currently have a property P. // ii. Return ? CreateDataProperty(Receiver, P, V). - return receiver.create_data_property(key, value, context); + return receiver.create_data_property_with_slot(key, value, context); } + context.slot().attributes |= SlotAttributes::FOUND; + // 4. Assert: IsAccessorDescriptor(ownDesc) is true. debug_assert!(own_desc.is_accessor_descriptor()); @@ -679,7 +785,7 @@ pub(crate) fn ordinary_set( pub(crate) fn ordinary_delete( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("Object::ordinary_delete", "object"); // 1. Assert: IsPropertyKey(P) is true. @@ -752,7 +858,7 @@ pub(crate) fn is_compatible_property_descriptor( Profiler::global().start_event("Object::is_compatible_property_descriptor", "object"); // 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current). - validate_and_apply_property_descriptor(None, extensible, desc, current) + validate_and_apply_property_descriptor(None, extensible, desc, current, &mut Slot::new()) } /// Abstract operation `ValidateAndApplyPropertyDescriptor` @@ -766,6 +872,7 @@ pub(crate) fn validate_and_apply_property_descriptor( extensible: bool, desc: PropertyDescriptor, current: Option, + slot: &mut Slot, ) -> bool { let _timer = Profiler::global().start_event("Object::validate_and_apply_property_descriptor", "object"); @@ -781,7 +888,7 @@ pub(crate) fn validate_and_apply_property_descriptor( // b. Assert: extensible is true. if let Some((obj, key)) = obj_and_key { - obj.borrow_mut().properties.insert( + obj.borrow_mut().properties.insert_with_slot( key, // c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then if desc.is_generic_descriptor() || desc.is_data_descriptor() { @@ -803,7 +910,9 @@ pub(crate) fn validate_and_apply_property_descriptor( // its default value. desc.into_accessor_defaulted() }, + slot, ); + slot.attributes |= SlotAttributes::FOUND; } // e. Return true. @@ -899,7 +1008,10 @@ pub(crate) fn validate_and_apply_property_descriptor( // a. For each field of Desc that is present, set the corresponding attribute of the // property named P of object O to the value of the field. current.fill_with(&desc); - obj.borrow_mut().properties.insert(key, current); + obj.borrow_mut() + .properties + .insert_with_slot(key, current, slot); + slot.attributes |= SlotAttributes::FOUND; } // 10. Return true. diff --git a/boa_engine/src/object/internal_methods/module_namespace.rs b/boa_engine/src/object/internal_methods/module_namespace.rs index a267418d16e..a0b48afe56d 100644 --- a/boa_engine/src/object/internal_methods/module_namespace.rs +++ b/boa_engine/src/object/internal_methods/module_namespace.rs @@ -11,7 +11,7 @@ use crate::{ use super::{ immutable_prototype, ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, ordinary_has_property, ordinary_own_property_keys, - InternalObjectMethods, ORDINARY_INTERNAL_METHODS, + InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }; /// Definitions of the internal object methods for [**Module Namespace Exotic Objects**][spec]. @@ -84,7 +84,7 @@ fn module_namespace_exotic_prevent_extensions(_: &JsObject, _: &mut Context) -> fn module_namespace_exotic_get_own_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult> { // 1. If P is a Symbol, return OrdinaryGetOwnProperty(O, P). let key = match key { @@ -128,7 +128,7 @@ fn module_namespace_exotic_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { // 1. If P is a Symbol, return ! OrdinaryDefineOwnProperty(O, P, Desc). if let PropertyKey::Symbol(_) = key { @@ -164,7 +164,7 @@ fn module_namespace_exotic_define_own_property( fn module_namespace_exotic_has_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { // 1. If P is a Symbol, return ! OrdinaryHasProperty(O, P). let key = match key { @@ -193,7 +193,7 @@ fn module_namespace_exotic_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { // 1. If P is a Symbol, then // a. Return ! OrdinaryGet(O, P, Receiver). @@ -269,7 +269,7 @@ fn module_namespace_exotic_set( _key: PropertyKey, _value: JsValue, _receiver: JsValue, - _context: &mut Context, + _context: &mut InternalMethodContext<'_>, ) -> JsResult { // 1. Return false. Ok(false) @@ -281,7 +281,7 @@ fn module_namespace_exotic_set( fn module_namespace_exotic_delete( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { // 1. If P is a Symbol, then // a. Return ! OrdinaryDelete(O, P). diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs index 9efda63307c..002afacee9e 100644 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ b/boa_engine/src/object/internal_methods/proxy.rs @@ -1,7 +1,7 @@ use crate::{ builtins::{array, object::Object}, error::JsNativeError, - object::{InternalObjectMethods, JsObject, JsPrototype}, + object::{shape::slot::SlotAttributes, InternalObjectMethods, JsObject, JsPrototype}, property::{PropertyDescriptor, PropertyKey}, string::utf16, value::Type, @@ -9,7 +9,7 @@ use crate::{ }; use rustc_hash::FxHashSet; -use super::{CallValue, ORDINARY_INTERNAL_METHODS}; +use super::{CallValue, InternalMethodContext, ORDINARY_INTERNAL_METHODS}; /// Definitions of the internal object methods for array exotic objects. /// @@ -268,8 +268,10 @@ pub(crate) fn proxy_exotic_prevent_extensions( pub(crate) fn proxy_exotic_get_own_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult> { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. // 3. Assert: Type(handler) is Object. @@ -284,6 +286,7 @@ pub(crate) fn proxy_exotic_get_own_property( let Some(trap) = handler.get_method(utf16!("getOwnPropertyDescriptor"), context)? else { // 6. If trap is undefined, then // a. Return ? target.[[GetOwnProperty]](P). + return target.__get_own_property__(key, context); }; @@ -392,8 +395,10 @@ pub(crate) fn proxy_exotic_define_own_property( obj: &JsObject, key: &PropertyKey, desc: PropertyDescriptor, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. // 3. Assert: Type(handler) is Object. @@ -503,8 +508,10 @@ pub(crate) fn proxy_exotic_define_own_property( pub(crate) fn proxy_exotic_has_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. // 3. Assert: Type(handler) is Object. @@ -569,8 +576,11 @@ pub(crate) fn proxy_exotic_get( obj: &JsObject, key: &PropertyKey, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { + // Proxy object can't be cached. + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. // 3. Assert: Type(handler) is Object. @@ -638,8 +648,10 @@ pub(crate) fn proxy_exotic_set( key: PropertyKey, value: JsValue, receiver: JsValue, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. // 3. Assert: Type(handler) is Object. @@ -719,7 +731,7 @@ pub(crate) fn proxy_exotic_set( pub(crate) fn proxy_exotic_delete( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult { // 1. Let handler be O.[[ProxyHandler]]. // 2. If handler is null, throw a TypeError exception. @@ -852,7 +864,7 @@ pub(crate) fn proxy_exotic_own_property_keys( // 16. For each element key of targetKeys, do for key in target_keys { // a. Let desc be ? target.[[GetOwnProperty]](key). - match target.__get_own_property__(&key, context)? { + match target.__get_own_property__(&key, &mut context.into())? { // b. If desc is not undefined and desc.[[Configurable]] is false, then Some(desc) if !desc.expect_configurable() => { // i. Append key as an element of targetNonconfigurableKeys. diff --git a/boa_engine/src/object/internal_methods/string.rs b/boa_engine/src/object/internal_methods/string.rs index 97053a74046..3876478141c 100644 --- a/boa_engine/src/object/internal_methods/string.rs +++ b/boa_engine/src/object/internal_methods/string.rs @@ -5,7 +5,7 @@ use crate::{ Context, JsResult, }; -use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; +use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; /// Definitions of the internal object methods for string exotic objects. /// @@ -29,7 +29,7 @@ pub(crate) static STRING_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = Intern pub(crate) fn string_exotic_get_own_property( obj: &JsObject, key: &PropertyKey, - context: &mut Context, + context: &mut InternalMethodContext<'_>, ) -> JsResult> { // 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 { // 1. Assert: IsPropertyKey(P) is true. // 2. Let stringDesc be ! StringGetOwnProperty(S, P). diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index d5be5d10843..6094c7db785 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -4,7 +4,7 @@ use super::{ internal_methods::{ - non_existant_call, non_existant_construct, InternalObjectMethods, + non_existant_call, non_existant_construct, InternalMethodContext, InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS, }, shape::RootShape, @@ -1000,6 +1000,8 @@ Cannot both specify accessors and a value or writable attribute", where K: Into, { + 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 83b747b86bb..fd102893897 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -10,7 +10,7 @@ use crate::{ Context, JsResult, JsSymbol, JsValue, }; -use super::ObjectKind; +use super::{internal_methods::InternalMethodContext, ObjectKind}; /// Object integrity level. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -73,7 +73,11 @@ impl JsObject { // 1. Assert: Type(O) is Object. // 2. Assert: IsPropertyKey(P) is true. // 3. Return ? O.[[Get]](P, O). - self.__get__(&key.into(), self.clone().into(), context) + self.__get__( + &key.into(), + self.clone().into(), + &mut InternalMethodContext::new(context), + ) } /// set property of object or throw if bool flag is passed. @@ -92,7 +96,12 @@ impl JsObject { // 2. Assert: IsPropertyKey(P) is true. // 3. Assert: Type(Throw) is Boolean. // 4. Let success be ? O.[[Set]](P, V, O). - let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?; + let success = self.__set__( + key.clone(), + value.into(), + self.clone().into(), + &mut InternalMethodContext::new(context), + )?; // 5. If success is false and Throw is true, throw a TypeError exception. if !success && throw { return Err(JsNativeError::typ() @@ -115,6 +124,38 @@ impl JsObject { value: V, context: &mut Context, ) -> JsResult + where + K: Into, + V: Into, + { + // 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( + &self, + key: K, + value: V, + context: &mut InternalMethodContext<'_>, + ) -> JsResult where K: Into, V: Into, @@ -221,7 +262,11 @@ impl JsObject { // 1. Assert: Type(O) is Object. // 2. Assert: IsPropertyKey(P) is true. // 3. Let success be ? O.[[DefineOwnProperty]](P, desc). - let success = self.__define_own_property__(&key, desc.into(), context)?; + let success = self.__define_own_property__( + &key, + desc.into(), + &mut InternalMethodContext::new(context), + )?; // 4. If success is false, throw a TypeError exception. if !success { return Err(JsNativeError::typ() @@ -246,7 +291,7 @@ impl JsObject { // 1. Assert: Type(O) is Object. // 2. Assert: IsPropertyKey(P) is true. // 3. Let success be ? O.[[Delete]](P). - let success = self.__delete__(&key, context)?; + let success = self.__delete__(&key, &mut InternalMethodContext::new(context))?; // 4. If success is false, throw a TypeError exception. if !success { return Err(JsNativeError::typ() @@ -270,7 +315,8 @@ impl JsObject { // 1. Assert: Type(O) is Object. // 2. Assert: IsPropertyKey(P) is true. // 3. Return ? O.[[HasProperty]](P). - self.__has_property__(&key.into(), context) + + self.__has_property__(&key.into(), &mut InternalMethodContext::new(context)) } /// Check if object has an own property. @@ -287,7 +333,7 @@ impl JsObject { // 1. Assert: Type(O) is Object. // 2. Assert: IsPropertyKey(P) is true. // 3. Let desc be ? O.[[GetOwnProperty]](P). - let desc = self.__get_own_property__(&key, context)?; + let desc = self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?; // 4. If desc is undefined, return false. // 5. Return true. Ok(desc.is_some()) @@ -403,14 +449,14 @@ impl JsObject { // 2. Assert: level is either sealed or frozen. // 3. Let status be ? O.[[PreventExtensions]](). - let status = self.__prevent_extensions__(context)?; + let status = self.__prevent_extensions__(&mut InternalMethodContext::new(context))?; // 4. If status is false, return false. if !status { return Ok(false); } // 5. Let keys be ? O.[[OwnPropertyKeys]](). - let keys = self.__own_property_keys__(context)?; + let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?; match level { // 6. If level is sealed, then @@ -431,7 +477,8 @@ impl JsObject { // b. For each element k of keys, do for k in keys { // i. Let currentDesc be ? O.[[GetOwnProperty]](k). - let current_desc = self.__get_own_property__(&k, context)?; + let current_desc = + self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?; // ii. If currentDesc is not undefined, then if let Some(current_desc) = current_desc { // 1. If IsAccessorDescriptor(currentDesc) is true, then @@ -481,12 +528,13 @@ impl JsObject { // 5. NOTE: If the object is extensible, none of its properties are examined. // 6. Let keys be ? O.[[OwnPropertyKeys]](). - let keys = self.__own_property_keys__(context)?; + let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?; // 7. For each element k of keys, do for k in keys { // a. Let currentDesc be ? O.[[GetOwnProperty]](k). - let current_desc = self.__get_own_property__(&k, context)?; + let current_desc = + self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?; // b. If currentDesc is not undefined, then if let Some(current_desc) = current_desc { // i. If currentDesc.[[Configurable]] is true, return false. @@ -595,7 +643,7 @@ impl JsObject { ) -> JsResult> { // 1. Assert: Type(O) is Object. // 2. Let ownKeys be ? O.[[OwnPropertyKeys]](). - let own_keys = self.__own_property_keys__(context)?; + let own_keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?; // 3. Let properties be a new empty List. let mut properties = vec![]; @@ -610,7 +658,8 @@ impl JsObject { if let Some(key_str) = key_str { // i. Let desc be ? O.[[GetOwnProperty]](key). - let desc = self.__get_own_property__(&key, context)?; + let desc = + self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?; // ii. If desc is not undefined and desc.[[Enumerable]] is true, then if let Some(desc) = desc { if desc.expect_enumerable() { @@ -661,7 +710,12 @@ impl JsObject { // 1. Assert: IsPropertyKey(P) is true. // 2. Let func be ? GetV(V, P). - match &self.__get__(&key.into(), self.clone().into(), context)? { + + match &self.__get__( + &key.into(), + self.clone().into(), + &mut InternalMethodContext::new(context), + )? { // 3. If func is either undefined or null, return undefined. JsValue::Undefined | JsValue::Null => Ok(None), // 5. Return func. @@ -1066,7 +1120,11 @@ impl JsObject { // 1. If argumentsList is not present, set argumentsList to a new empty List. // 2. Let func be ? GetV(V, P). - let func = self.__get__(&key.into(), this_value.clone(), context)?; + let func = self.__get__( + &key.into(), + this_value.clone(), + &mut InternalMethodContext::new(context), + )?; // 3. Return ? Call(func, V, argumentsList) func.call(&this_value, args, context) @@ -1092,7 +1150,12 @@ impl JsValue { let o = self.to_object(context)?; // 2. Return ? O.[[Get]](P, V). - o.__get__(&key.into(), self.clone(), context) + + o.__get__( + &key.into(), + self.clone(), + &mut InternalMethodContext::new(context), + ) } /// Abstract operation `GetMethod ( V, P )` @@ -1260,7 +1323,7 @@ impl JsValue { // 6. Repeat, loop { // a. Set O to ? O.[[GetPrototypeOf]](). - object = match object.__get_prototype_of__(context)? { + object = match object.__get_prototype_of__(&mut InternalMethodContext::new(context))? { Some(obj) => obj, // b. If O is null, return false. None => return Ok(false), diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs index a7046fde8fd..f2283672eab 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 { + if let PropertyKey::Index(index) = key { + return self.indexed_properties.get(index.get()); + } + if let Some(slot) = self.shape.lookup(key) { + out_slot.index = slot.index; + + // Remove all descriptor attributes, but keep inline caching bits. + out_slot.attributes = + (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | slot.attributes; + return Some(self.get_storage(slot)); + } + + None + } + /// Get the property with the given key from the [`PropertyMap`]. #[must_use] pub(crate) fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor { @@ -300,6 +322,17 @@ impl PropertyMap { /// Insert the given property descriptor with the given key [`PropertyMap`]. pub fn insert(&mut self, key: &PropertyKey, property: PropertyDescriptor) -> bool { + let mut dummy_slot = Slot::new(); + self.insert_with_slot(key, property, &mut dummy_slot) + } + + /// Insert the given property descriptor with the given key [`PropertyMap`]. + pub(crate) fn insert_with_slot( + &mut self, + key: &PropertyKey, + property: PropertyDescriptor, + out_slot: &mut Slot, + ) -> bool { if let PropertyKey::Index(index) = key { return self.indexed_properties.insert(index.get(), property); } @@ -346,6 +379,9 @@ impl PropertyMap { } else { self.storage[index] = property.expect_value().clone(); } + out_slot.index = slot.index; + out_slot.attributes = + (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes; return true; } @@ -364,6 +400,10 @@ impl PropertyMap { }) ); + out_slot.index = self.storage.len() as u32; + out_slot.attributes = + (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes; + if attributes.is_accessor_descriptor() { self.storage.push( property diff --git a/boa_engine/src/object/shape/mod.rs b/boa_engine/src/object/shape/mod.rs index 5754e70f9dc..472a8b4a159 100644 --- a/boa_engine/src/object/shape/mod.rs +++ b/boa_engine/src/object/shape/mod.rs @@ -16,7 +16,11 @@ use boa_gc::{Finalize, Trace}; use crate::property::PropertyKey; -use self::{shared_shape::TransitionKey, slot::Slot}; +use self::{ + shared_shape::{TransitionKey, WeakSharedShape}, + slot::Slot, + unique_shape::WeakUniqueShape, +}; use super::JsPrototype; @@ -225,3 +229,35 @@ impl From for Shape { } } } + +/// Represents a weak reaference to an object's [`Shape`]. +#[derive(Debug, Trace, Finalize, Clone)] +pub(crate) enum WeakShape { + Unique(WeakUniqueShape), + Shared(WeakSharedShape), + None, +} + +impl WeakShape { + /// Return location in memory of the [`Shape`]. + /// + /// Returns `0` if the shape has been freed. + #[inline] + #[must_use] + pub(crate) fn to_addr_usize(&self) -> usize { + match self { + WeakShape::Shared(shape) => shape.to_addr_usize(), + WeakShape::Unique(shape) => shape.to_addr_usize(), + WeakShape::None => 0, + } + } +} + +impl From<&Shape> for WeakShape { + fn from(value: &Shape) -> Self { + match &value.inner { + Inner::Shared(shape) => WeakShape::Shared(shape.into()), + Inner::Unique(shape) => WeakShape::Unique(shape.into()), + } + } +} diff --git a/boa_engine/src/object/shape/shared_shape/mod.rs b/boa_engine/src/object/shape/shared_shape/mod.rs index 18bb7194096..0dc58542c30 100644 --- a/boa_engine/src/object/shape/shared_shape/mod.rs +++ b/boa_engine/src/object/shape/shared_shape/mod.rs @@ -7,7 +7,7 @@ mod tests; use std::{collections::hash_map::RandomState, hash::Hash}; use bitflags::bitflags; -use boa_gc::{empty_trace, Finalize, Gc, Trace}; +use boa_gc::{empty_trace, Finalize, Gc, Trace, WeakGc}; use indexmap::IndexMap; use crate::{object::JsPrototype, property::PropertyKey, JsObject}; @@ -474,9 +474,37 @@ impl SharedShape { ) } - /// Return location in memory of the [`UniqueShape`]. + /// Return location in memory of the [`SharedShape`]. pub(crate) fn to_addr_usize(&self) -> usize { let ptr: *const _ = self.inner.as_ref(); ptr as usize } } + +/// Represents a weak reference to [`SharedShape`]. +#[derive(Debug, Trace, Finalize, Clone)] +pub(crate) struct WeakSharedShape { + inner: WeakGc, +} + +impl WeakSharedShape { + /// Return location in memory of the [`WeakSharedShape`]. + /// + /// Returns `0` if the inner [`SharedShape`] has been freed. + #[inline] + #[must_use] + pub(crate) fn to_addr_usize(&self) -> usize { + self.inner.upgrade().map_or(0, |inner| { + let ptr: *const _ = inner.as_ref(); + ptr as usize + }) + } +} + +impl From<&SharedShape> for WeakSharedShape { + fn from(value: &SharedShape) -> Self { + WeakSharedShape { + inner: WeakGc::new(&value.inner), + } + } +} diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs index e0a33c01114..cc95eba01c4 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/object/shape/unique_shape.rs b/boa_engine/src/object/shape/unique_shape.rs index 68872a96c82..ca6167c62d2 100644 --- a/boa_engine/src/object/shape/unique_shape.rs +++ b/boa_engine/src/object/shape/unique_shape.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, fmt::Debug}; -use boa_gc::{Finalize, Gc, GcRefCell, Trace}; +use boa_gc::{Finalize, Gc, GcRefCell, Trace, WeakGc}; use crate::property::PropertyKey; @@ -239,3 +239,31 @@ impl UniqueShape { ptr as usize } } + +/// Represents a weak reference to [`UniqueShape`]. +#[derive(Debug, Clone, Trace, Finalize)] +pub(crate) struct WeakUniqueShape { + inner: WeakGc, +} + +impl WeakUniqueShape { + /// Return location in memory of the [`WeakUniqueShape`]. + /// + /// Returns `0` if the inner [`UniqueShape`] has been freed. + #[inline] + #[must_use] + pub(crate) fn to_addr_usize(&self) -> usize { + self.inner.upgrade().map_or(0, |inner| { + let ptr: *const _ = inner.as_ref(); + ptr as usize + }) + } +} + +impl From<&UniqueShape> for WeakUniqueShape { + fn from(value: &UniqueShape) -> Self { + WeakUniqueShape { + inner: WeakGc::new(&value.inner), + } + } +} diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs index a99deb4e601..74c2c7ed5e5 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::{js_string, run_test_actions, TestAction}; use std::collections::hash_map::DefaultHasher; @@ -197,10 +198,13 @@ fn float_display() { #[test] fn string_length_is_not_enumerable() { - run_test_actions([TestAction::assert_context(|ctx| { - let object = JsValue::new(js_string!("foo")).to_object(ctx).unwrap(); + run_test_actions([TestAction::assert_context(|context| { + let object = JsValue::new(js_string!("foo")).to_object(context).unwrap(); let length_desc = object - .__get_own_property__(&js_string!("length").into(), ctx) + .__get_own_property__( + &PropertyKey::from(js_string!("length")), + &mut InternalMethodContext::new(context), + ) .unwrap() .unwrap(); !length_desc.expect_enumerable() @@ -209,16 +213,20 @@ fn string_length_is_not_enumerable() { #[test] fn string_length_is_in_utf16_codeunits() { - run_test_actions([TestAction::assert_context(|ctx| { + run_test_actions([TestAction::assert_context(|context| { // 😀 is one Unicode code point, but 2 UTF-16 code units - let object = JsValue::new(js_string!("😀")).to_object(ctx).unwrap(); + let object = JsValue::new(js_string!("😀")).to_object(context).unwrap(); + let length_desc = object - .__get_own_property__(&js_string!("length").into(), ctx) + .__get_own_property__( + &PropertyKey::from(js_string!("length")), + &mut InternalMethodContext::new(context), + ) .unwrap() .unwrap(); length_desc .expect_value() - .to_integer_or_infinity(ctx) + .to_integer_or_infinity(context) .unwrap() == IntegerOrInfinity::Integer(2) })]); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 4ba94b1a007..cd951a9b7fb 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -5,12 +5,15 @@ use crate::{ builtins::function::{OrdinaryFunction, ThisMode}, environments::{BindingLocator, CompileTimeEnvironment}, - object::{JsObject, ObjectData}, + object::{ + shape::{slot::Slot, Shape, WeakShape}, + JsObject, ObjectData, + }, Context, JsBigInt, JsString, JsValue, }; use bitflags::bitflags; use boa_ast::function::FormalParameterList; -use boa_gc::{empty_trace, Finalize, Gc, Trace}; +use boa_gc::{empty_trace, Finalize, Gc, GcRefCell, Trace}; use boa_profiler::Profiler; use std::{cell::Cell, mem::size_of, rc::Rc}; use thin_vec::ThinVec; @@ -121,6 +124,43 @@ pub(crate) enum Constant { CompileTimeEnvironment(#[unsafe_ignore_trace] Rc), } +/// An inline cache entry for a property access. +#[derive(Clone, Debug, Trace, Finalize)] +pub(crate) struct InlineCache { + /// The property that is accessed. + pub(crate) name: JsString, + + /// A pointer is kept to the shape to avoid the shape from being deallocated. + pub(crate) shape: GcRefCell, + + /// The [`Slot`] of the property. + #[unsafe_ignore_trace] + pub(crate) slot: Cell, +} + +impl InlineCache { + pub(crate) const fn new(name: JsString) -> Self { + Self { + name, + shape: GcRefCell::new(WeakShape::None), + slot: Cell::new(Slot::new()), + } + } + + pub(crate) fn set(&self, shape: &Shape, slot: Slot) { + *self.shape.borrow_mut() = shape.into(); + self.slot.set(slot); + } + + pub(crate) fn slot(&self) -> Slot { + self.slot.get() + } + + pub(crate) fn matches(&self, shape: &Shape) -> bool { + self.shape.borrow().to_addr_usize() == shape.to_addr_usize() + } +} + /// The internal representation of a JavaScript function. /// /// A `CodeBlock` is generated for each function compiled by the @@ -146,6 +186,7 @@ pub struct CodeBlock { pub(crate) params: FormalParameterList, /// Bytecode + #[unsafe_ignore_trace] pub(crate) bytecode: Box<[u8]>, pub(crate) constants: ThinVec, @@ -157,6 +198,9 @@ pub struct CodeBlock { /// Exception [`Handler`]s. #[unsafe_ignore_trace] pub(crate) handlers: ThinVec, + + /// inline caching + pub(crate) ic: Box<[InlineCache]>, } /// ---- `CodeBlock` public API ---- @@ -176,6 +220,7 @@ impl CodeBlock { this_mode: ThisMode::Global, params: FormalParameterList::default(), handlers: ThinVec::default(), + ic: Box::default(), } } @@ -457,9 +502,7 @@ impl CodeBlock { interner.resolve_expect(self.bindings[index.value() as usize].name().sym()), ) } - Instruction::GetPropertyByName { index } - | Instruction::SetPropertyByName { index } - | Instruction::DefineOwnPropertyByName { index } + Instruction::DefineOwnPropertyByName { index } | Instruction::DefineClassStaticMethodByName { index } | Instruction::DefineClassMethodByName { index } | Instruction::SetPropertyGetterByName { index } @@ -488,6 +531,18 @@ impl CodeBlock { .to_std_string_escaped(), ) } + Instruction::GetPropertyByName { index } | Instruction::SetPropertyByName { index } => { + let ic = &self.ic[index.value() as usize]; + let slot = ic.slot(); + format!( + "{:04}: '{}', Shape: 0x{:x}, Slot: index: {}, attributes {:?}", + index.value(), + ic.name.to_std_string_escaped(), + ic.shape.borrow().to_addr_usize(), + slot.index, + slot.attributes, + ) + } Instruction::PushPrivateEnvironment { name_indices } => { format!("{name_indices:?}") } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index a924ee1c2ef..d4452d92079 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -41,6 +41,7 @@ pub(crate) use { call_frame::CallFrameFlags, code_block::{ create_function_object, create_function_object_fast, CodeBlockFlags, Constant, Handler, + InlineCache, }, completion_record::CompletionRecord, opcode::BindingOpcode, diff --git a/boa_engine/src/vm/opcode/define/class/getter.rs b/boa_engine/src/vm/opcode/define/class/getter.rs index 128fd886ce4..306f08d4925 100644 --- a/boa_engine/src/vm/opcode/define/class/getter.rs +++ b/boa_engine/src/vm/opcode/define/class/getter.rs @@ -1,5 +1,6 @@ use crate::{ builtins::function::set_function_name, + object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, @@ -35,7 +36,7 @@ impl DefineClassStaticGetterByName { function_mut.set_home_object(class.clone()); } let set = class - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -47,7 +48,7 @@ impl DefineClassStaticGetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -104,7 +105,7 @@ impl DefineClassGetterByName { function_mut.set_home_object(class_proto.clone()); } let set = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -116,7 +117,7 @@ impl DefineClassGetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -174,8 +175,9 @@ impl Operation for DefineClassStaticGetterByValue { .expect("method must be function object"); function_mut.set_home_object(class.clone()); } + let set = class - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -225,7 +227,7 @@ impl Operation for DefineClassGetterByValue { function_mut.set_home_object(class_proto.clone()); } let set = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -237,7 +239,7 @@ impl Operation for DefineClassGetterByValue { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/define/class/method.rs b/boa_engine/src/vm/opcode/define/class/method.rs index 19cb735f2fb..2f8768b3867 100644 --- a/boa_engine/src/vm/opcode/define/class/method.rs +++ b/boa_engine/src/vm/opcode/define/class/method.rs @@ -1,5 +1,6 @@ use crate::{ builtins::function::set_function_name, + object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, @@ -43,7 +44,7 @@ impl DefineClassStaticMethodByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -108,7 +109,7 @@ impl DefineClassMethodByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -221,7 +222,7 @@ impl Operation for DefineClassMethodByValue { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/define/class/setter.rs b/boa_engine/src/vm/opcode/define/class/setter.rs index 58a7131a119..b2277af6319 100644 --- a/boa_engine/src/vm/opcode/define/class/setter.rs +++ b/boa_engine/src/vm/opcode/define/class/setter.rs @@ -1,5 +1,6 @@ use crate::{ builtins::function::set_function_name, + object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsString, @@ -35,7 +36,7 @@ impl DefineClassStaticSetterByName { function_mut.set_home_object(class.clone()); } let get = class - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -48,7 +49,7 @@ impl DefineClassStaticSetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -105,7 +106,7 @@ impl DefineClassSetterByName { function_mut.set_home_object(class_proto.clone()); } let get = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -118,7 +119,7 @@ impl DefineClassSetterByName { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) @@ -178,7 +179,7 @@ impl Operation for DefineClassStaticSetterByValue { function_mut.set_home_object(class.clone()); } let get = class - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -230,7 +231,7 @@ impl Operation for DefineClassSetterByValue { function_mut.set_home_object(class_proto.clone()); } let get = class_proto - .__get_own_property__(&key, context)? + .__get_own_property__(&key, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -243,7 +244,7 @@ impl Operation for DefineClassSetterByValue { .enumerable(false) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) diff --git a/boa_engine/src/vm/opcode/define/own_property.rs b/boa_engine/src/vm/opcode/define/own_property.rs index 817b93ca707..f297dcb877e 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, @@ -29,7 +30,7 @@ impl DefineOwnPropertyByName { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -86,7 +87,7 @@ impl Operation for DefineOwnPropertyByValue { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; if !success { return Err(JsNativeError::typ() diff --git a/boa_engine/src/vm/opcode/delete/mod.rs b/boa_engine/src/vm/opcode/delete/mod.rs index 375dd270681..b6caf4d08d5 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, }; @@ -21,8 +22,9 @@ impl DeletePropertyByName { .code_block() .constant_string(index) .into(); - let result = object.__delete__(&key, context)?; - if !result && context.vm.frame().code_block.strict() { + + let result = object.__delete__(&key, &mut InternalMethodContext::new(context))?; + if !result && context.vm.frame().code_block().strict() { return Err(JsNativeError::typ() .with_message("Cannot delete property") .into()); @@ -70,8 +72,9 @@ impl Operation for DeletePropertyByValue { let value = context.vm.pop(); let object = value.to_object(context)?; let property_key = key_value.to_property_key(context)?; - let result = object.__delete__(&property_key, context)?; - if !result && context.vm.frame().code_block.strict() { + + let result = object.__delete__(&property_key, &mut InternalMethodContext::new(context))?; + if !result && context.vm.frame().code_block().strict() { return Err(JsNativeError::typ() .with_message("Cannot delete property") .into()); diff --git a/boa_engine/src/vm/opcode/environment/mod.rs b/boa_engine/src/vm/opcode/environment/mod.rs index aadba960aca..6331724dca2 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, }; @@ -54,7 +55,7 @@ impl Operation for Super { }; let value = home_object - .map(|o| o.__get_prototype_of__(context)) + .map(|o| o.__get_prototype_of__(&mut InternalMethodContext::new(context))) .transpose()? .flatten() .map_or_else(JsValue::null, JsValue::from); @@ -85,7 +86,7 @@ impl Operation for SuperCallPrepare { .expect("super call must be in function environment"); let active_function = this_env.slots().function_object().clone(); let super_constructor = active_function - .__get_prototype_of__(context) + .__get_prototype_of__(&mut InternalMethodContext::new(context)) .expect("function object must have prototype"); context @@ -242,7 +243,7 @@ impl Operation for SuperCallDerived { .clone(); let active_function = this_env.slots().function_object().clone(); let super_constructor = active_function - .__get_prototype_of__(context) + .__get_prototype_of__(&mut InternalMethodContext::new(context)) .expect("function object must have prototype") .expect("function object must have prototype"); diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs index 1a7aa2873fa..ae193c81cdd 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, @@ -21,14 +22,52 @@ impl GetPropertyByName { value.to_object(context)? }; - let key = context - .vm - .frame() - .code_block() - .constant_string(index) - .into(); + let ic = &context.vm.frame().code_block().ic[index]; + let mut slot = ic.slot(); + if slot.is_cachable() { + let object_borrowed = object.borrow(); + if ic.matches(object_borrowed.shape()) { + let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { + let prototype = object + .borrow() + .properties() + .shape + .prototype() + .expect("prototype should have value"); + let prototype = prototype.borrow(); + prototype.properties().storage[slot.index as usize].clone() + } else { + object_borrowed.properties().storage[slot.index as usize].clone() + }; + + drop(object_borrowed); + if slot.attributes.has_get() && result.is_object() { + result = result.as_object().expect("should contain getter").call( + &receiver, + &[], + context, + )?; + } + context.vm.push(result); + return Ok(CompletionType::Normal); + } + } + + let key: PropertyKey = ic.name.clone().into(); + + let context = &mut InternalMethodContext::new(context); let result = object.__get__(&key, receiver, context)?; + slot = *context.slot(); + + // Cache the property. + if slot.attributes.is_cachable() { + let ic = &context.vm.frame().code_block.ic[index]; + let object_borrowed = object.borrow(); + let shape = object_borrowed.shape(); + ic.set(shape, slot); + } + context.vm.push(result); Ok(CompletionType::Normal) } @@ -95,7 +134,7 @@ impl Operation for GetPropertyByValue { } // Slow path: - let result = object.__get__(&key, receiver, context)?; + let result = object.__get__(&key, receiver, &mut InternalMethodContext::new(context))?; context.vm.push(result); Ok(CompletionType::Normal) @@ -143,7 +182,7 @@ impl Operation for GetPropertyByValuePush { } // Slow path: - let result = object.__get__(&key, receiver, context)?; + let result = object.__get__(&key, receiver, &mut InternalMethodContext::new(context))?; context.vm.push(key); context.vm.push(result); diff --git a/boa_engine/src/vm/opcode/push/class/private.rs b/boa_engine/src/vm/opcode/push/class/private.rs index cce5288a6a7..ed0e1a77e0c 100644 --- a/boa_engine/src/vm/opcode/push/class/private.rs +++ b/boa_engine/src/vm/opcode/push/class/private.rs @@ -1,6 +1,6 @@ use crate::{ js_string, - object::PrivateElement, + object::{internal_methods::InternalMethodContext, PrivateElement}, property::PropertyDescriptor, string::utf16, vm::{opcode::Operation, CompletionType}, @@ -29,7 +29,11 @@ impl PushClassPrivateMethod { .configurable(true) .build(); method_object - .__define_own_property__(&utf16!("name").into(), desc, context) + .__define_own_property__( + &utf16!("name").into(), + desc, + &mut InternalMethodContext::new(context), + ) .expect("failed to set name property on private method"); let class = context.vm.pop(); diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index 8f917a5a7d1..a115bfc9a1b 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, @@ -63,7 +65,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 d12574e7a81..98e2b771122 100644 --- a/boa_engine/src/vm/opcode/set/private.rs +++ b/boa_engine/src/vm/opcode/set/private.rs @@ -122,7 +122,7 @@ impl SetPrivateMethod { .configurable(true) .build(); value - .__define_own_property__(&js_string!("name").into(), desc, context) + .__define_own_property__(&js_string!("name").into(), desc, &mut context.into()) .expect("failed to set name property on private method"); let object = context.vm.pop(); diff --git a/boa_engine/src/vm/opcode/set/property.rs b/boa_engine/src/vm/opcode/set/property.rs index 9f609733c73..7ff827188f8 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, @@ -25,19 +26,73 @@ impl SetPropertyByName { object.to_object(context)? }; - let name: PropertyKey = context - .vm - .frame() - .code_block() - .constant_string(index) - .into(); + let ic = &context.vm.frame().code_block().ic[index]; + let mut slot = ic.slot(); + if slot.is_cachable() { + let object_borrowed = object.borrow(); + if ic.matches(object_borrowed.shape()) { + let slot_index = slot.index as usize; + + if slot.attributes.is_accessor_descriptor() { + let result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { + let prototype = object_borrowed + .properties() + .shape + .prototype() + .expect("prototype should have value"); + let prototype = prototype.borrow(); + + prototype.properties().storage[slot_index + 1].clone() + } else { + object_borrowed.properties().storage[slot_index + 1].clone() + }; + + drop(object_borrowed); + if slot.attributes.has_set() && result.is_object() { + result.as_object().expect("should contain getter").call( + &receiver, + &[value.clone()], + context, + )?; + } + } else if slot.attributes.contains(SlotAttributes::PROTOTYPE) { + let prototype = object_borrowed + .properties() + .shape + .prototype() + .expect("prototype should have value"); + let mut prototype = prototype.borrow_mut(); + + prototype.properties_mut().storage[slot_index] = value.clone(); + } else { + drop(object_borrowed); + let mut object_borrowed = object.borrow_mut(); + object_borrowed.properties_mut().storage[slot_index] = value.clone(); + } + context.vm.push(value); + return Ok(CompletionType::Normal); + } + } + + let name: PropertyKey = ic.name.clone().into(); + let context = &mut InternalMethodContext::new(context); let succeeded = object.__set__(name.clone(), value.clone(), receiver, context)?; if !succeeded && context.vm.frame().code_block.strict() { return Err(JsNativeError::typ() .with_message(format!("cannot set non-writable property: {name}")) .into()); } + + slot = *context.slot(); + + // Cache the property. + if succeeded && slot.is_cachable() { + let ic = &context.vm.frame().code_block.ic[index]; + let object_borrowed = object.borrow(); + let shape = object_borrowed.shape(); + ic.set(shape, slot); + } context.vm.stack.push(value); Ok(CompletionType::Normal) } @@ -158,7 +213,8 @@ impl Operation for SetPropertyByValue { } // Slow path: - let succeeded = object.__set__(key.clone(), value.clone(), receiver, context)?; + let succeeded = + object.__set__(key.clone(), value.clone(), receiver, &mut context.into())?; if !succeeded && context.vm.frame().code_block.strict() { return Err(JsNativeError::typ() .with_message(format!("cannot set non-writable property: {key}")) @@ -188,7 +244,7 @@ impl SetPropertyGetterByName { .constant_string(index) .into(); let set = object - .__get_own_property__(&name, context)? + .__get_own_property__(&name, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -200,7 +256,7 @@ impl SetPropertyGetterByName { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -245,8 +301,9 @@ impl Operation for SetPropertyGetterByValue { let object = context.vm.pop(); let object = object.to_object(context)?; let name = key.to_property_key(context)?; + let set = object - .__get_own_property__(&name, context)? + .__get_own_property__(&name, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::set) .cloned(); @@ -258,7 +315,7 @@ impl Operation for SetPropertyGetterByValue { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -282,8 +339,9 @@ impl SetPropertySetterByName { .code_block() .constant_string(index) .into(); + let get = object - .__get_own_property__(&name, context)? + .__get_own_property__(&name, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -295,7 +353,7 @@ impl SetPropertySetterByName { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } @@ -340,8 +398,9 @@ impl Operation for SetPropertySetterByValue { let object = context.vm.pop(); let object = object.to_object(context)?; let name = key.to_property_key(context)?; + let get = object - .__get_own_property__(&name, context)? + .__get_own_property__(&name, &mut InternalMethodContext::new(context))? .as_ref() .and_then(PropertyDescriptor::get) .cloned(); @@ -353,7 +412,7 @@ impl Operation for SetPropertySetterByValue { .enumerable(true) .configurable(true) .build(), - context, + &mut InternalMethodContext::new(context), )?; Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/set/prototype.rs b/boa_engine/src/vm/opcode/set/prototype.rs index e22a4530f96..2469de1c884 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, }; @@ -29,7 +30,7 @@ impl Operation for SetPrototype { let object = object.as_object().expect("object is not an object"); object - .__set_prototype_of__(prototype, context) + .__set_prototype_of__(prototype, &mut InternalMethodContext::new(context)) .expect("cannot fail per spec"); Ok(CompletionType::Normal)