From e7f8005b7eed6d49a76ff66f6a13fb9d9f6e1cc9 Mon Sep 17 00:00:00 2001
From: Haled Odat <8566042+HalidOdat@users.noreply.github.com>
Date: Fri, 12 May 2023 01:38:00 +0200
Subject: [PATCH] Implement Inline Caching

 - Implement InternalMethodContext context wrapper
---
 boa_engine/src/builtins/array/mod.rs          |   7 +-
 boa_engine/src/builtins/function/mod.rs       |   5 +-
 boa_engine/src/builtins/function/tests.rs     |   7 +-
 boa_engine/src/builtins/json/mod.rs           |   6 +-
 .../src/builtins/object/for_in_iterator.rs    |  12 +-
 boa_engine/src/builtins/object/mod.rs         |  52 ++--
 boa_engine/src/builtins/reflect/mod.rs        |  44 ++-
 .../declaration/declaration_pattern.rs        |   9 +-
 .../src/bytecompiler/expression/assign.rs     |  10 +-
 boa_engine/src/bytecompiler/expression/mod.rs |   3 +-
 .../src/bytecompiler/expression/update.rs     |  10 +-
 boa_engine/src/bytecompiler/function.rs       |   2 +-
 boa_engine/src/bytecompiler/mod.rs            |  62 +++--
 boa_engine/src/context/mod.rs                 |  16 +-
 boa_engine/src/environments/runtime/mod.rs    |   7 +-
 .../src/object/internal_methods/arguments.rs  |  14 +-
 .../src/object/internal_methods/array.rs      |   8 +-
 .../object/internal_methods/bound_function.rs |   8 +-
 .../src/object/internal_methods/function.rs   |   8 +-
 .../internal_methods/immutable_prototype.rs   |   6 +-
 .../internal_methods/integer_indexed.rs       |  20 +-
 boa_engine/src/object/internal_methods/mod.rs | 261 ++++++++++++++----
 .../internal_methods/module_namespace.rs      |  32 ++-
 .../src/object/internal_methods/proxy.rs      |  52 ++--
 .../src/object/internal_methods/string.rs     |  10 +-
 boa_engine/src/object/jsobject.rs             |   6 +-
 boa_engine/src/object/operations.rs           | 107 +++++--
 boa_engine/src/object/property_map.rs         |  40 +++
 boa_engine/src/object/shape/slot.rs           |  33 +++
 boa_engine/src/value/tests.rs                 |  22 +-
 boa_engine/src/vm/code_block.rs               |  88 +++++-
 boa_engine/src/vm/flowgraph/mod.rs            |  14 +-
 boa_engine/src/vm/mod.rs                      |   8 +-
 boa_engine/src/vm/opcode/call/mod.rs          |  14 +-
 .../src/vm/opcode/define/class/getter.rs      |  17 +-
 .../src/vm/opcode/define/class/method.rs      |   8 +-
 .../src/vm/opcode/define/class/setter.rs      |  16 +-
 .../src/vm/opcode/define/own_property.rs      |   5 +-
 boa_engine/src/vm/opcode/delete/mod.rs        |  11 +-
 boa_engine/src/vm/opcode/environment/mod.rs   |  25 +-
 boa_engine/src/vm/opcode/get/property.rs      |  68 ++++-
 boa_engine/src/vm/opcode/mod.rs               |   4 +-
 boa_engine/src/vm/opcode/new/mod.rs           |   9 +-
 .../src/vm/opcode/push/class/private.rs       |   8 +-
 .../src/vm/opcode/set/class_prototype.rs      |   6 +-
 boa_engine/src/vm/opcode/set/private.rs       |   8 +-
 boa_engine/src/vm/opcode/set/property.rs      |  96 ++++++-
 boa_engine/src/vm/opcode/set/prototype.rs     |   3 +-
 48 files changed, 952 insertions(+), 335 deletions(-)

diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs
index 245b30a28e1..7f4c57197a2 100644
--- a/boa_engine/src/builtins/array/mod.rs
+++ b/boa_engine/src/builtins/array/mod.rs
@@ -20,7 +20,10 @@ use crate::{
     context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors},
     error::JsNativeError,
     js_string,
-    object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, CONSTRUCTOR},
+    object::{
+        internal_methods::{get_prototype_from_constructor, InternalMethodContext},
+        JsObject, ObjectData, CONSTRUCTOR,
+    },
     property::{Attribute, PropertyDescriptor, PropertyNameKind},
     realm::Realm,
     symbol::JsSymbol,
@@ -323,7 +326,7 @@ impl Array {
                 .enumerable(false)
                 .configurable(false)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
 
         Ok(array)
diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs
index 6e32a459fc1..cf0a3f2ecf9 100644
--- a/boa_engine/src/builtins/function/mod.rs
+++ b/boa_engine/src/builtins/function/mod.rs
@@ -20,7 +20,7 @@ use crate::{
     js_string,
     native_function::NativeFunction,
     object::{internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData},
-    object::{JsFunction, PrivateElement, PrivateName},
+    object::{internal_methods::InternalMethodContext, JsFunction, PrivateElement, PrivateName},
     property::{Attribute, PropertyDescriptor, PropertyKey},
     realm::Realm,
     string::utf16,
@@ -1100,7 +1100,8 @@ impl BoundFunction {
         context: &mut Context<'_>,
     ) -> JsResult<JsObject> {
         // 1. Let proto be ? targetFunction.[[GetPrototypeOf]]().
-        let proto = target_function.__get_prototype_of__(context)?;
+        let proto =
+            target_function.__get_prototype_of__(&mut InternalMethodContext::new(context))?;
         let is_constructor = target_function.is_constructor();
 
         // 2. Let internalSlotsList be the internal slots listed in Table 35, plus [[Prototype]] and [[Extensible]].
diff --git a/boa_engine/src/builtins/function/tests.rs b/boa_engine/src/builtins/function/tests.rs
index d5d87131fbd..baa6cd0a8c2 100644
--- a/boa_engine/src/builtins/function/tests.rs
+++ b/boa_engine/src/builtins/function/tests.rs
@@ -2,7 +2,7 @@ use crate::{
     error::JsNativeError,
     js_string,
     native_function::NativeFunction,
-    object::{FunctionObjectBuilder, JsObject},
+    object::{internal_methods::InternalMethodContext, FunctionObjectBuilder, JsObject},
     property::{Attribute, PropertyDescriptor},
     run_test_actions, JsNativeErrorKind, JsValue, TestAction,
 };
@@ -153,7 +153,10 @@ fn closure_capture_clone() {
                         let hw = js_string!(
                             string,
                             &object
-                                .__get_own_property__(&"key".into(), context)?
+                                .__get_own_property__(
+                                    &"key".into(),
+                                    &mut InternalMethodContext::new(context)
+                                )?
                                 .and_then(|prop| prop.value().cloned())
                                 .and_then(|val| val.as_string().cloned())
                                 .ok_or_else(
diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs
index 20b80fef102..75be7a62d7a 100644
--- a/boa_engine/src/builtins/json/mod.rs
+++ b/boa_engine/src/builtins/json/mod.rs
@@ -23,7 +23,7 @@ use crate::{
     context::intrinsics::Intrinsics,
     error::JsNativeError,
     js_string,
-    object::JsObject,
+    object::{internal_methods::InternalMethodContext, JsObject},
     property::{Attribute, PropertyNameKind},
     realm::Realm,
     string::{utf16, CodePoint},
@@ -191,7 +191,7 @@ impl Json {
                     // 3. If newElement is undefined, then
                     if new_element.is_undefined() {
                         // a. Perform ? val.[[Delete]](prop).
-                        obj.__delete__(&i.into(), context)?;
+                        obj.__delete__(&i.into(), &mut InternalMethodContext::new(context))?;
                     }
                     // 4. Else,
                     else {
@@ -220,7 +220,7 @@ impl Json {
                     // 2. If newElement is undefined, then
                     if new_element.is_undefined() {
                         // a. Perform ? val.[[Delete]](P).
-                        obj.__delete__(&p.into(), context)?;
+                        obj.__delete__(&p.into(), &mut InternalMethodContext::new(context))?;
                     }
                     // 3. Else,
                     else {
diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs
index 44af9f42f1c..0f2ef4d611b 100644
--- a/boa_engine/src/builtins/object/for_in_iterator.rs
+++ b/boa_engine/src/builtins/object/for_in_iterator.rs
@@ -12,7 +12,7 @@ use crate::{
     builtins::{iterable::create_iter_result_object, BuiltInBuilder, IntrinsicObject},
     context::intrinsics::Intrinsics,
     error::JsNativeError,
-    object::{JsObject, ObjectData},
+    object::{internal_methods::InternalMethodContext, JsObject, ObjectData},
     property::PropertyKey,
     realm::Realm,
     Context, JsResult, JsString, JsValue,
@@ -109,7 +109,8 @@ impl ForInIterator {
         let mut object = iterator.object.to_object(context)?;
         loop {
             if !iterator.object_was_visited {
-                let keys = object.__own_property_keys__(context)?;
+                let keys =
+                    object.__own_property_keys__(&mut InternalMethodContext::new(context))?;
                 for k in keys {
                     match k {
                         PropertyKey::String(ref k) => {
@@ -125,9 +126,10 @@ impl ForInIterator {
             }
             while let Some(r) = iterator.remaining_keys.pop_front() {
                 if !iterator.visited_keys.contains(&r) {
-                    if let Some(desc) =
-                        object.__get_own_property__(&PropertyKey::from(r.clone()), context)?
-                    {
+                    if let Some(desc) = object.__get_own_property__(
+                        &PropertyKey::from(r.clone()),
+                        &mut InternalMethodContext::new(context),
+                    )? {
                         iterator.visited_keys.insert(r.clone());
                         if desc.expect_enumerable() {
                             return Ok(create_iter_result_object(JsValue::new(r), false, context));
diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs
index 4c4fc5202a1..5a08aa7346a 100644
--- a/boa_engine/src/builtins/object/mod.rs
+++ b/boa_engine/src/builtins/object/mod.rs
@@ -23,8 +23,8 @@ use crate::{
     js_string,
     native_function::NativeFunction,
     object::{
-        internal_methods::get_prototype_from_constructor, FunctionObjectBuilder, IntegrityLevel,
-        JsObject, ObjectData, ObjectKind,
+        internal_methods::{get_prototype_from_constructor, InternalMethodContext},
+        FunctionObjectBuilder, IntegrityLevel, JsObject, ObjectData, ObjectKind,
     },
     property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind},
     realm::Realm,
@@ -181,7 +181,7 @@ impl Object {
         let obj = this.to_object(context)?;
 
         // 2. Return ? O.[[GetPrototypeOf]]().
-        let proto = obj.__get_prototype_of__(context)?;
+        let proto = obj.__get_prototype_of__(&mut InternalMethodContext::new(context))?;
 
         Ok(proto.map_or(JsValue::Null, JsValue::new))
     }
@@ -218,7 +218,8 @@ impl Object {
         };
 
         // 4. Let status be ? O.[[SetPrototypeOf]](proto).
-        let status = object.__set_prototype_of__(proto, context)?;
+        let status =
+            object.__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?;
 
         // 5. If status is false, throw a TypeError exception.
         if !status {
@@ -341,7 +342,8 @@ impl Object {
         // 3. Repeat
         loop {
             // a. Let desc be ? O.[[GetOwnProperty]](key).
-            let desc = obj.__get_own_property__(&key, context)?;
+
+            let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
 
             // b. If desc is not undefined, then
             if let Some(current_desc) = desc {
@@ -353,7 +355,7 @@ impl Object {
                     Ok(JsValue::undefined())
                 };
             }
-            match obj.__get_prototype_of__(context)? {
+            match obj.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
                 // c. Set O to ? O.[[GetPrototypeOf]]().
                 Some(o) => obj = o,
                 // d. If O is null, return undefined.
@@ -385,7 +387,8 @@ impl Object {
         // 3. Repeat
         loop {
             // a. Let desc be ? O.[[GetOwnProperty]](key).
-            let desc = obj.__get_own_property__(&key, context)?;
+
+            let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
 
             // b. If desc is not undefined, then
             if let Some(current_desc) = desc {
@@ -397,7 +400,7 @@ impl Object {
                     Ok(JsValue::undefined())
                 };
             }
-            match obj.__get_prototype_of__(context)? {
+            match obj.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
                 // c. Set O to ? O.[[GetPrototypeOf]]().
                 Some(o) => obj = o,
                 // d. If O is null, return undefined.
@@ -466,7 +469,8 @@ impl Object {
         let key = args.get_or_undefined(1).to_property_key(context)?;
 
         // 3. Let desc be ? obj.[[GetOwnProperty]](key).
-        let desc = obj.__get_own_property__(&key, context)?;
+
+        let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
 
         // 4. Return FromPropertyDescriptor(desc).
         Ok(Self::from_property_descriptor(desc, context))
@@ -491,7 +495,7 @@ impl Object {
         let obj = args.get_or_undefined(0).to_object(context)?;
 
         // 2. Let ownKeys be ? obj.[[OwnPropertyKeys]]().
-        let own_keys = obj.__own_property_keys__(context)?;
+        let own_keys = obj.__own_property_keys__(&mut InternalMethodContext::new(context))?;
 
         // 3. Let descriptors be OrdinaryObjectCreate(%Object.prototype%).
         let descriptors = JsObject::with_object_proto(context.intrinsics());
@@ -499,7 +503,8 @@ impl Object {
         // 4. For each element key of ownKeys, do
         for key in own_keys {
             // a. Let desc be ? obj.[[GetOwnProperty]](key).
-            let desc = obj.__get_own_property__(&key, context)?;
+
+            let desc = obj.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
 
             // b. Let descriptor be FromPropertyDescriptor(desc).
             let descriptor = Self::from_property_descriptor(desc, context);
@@ -612,7 +617,7 @@ impl Object {
 
         // 2. Return ? obj.[[GetPrototypeOf]]().
         Ok(obj
-            .__get_prototype_of__(context)?
+            .__get_prototype_of__(&mut InternalMethodContext::new(context))?
             .map_or(JsValue::Null, JsValue::new))
     }
 
@@ -663,7 +668,7 @@ impl Object {
         };
 
         // 4. Let status be ? O.[[SetPrototypeOf]](proto).
-        let status = obj.__set_prototype_of__(proto, context)?;
+        let status = obj.__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?;
 
         // 5. If status is false, throw a TypeError exception.
         if !status {
@@ -905,9 +910,10 @@ impl Object {
         };
 
         let key = key.to_property_key(context)?;
+
         let own_prop = this
             .to_object(context)?
-            .__get_own_property__(&key, context)?;
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
 
         own_prop
             .as_ref()
@@ -946,11 +952,14 @@ impl Object {
                     .to_object(context)
                     .expect("this ToObject call must not fail");
                 // 3.a.ii. Let keys be ? from.[[OwnPropertyKeys]]().
-                let keys = from.__own_property_keys__(context)?;
+                let keys = from.__own_property_keys__(&mut InternalMethodContext::new(context))?;
                 // 3.a.iii. For each element nextKey of keys, do
                 for key in keys {
                     // 3.a.iii.1. Let desc be ? from.[[GetOwnProperty]](nextKey).
-                    if let Some(desc) = from.__get_own_property__(&key, context)? {
+
+                    if let Some(desc) =
+                        from.__get_own_property__(&key, &mut InternalMethodContext::new(context))?
+                    {
                         // 3.a.iii.2. If desc is not undefined and desc.[[Enumerable]] is true, then
                         if desc.expect_enumerable() {
                             // 3.a.iii.2.a. Let propValue be ? Get(from, nextKey).
@@ -1169,7 +1178,7 @@ impl Object {
 
         if let Some(o) = o.as_object() {
             // 2. Let status be ? O.[[PreventExtensions]]().
-            let status = o.__prevent_extensions__(context)?;
+            let status = o.__prevent_extensions__(&mut InternalMethodContext::new(context))?;
             // 3. If status is false, throw a TypeError exception.
             if !status {
                 return Err(JsNativeError::typ()
@@ -1326,7 +1335,7 @@ fn object_define_properties(
     let props = &props.to_object(context)?;
 
     // 3. Let keys be ? props.[[OwnPropertyKeys]]().
-    let keys = props.__own_property_keys__(context)?;
+    let keys = props.__own_property_keys__(&mut InternalMethodContext::new(context))?;
 
     // 4. Let descriptors be a new empty List.
     let mut descriptors: Vec<(PropertyKey, PropertyDescriptor)> = Vec::new();
@@ -1335,7 +1344,10 @@ fn object_define_properties(
     for next_key in keys {
         // a. Let propDesc be ? props.[[GetOwnProperty]](nextKey).
         // b. If propDesc is not undefined and propDesc.[[Enumerable]] is true, then
-        if let Some(prop_desc) = props.__get_own_property__(&next_key, context)? {
+
+        if let Some(prop_desc) =
+            props.__get_own_property__(&next_key, &mut InternalMethodContext::new(context))?
+        {
             if prop_desc.expect_enumerable() {
                 // i. Let descObj be ? Get(props, nextKey).
                 let desc_obj = props.get(next_key.clone(), context)?;
@@ -1383,7 +1395,7 @@ fn get_own_property_keys(
     let obj = o.to_object(context)?;
 
     // 2. Let keys be ? obj.[[OwnPropertyKeys]]().
-    let keys = obj.__own_property_keys__(context)?;
+    let keys = obj.__own_property_keys__(&mut InternalMethodContext::new(context))?;
 
     // 3. Let nameList be a new empty List.
     // 4. For each element nextKey of keys, do
diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs
index a0b2807fe0d..1067c60e35a 100644
--- a/boa_engine/src/builtins/reflect/mod.rs
+++ b/boa_engine/src/builtins/reflect/mod.rs
@@ -15,7 +15,7 @@ use crate::{
     builtins::{self, BuiltInObject},
     context::intrinsics::Intrinsics,
     error::JsNativeError,
-    object::JsObject,
+    object::{internal_methods::InternalMethodContext, JsObject},
     property::Attribute,
     realm::Realm,
     symbol::JsSymbol,
@@ -168,7 +168,11 @@ impl Reflect {
             .into();
 
         target
-            .__define_own_property__(&key, prop_desc.to_property_descriptor(context)?, context)
+            .__define_own_property__(
+                &key,
+                prop_desc.to_property_descriptor(context)?,
+                &mut InternalMethodContext::new(context),
+            )
             .map(Into::into)
     }
 
@@ -191,7 +195,9 @@ impl Reflect {
             .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
         let key = args.get_or_undefined(1).to_property_key(context)?;
 
-        Ok(target.__delete__(&key, context)?.into())
+        Ok(target
+            .__delete__(&key, &mut InternalMethodContext::new(context))?
+            .into())
     }
 
     /// Gets a property of an object.
@@ -221,7 +227,8 @@ impl Reflect {
             .cloned()
             .unwrap_or_else(|| target.clone().into());
         // 4. Return ? target.[[Get]](key, receiver).
-        target.__get__(&key, receiver, context)
+
+        target.__get__(&key, receiver, &mut InternalMethodContext::new(context))
     }
 
     /// Gets a property of an object.
@@ -270,7 +277,7 @@ impl Reflect {
             .and_then(JsValue::as_object)
             .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
         Ok(target
-            .__get_prototype_of__(context)?
+            .__get_prototype_of__(&mut InternalMethodContext::new(context))?
             .map_or(JsValue::Null, JsValue::new))
     }
 
@@ -295,7 +302,10 @@ impl Reflect {
             .get(1)
             .unwrap_or(&JsValue::undefined())
             .to_property_key(context)?;
-        Ok(target.__has_property__(&key, context)?.into())
+
+        Ok(target
+            .__has_property__(&key, &mut InternalMethodContext::new(context))?
+            .into())
     }
 
     /// Returns `true` if the object is extensible, `false` otherwise.
@@ -315,7 +325,9 @@ impl Reflect {
             .get(0)
             .and_then(JsValue::as_object)
             .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
-        Ok(target.__is_extensible__(context)?.into())
+        Ok(target
+            .__is_extensible__(&mut InternalMethodContext::new(context))?
+            .into())
     }
 
     /// Returns an array of object own property keys.
@@ -337,7 +349,7 @@ impl Reflect {
             .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
 
         let keys: Vec<JsValue> = target
-            .__own_property_keys__(context)?
+            .__own_property_keys__(&mut InternalMethodContext::new(context))?
             .into_iter()
             .map(Into::into)
             .collect();
@@ -363,7 +375,9 @@ impl Reflect {
             .and_then(JsValue::as_object)
             .ok_or_else(|| JsNativeError::typ().with_message("target must be an object"))?;
 
-        Ok(target.__prevent_extensions__(context)?.into())
+        Ok(target
+            .__prevent_extensions__(&mut InternalMethodContext::new(context))?
+            .into())
     }
 
     /// Sets a property of an object.
@@ -389,8 +403,14 @@ impl Reflect {
             .get(3)
             .cloned()
             .unwrap_or_else(|| target.clone().into());
+
         Ok(target
-            .__set__(key, value.clone(), receiver, context)?
+            .__set__(
+                key,
+                value.clone(),
+                receiver,
+                &mut InternalMethodContext::new(context),
+            )?
             .into())
     }
 
@@ -420,6 +440,8 @@ impl Reflect {
                     .into())
             }
         };
-        Ok(target.__set_prototype_of__(proto, context)?.into())
+        Ok(target
+            .__set_prototype_of__(proto, &mut InternalMethodContext::new(context))?
+            .into())
     }
 }
diff --git a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
index 9801c63bd3d..a72bd5fb6cf 100644
--- a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
+++ b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
@@ -39,8 +39,7 @@ impl ByteCompiler<'_, '_> {
                             self.emit_opcode(Opcode::Dup);
                             match name {
                                 PropertyName::Literal(name) => {
-                                    let index = self.get_or_insert_name((*name).into());
-                                    self.emit(Opcode::GetPropertyByName, &[index]);
+                                    self.emit_get_property_by_name(*name);
                                 }
                                 PropertyName::Computed(node) => {
                                     self.compile_expr(node, true);
@@ -111,8 +110,7 @@ impl ByteCompiler<'_, '_> {
                             self.emit_opcode(Opcode::Dup);
                             match name {
                                 PropertyName::Literal(name) => {
-                                    let index = self.get_or_insert_name((*name).into());
-                                    self.emit(Opcode::GetPropertyByName, &[index]);
+                                    self.emit_get_property_by_name(*name);
                                 }
                                 PropertyName::Computed(node) => {
                                     self.compile_expr(node, true);
@@ -151,8 +149,7 @@ impl ByteCompiler<'_, '_> {
                             self.emit_opcode(Opcode::Dup);
                             match name {
                                 PropertyName::Literal(name) => {
-                                    let index = self.get_or_insert_name((*name).into());
-                                    self.emit(Opcode::GetPropertyByName, &[index]);
+                                    self.emit_get_property_by_name(*name);
                                 }
                                 PropertyName::Computed(node) => {
                                     self.compile_expr(node, true);
diff --git a/boa_engine/src/bytecompiler/expression/assign.rs b/boa_engine/src/bytecompiler/expression/assign.rs
index 4d8d0bc99c3..e4b85a6b7bf 100644
--- a/boa_engine/src/bytecompiler/expression/assign.rs
+++ b/boa_engine/src/bytecompiler/expression/assign.rs
@@ -96,13 +96,12 @@ impl ByteCompiler<'_, '_> {
                 Access::Property { access } => match access {
                     PropertyAccess::Simple(access) => match access.field() {
                         PropertyAccessField::Const(name) => {
-                            let index = self.get_or_insert_name((*name).into());
                             self.compile_expr(access.target(), true);
                             self.emit_opcode(Opcode::Dup);
                             self.emit_opcode(Opcode::Dup);
                             self.emit_opcode(Opcode::Dup);
 
-                            self.emit(Opcode::GetPropertyByName, &[index]);
+                            self.emit_get_property_by_name(*name);
                             if short_circuit {
                                 pop_count = 2;
                                 early_exit = Some(self.emit_opcode_with_operand(opcode));
@@ -112,7 +111,7 @@ impl ByteCompiler<'_, '_> {
                                 self.emit_opcode(opcode);
                             }
 
-                            self.emit(Opcode::SetPropertyByName, &[index]);
+                            self.emit_set_property_by_name(*name);
                             if !use_expr {
                                 self.emit_opcode(Opcode::Pop);
                             }
@@ -162,14 +161,13 @@ impl ByteCompiler<'_, '_> {
                     }
                     PropertyAccess::Super(access) => match access.field() {
                         PropertyAccessField::Const(name) => {
-                            let index = self.get_or_insert_name((*name).into());
                             self.emit_opcode(Opcode::Super);
                             self.emit_opcode(Opcode::Dup);
                             self.emit_opcode(Opcode::This);
                             self.emit_opcode(Opcode::Swap);
                             self.emit_opcode(Opcode::This);
 
-                            self.emit(Opcode::GetPropertyByName, &[index]);
+                            self.emit_get_property_by_name(*name);
                             if short_circuit {
                                 pop_count = 2;
                                 early_exit = Some(self.emit_opcode_with_operand(opcode));
@@ -179,7 +177,7 @@ impl ByteCompiler<'_, '_> {
                                 self.emit_opcode(opcode);
                             }
 
-                            self.emit(Opcode::SetPropertyByName, &[index]);
+                            self.emit_set_property_by_name(*name);
                             if !use_expr {
                                 self.emit_opcode(Opcode::Pop);
                             }
diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs
index ef118a5cfa0..9df154de4aa 100644
--- a/boa_engine/src/bytecompiler/expression/mod.rs
+++ b/boa_engine/src/bytecompiler/expression/mod.rs
@@ -217,8 +217,7 @@ impl ByteCompiler<'_, '_> {
                         self.emit_opcode(Opcode::Dup);
                         match access.field() {
                             PropertyAccessField::Const(field) => {
-                                let index = self.get_or_insert_name((*field).into());
-                                self.emit(Opcode::GetPropertyByName, &[index]);
+                                self.emit_get_property_by_name(*field);
                             }
                             PropertyAccessField::Expr(field) => {
                                 self.compile_expr(field, true);
diff --git a/boa_engine/src/bytecompiler/expression/update.rs b/boa_engine/src/bytecompiler/expression/update.rs
index 9e2ab13b2b8..42f8eed5ed0 100644
--- a/boa_engine/src/bytecompiler/expression/update.rs
+++ b/boa_engine/src/bytecompiler/expression/update.rs
@@ -61,20 +61,19 @@ impl ByteCompiler<'_, '_> {
             Access::Property { access } => match access {
                 PropertyAccess::Simple(access) => match access.field() {
                     PropertyAccessField::Const(name) => {
-                        let index = self.get_or_insert_name((*name).into());
                         self.compile_expr(access.target(), true);
                         self.emit_opcode(Opcode::Dup);
                         self.emit_opcode(Opcode::Dup);
                         self.emit_opcode(Opcode::Dup);
 
-                        self.emit(Opcode::GetPropertyByName, &[index]);
+                        self.emit_get_property_by_name(*name);
                         self.emit_opcode(opcode);
                         if post {
                             self.emit_opcode(Opcode::RotateRight);
                             self.emit_u8(4);
                         }
 
-                        self.emit(Opcode::SetPropertyByName, &[index]);
+                        self.emit_set_property_by_name(*name);
                         if post {
                             self.emit_opcode(Opcode::Pop);
                         }
@@ -118,21 +117,20 @@ impl ByteCompiler<'_, '_> {
                 }
                 PropertyAccess::Super(access) => match access.field() {
                     PropertyAccessField::Const(name) => {
-                        let index = self.get_or_insert_name((*name).into());
                         self.emit_opcode(Opcode::Super);
                         self.emit_opcode(Opcode::Dup);
                         self.emit_opcode(Opcode::This);
                         self.emit_opcode(Opcode::Swap);
                         self.emit_opcode(Opcode::This);
 
-                        self.emit(Opcode::GetPropertyByName, &[index]);
+                        self.emit_get_property_by_name(*name);
                         self.emit_opcode(opcode);
                         if post {
                             self.emit_opcode(Opcode::RotateRight);
                             self.emit_u8(3);
                         }
 
-                        self.emit(Opcode::SetPropertyByName, &[index]);
+                        self.emit_set_property_by_name(*name);
                         if post {
                             self.emit_opcode(Opcode::Pop);
                         }
diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs
index b64e963e6fd..417e2f663b5 100644
--- a/boa_engine/src/bytecompiler/function.rs
+++ b/boa_engine/src/bytecompiler/function.rs
@@ -154,7 +154,7 @@ impl FunctionCompiler {
         if compiler
             .bytecode
             .last()
-            .filter(|last| **last == Opcode::Return as u8)
+            .filter(|last| **last == (Opcode::Return as u8).into())
             .is_none()
         {
             compiler.emit_opcode(Opcode::Return);
diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs
index b372fb0a55c..ecb7ef8e28c 100644
--- a/boa_engine/src/bytecompiler/mod.rs
+++ b/boa_engine/src/bytecompiler/mod.rs
@@ -20,7 +20,7 @@ use crate::{
     builtins::function::ThisMode,
     environments::{BindingLocator, BindingLocatorError, CompileTimeEnvironment},
     js_string,
-    vm::{BindingOpcode, CodeBlock, CodeBlockFlags, Opcode},
+    vm::{BindingOpcode, CodeBlock, CodeBlockFlags, InlineCache, Opcode},
     Context, JsBigInt, JsString, JsValue,
 };
 use boa_ast::{
@@ -229,7 +229,7 @@ pub struct ByteCompiler<'ctx, 'host> {
     pub(crate) params: FormalParameterList,
 
     /// Bytecode
-    pub(crate) bytecode: Vec<u8>,
+    pub(crate) bytecode: Vec<Cell<u8>>,
 
     /// Literals
     pub(crate) literals: Vec<JsValue>,
@@ -251,6 +251,8 @@ pub struct ByteCompiler<'ctx, 'host> {
 
     pub(crate) code_block_flags: CodeBlockFlags,
 
+    pub(crate) ic: Vec<InlineCache>,
+
     literals_map: FxHashMap<Literal, u32>,
     names_map: FxHashMap<Identifier, u32>,
     bindings_map: FxHashMap<BindingLocator, u32>,
@@ -294,6 +296,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
             params: FormalParameterList::default(),
             compile_environments: Vec::default(),
             code_block_flags,
+            ic: Vec::default(),
 
             literals_map: FxHashMap::default(),
             names_map: FxHashMap::default(),
@@ -428,16 +431,36 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
         }
     }
 
+    fn emit_get_property_by_name(&mut self, ident: Sym) {
+        let ic_index = self.ic.len() as u32;
+
+        let name_index = self.get_or_insert_name(Identifier::new(ident));
+        let name = self.names[name_index as usize].clone();
+        self.ic.push(InlineCache::new(name));
+
+        self.emit(Opcode::GetPropertyByName, &[ic_index]);
+    }
+
+    fn emit_set_property_by_name(&mut self, ident: Sym) {
+        let ic_index = self.ic.len() as u32;
+
+        let name_index = self.get_or_insert_name(Identifier::new(ident));
+        let name = self.names[name_index as usize].clone();
+        self.ic.push(InlineCache::new(name));
+
+        self.emit(Opcode::SetPropertyByName, &[ic_index]);
+    }
+
     fn emit_u64(&mut self, value: u64) {
-        self.bytecode.extend(value.to_ne_bytes());
+        self.bytecode.extend(value.to_ne_bytes().map(Cell::new));
     }
 
     fn emit_u32(&mut self, value: u32) {
-        self.bytecode.extend(value.to_ne_bytes());
+        self.bytecode.extend(value.to_ne_bytes().map(Cell::new));
     }
 
     fn emit_u16(&mut self, value: u16) {
-        self.bytecode.extend(value.to_ne_bytes());
+        self.bytecode.extend(value.to_ne_bytes().map(Cell::new));
     }
 
     pub(crate) fn emit_opcode(&mut self, opcode: Opcode) {
@@ -445,7 +468,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
     }
 
     fn emit_u8(&mut self, value: u8) {
-        self.bytecode.push(value);
+        self.bytecode.push(Cell::new(value));
     }
 
     fn emit_push_integer(&mut self, value: i32) {
@@ -550,11 +573,11 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
         let Label { index } = label;
 
         let index = index as usize;
-        let bytes = target.to_ne_bytes();
+        let bytes = target.to_ne_bytes().map(Cell::new);
 
         // This is done to avoid unneeded bounds checks.
         assert!(self.bytecode.len() > index + U32_SIZE && usize::MAX - U32_SIZE >= index);
-        self.bytecode[index + 1..=index + U32_SIZE].copy_from_slice(bytes.as_slice());
+        self.bytecode[index + 1..=index + U32_SIZE].clone_from_slice(bytes.as_slice());
     }
 
     fn patch_jump(&mut self, label: Label) {
@@ -572,10 +595,9 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
             Access::Property { access } => match access {
                 PropertyAccess::Simple(access) => match access.field() {
                     PropertyAccessField::Const(name) => {
-                        let index = self.get_or_insert_name((*name).into());
                         self.compile_expr(access.target(), true);
                         self.emit_opcode(Opcode::Dup);
-                        self.emit(Opcode::GetPropertyByName, &[index]);
+                        self.emit_get_property_by_name(*name);
                     }
                     PropertyAccessField::Expr(expr) => {
                         self.compile_expr(access.target(), true);
@@ -591,10 +613,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
                 }
                 PropertyAccess::Super(access) => match access.field() {
                     PropertyAccessField::Const(field) => {
-                        let index = self.get_or_insert_name((*field).into());
                         self.emit_opcode(Opcode::Super);
                         self.emit_opcode(Opcode::This);
-                        self.emit(Opcode::GetPropertyByName, &[index]);
+
+                        self.emit_get_property_by_name(*field);
                     }
                     PropertyAccessField::Expr(expr) => {
                         self.emit_opcode(Opcode::Super);
@@ -668,9 +690,8 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
                         self.compile_expr(access.target(), true);
                         self.emit_opcode(Opcode::Dup);
                         expr_fn(self, 2);
-                        let index = self.get_or_insert_name((*name).into());
 
-                        self.emit(Opcode::SetPropertyByName, &[index]);
+                        self.emit_set_property_by_name(*name);
                         if !use_expr {
                             self.emit(Opcode::Pop, &[]);
                         }
@@ -700,8 +721,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
                         self.emit_opcode(Opcode::Super);
                         self.emit_opcode(Opcode::This);
                         expr_fn(self, 1);
-                        let index = self.get_or_insert_name((*name).into());
-                        self.emit(Opcode::SetPropertyByName, &[index]);
+                        self.emit_set_property_by_name(*name);
                         if !use_expr {
                             self.emit(Opcode::Pop, &[]);
                         }
@@ -818,8 +838,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
                 self.emit_opcode(Opcode::Dup);
                 match access.field() {
                     PropertyAccessField::Const(field) => {
-                        let index = self.get_or_insert_name((*field).into());
-                        self.emit(Opcode::GetPropertyByName, &[index]);
+                        self.emit_get_property_by_name(*field);
                     }
                     PropertyAccessField::Expr(field) => {
                         self.compile_expr(field, true);
@@ -839,8 +858,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
                 self.emit_opcode(Opcode::This);
                 match access.field() {
                     PropertyAccessField::Const(field) => {
-                        let index = self.get_or_insert_name((*field).into());
-                        self.emit(Opcode::GetPropertyByName, &[index]);
+                        self.emit_get_property_by_name(*field);
                     }
                     PropertyAccessField::Expr(expr) => {
                         self.compile_expr(expr, true);
@@ -925,8 +943,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
                 self.emit_opcode(Opcode::Dup);
                 match field {
                     PropertyAccessField::Const(name) => {
-                        let index = self.get_or_insert_name((*name).into());
-                        self.emit(Opcode::GetPropertyByName, &[index]);
+                        self.emit_get_property_by_name(*name);
                     }
                     PropertyAccessField::Expr(expr) => {
                         self.compile_expr(expr, true);
@@ -1402,6 +1419,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
             functions: self.functions.into_boxed_slice(),
             compile_environments: self.compile_environments.into_boxed_slice(),
             flags: Cell::new(self.code_block_flags),
+            ic: self.ic.into_boxed_slice(),
         }
     }
 
diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs
index 5c4ea4fc760..cbda51ecbfb 100644
--- a/boa_engine/src/context/mod.rs
+++ b/boa_engine/src/context/mod.rs
@@ -22,7 +22,9 @@ use crate::{
     job::{JobQueue, NativeJob, SimpleJobQueue},
     module::{IdleModuleLoader, ModuleLoader, SimpleModuleLoader},
     native_function::NativeFunction,
-    object::{shape::RootShape, FunctionObjectBuilder, JsObject},
+    object::{
+        internal_methods::InternalMethodContext, shape::RootShape, FunctionObjectBuilder, JsObject,
+    },
     optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
     property::{Attribute, PropertyDescriptor, PropertyKey},
     realm::Realm,
@@ -519,7 +521,9 @@ impl Context<'_> {
 
         // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
         let name = self.interner().resolve_expect(name.sym()).utf16().into();
-        let existing_prop = global_object.__get_own_property__(&name, self)?;
+
+        let existing_prop =
+            global_object.__get_own_property__(&name, &mut InternalMethodContext::new(self))?;
 
         // 4. If existingProp is undefined, return ? IsExtensible(globalObject).
         let Some(existing_prop) = existing_prop else {
@@ -629,7 +633,9 @@ impl Context<'_> {
 
         // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
         let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
-        let existing_prop = global_object.__get_own_property__(&name, self)?;
+
+        let existing_prop =
+            global_object.__get_own_property__(&name, &mut InternalMethodContext::new(self))?;
 
         // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then
         let desc = if existing_prop.is_none()
@@ -676,7 +682,9 @@ impl Context<'_> {
 
         // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
         let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
-        let existing_prop = global_object.__get_own_property__(&name, self)?;
+
+        let existing_prop =
+            global_object.__get_own_property__(&name, &mut InternalMethodContext::new(self))?;
 
         // 4. If existingProp is undefined, return false.
         let Some(existing_prop) = existing_prop else {
diff --git a/boa_engine/src/environments/runtime/mod.rs b/boa_engine/src/environments/runtime/mod.rs
index 0b09a4f06e8..9ab0ccf4569 100644
--- a/boa_engine/src/environments/runtime/mod.rs
+++ b/boa_engine/src/environments/runtime/mod.rs
@@ -2,7 +2,7 @@ use std::{cell::RefCell, rc::Rc};
 
 use crate::{
     environments::CompileTimeEnvironment,
-    object::{JsObject, PrivateName},
+    object::{internal_methods::InternalMethodContext, JsObject, PrivateName},
     Context, JsResult, JsString, JsSymbol, JsValue,
 };
 use boa_ast::expression::Identifier;
@@ -754,7 +754,8 @@ impl Context<'_> {
                 .interner()
                 .resolve_expect(locator.name().sym())
                 .into_common::<JsString>(false);
-            self.global_object().__delete__(&key.into(), self)
+            self.global_object()
+                .__delete__(&key.into(), &mut InternalMethodContext::new(self))
         } else {
             match self.environment_expect(locator.environment_index) {
                 Environment::Declarative(_) => Ok(false),
@@ -765,7 +766,7 @@ impl Context<'_> {
                         .resolve_expect(locator.name.sym())
                         .into_common(false);
 
-                    obj.__delete__(&key.into(), self)
+                    obj.__delete__(&key.into(), &mut InternalMethodContext::new(self))
                 }
             }
         }
diff --git a/boa_engine/src/object/internal_methods/arguments.rs b/boa_engine/src/object/internal_methods/arguments.rs
index bb919def58a..9c42e6d2b9d 100644
--- a/boa_engine/src/object/internal_methods/arguments.rs
+++ b/boa_engine/src/object/internal_methods/arguments.rs
@@ -1,10 +1,10 @@
 use crate::{
     object::JsObject,
     property::{DescriptorKind, PropertyDescriptor, PropertyKey},
-    Context, JsResult, JsValue,
+    JsResult, JsValue,
 };
 
-use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
+use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
 
 pub(crate) static ARGUMENTS_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
     InternalObjectMethods {
@@ -25,7 +25,7 @@ pub(crate) static ARGUMENTS_EXOTIC_INTERNAL_METHODS: InternalObjectMethods =
 pub(crate) fn arguments_exotic_get_own_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Option<PropertyDescriptor>> {
     // 1. Let desc be OrdinaryGetOwnProperty(args, P).
     // 2. If desc is undefined, return desc.
@@ -70,7 +70,7 @@ pub(crate) fn arguments_exotic_define_own_property(
     obj: &JsObject,
     key: &PropertyKey,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 2. Let isMapped be HasOwnProperty(map, P).
     let mapped = if let &PropertyKey::Index(index) = key {
@@ -161,7 +161,7 @@ pub(crate) fn arguments_exotic_get(
     obj: &JsObject,
     key: &PropertyKey,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
     if let PropertyKey::Index(index) = key {
         // 1. Let map be args.[[ParameterMap]].
@@ -194,7 +194,7 @@ pub(crate) fn arguments_exotic_set(
     key: PropertyKey,
     value: JsValue,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If SameValue(args, Receiver) is false, then
     // a. Let isMapped be false.
@@ -226,7 +226,7 @@ pub(crate) fn arguments_exotic_set(
 pub(crate) fn arguments_exotic_delete(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 3. Let result be ? OrdinaryDelete(args, P).
     let result = super::ordinary_delete(obj, key, context)?;
diff --git a/boa_engine/src/object/internal_methods/array.rs b/boa_engine/src/object/internal_methods/array.rs
index 6e095174f51..af7eee0b2b3 100644
--- a/boa_engine/src/object/internal_methods/array.rs
+++ b/boa_engine/src/object/internal_methods/array.rs
@@ -3,10 +3,10 @@ use crate::{
     object::JsObject,
     property::{PropertyDescriptor, PropertyKey},
     string::utf16,
-    Context, JsResult,
+    JsResult,
 };
 
-use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
+use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
 
 /// Definitions of the internal object methods for array exotic objects.
 ///
@@ -29,7 +29,7 @@ pub(crate) fn array_exotic_define_own_property(
     obj: &JsObject,
     key: &PropertyKey,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Assert: IsPropertyKey(P) is true.
     match *key {
@@ -109,7 +109,7 @@ pub(crate) fn array_exotic_define_own_property(
 fn array_set_length(
     obj: &JsObject,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If Desc.[[Value]] is absent, then
     let Some(new_len_val) = desc.value() else {
diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs
index 8fe52973561..6bd3ec9de39 100644
--- a/boa_engine/src/object/internal_methods/bound_function.rs
+++ b/boa_engine/src/object/internal_methods/bound_function.rs
@@ -1,6 +1,6 @@
-use crate::{object::JsObject, Context, JsResult, JsValue};
+use crate::{object::JsObject, JsResult, JsValue};
 
-use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
+use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
 
 /// Definitions of the internal object methods for function objects.
 ///
@@ -32,7 +32,7 @@ fn bound_function_exotic_call(
     obj: &JsObject,
     _: &JsValue,
     arguments_list: &[JsValue],
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
     let obj = obj.borrow();
     let bound_function = obj
@@ -67,7 +67,7 @@ fn bound_function_exotic_construct(
     obj: &JsObject,
     arguments_list: &[JsValue],
     new_target: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsObject> {
     let object = obj.borrow();
     let bound_function = object
diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs
index 2b32875a841..57b30d9e42a 100644
--- a/boa_engine/src/object/internal_methods/function.rs
+++ b/boa_engine/src/object/internal_methods/function.rs
@@ -3,9 +3,11 @@ use crate::{
         internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
         JsObject,
     },
-    Context, JsResult, JsValue,
+    JsResult, JsValue,
 };
 
+use super::InternalMethodContext;
+
 /// Definitions of the internal object methods for function objects.
 ///
 /// More information:
@@ -36,7 +38,7 @@ fn function_call(
     obj: &JsObject,
     this: &JsValue,
     args: &[JsValue],
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
     obj.call_internal(this, args, context)
 }
@@ -52,7 +54,7 @@ fn function_construct(
     obj: &JsObject,
     args: &[JsValue],
     new_target: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsObject> {
     obj.construct_internal(args, &new_target.clone().into(), context)
 }
diff --git a/boa_engine/src/object/internal_methods/immutable_prototype.rs b/boa_engine/src/object/internal_methods/immutable_prototype.rs
index f9cf56c7410..e7bca73421e 100644
--- a/boa_engine/src/object/internal_methods/immutable_prototype.rs
+++ b/boa_engine/src/object/internal_methods/immutable_prototype.rs
@@ -1,9 +1,9 @@
 use crate::{
     object::{JsObject, JsPrototype},
-    Context, JsResult,
+    JsResult,
 };
 
-use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
+use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
 
 /// Definitions of the internal object methods for [**Immutable Prototype Exotic Objects**][spec].
 ///
@@ -21,7 +21,7 @@ pub(crate) static IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS: InternalObjectMet
 pub(crate) fn immutable_prototype_exotic_set_prototype_of(
     obj: &JsObject,
     val: JsPrototype,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Return ? SetImmutablePrototype(O, V).
 
diff --git a/boa_engine/src/object/internal_methods/integer_indexed.rs b/boa_engine/src/object/internal_methods/integer_indexed.rs
index e5fb7909db9..374af9d50ff 100644
--- a/boa_engine/src/object/internal_methods/integer_indexed.rs
+++ b/boa_engine/src/object/internal_methods/integer_indexed.rs
@@ -4,10 +4,10 @@ use crate::{
     builtins::{array_buffer::SharedMemoryOrder, typed_array::integer_indexed_object::ContentType},
     object::JsObject,
     property::{PropertyDescriptor, PropertyKey},
-    Context, JsResult, JsValue,
+    JsResult, JsValue,
 };
 
-use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
+use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
 
 /// Definitions of the internal object methods for integer-indexed exotic objects.
 ///
@@ -36,7 +36,7 @@ pub(crate) static INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS: InternalObjectMethods
 pub(crate) fn integer_indexed_exotic_get_own_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Option<PropertyDescriptor>> {
     // 1. If Type(P) is String, then
     // a. Let numericIndex be ! CanonicalNumericIndexString(P).
@@ -76,7 +76,7 @@ pub(crate) fn integer_indexed_exotic_get_own_property(
 pub(crate) fn integer_indexed_exotic_has_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If Type(P) is String, then
     // a. Let numericIndex be ! CanonicalNumericIndexString(P).
@@ -105,7 +105,7 @@ pub(crate) fn integer_indexed_exotic_define_own_property(
     obj: &JsObject,
     key: &PropertyKey,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If Type(P) is String, then
     // a. Let numericIndex be ! CanonicalNumericIndexString(P).
@@ -154,7 +154,7 @@ pub(crate) fn integer_indexed_exotic_get(
     obj: &JsObject,
     key: &PropertyKey,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
     // 1. If Type(P) is String, then
     // a. Let numericIndex be ! CanonicalNumericIndexString(P).
@@ -185,7 +185,7 @@ pub(crate) fn integer_indexed_exotic_set(
     key: PropertyKey,
     value: JsValue,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If Type(P) is String, then
     // a. Let numericIndex be ! CanonicalNumericIndexString(P).
@@ -217,7 +217,7 @@ pub(crate) fn integer_indexed_exotic_set(
 pub(crate) fn integer_indexed_exotic_delete(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If Type(P) is String, then
     // a. Let numericIndex be ! CanonicalNumericIndexString(P).
@@ -261,7 +261,7 @@ pub(crate) fn integer_indexed_exotic_delete(
 #[allow(clippy::unnecessary_wraps)]
 pub(crate) fn integer_indexed_exotic_own_property_keys(
     obj: &JsObject,
-    _context: &mut Context<'_>,
+    _context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Vec<PropertyKey>> {
     let obj = obj.borrow();
     let inner = obj.as_typed_array().expect(
@@ -375,7 +375,7 @@ fn integer_indexed_element_set(
     obj: &JsObject,
     index: usize,
     value: &JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<()> {
     let obj_borrow = obj.borrow();
     let inner = obj_borrow.as_typed_array().expect(
diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs
index 976f2824226..b63ac3e1a4f 100644
--- a/boa_engine/src/object/internal_methods/mod.rs
+++ b/boa_engine/src/object/internal_methods/mod.rs
@@ -5,7 +5,12 @@
 //!
 //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
 
-use super::{JsPrototype, PROTOTYPE};
+use std::ops::{Deref, DerefMut};
+
+use super::{
+    shape::slot::{Slot, SlotAttributes},
+    JsPrototype, PROTOTYPE,
+};
 use crate::{
     context::intrinsics::{StandardConstructor, StandardConstructors},
     object::JsObject,
@@ -27,6 +32,54 @@ pub(super) mod string;
 
 pub(crate) use array::ARRAY_EXOTIC_INTERNAL_METHODS;
 
+/// A lightweight wrapper around [`Context`] used in [`InternalObjectMethods`].
+#[derive(Debug)]
+pub(crate) struct InternalMethodContext<'ctx, 'host> {
+    context: &'ctx mut Context<'host>,
+    slot: Slot,
+}
+
+impl<'ctx, 'host> InternalMethodContext<'ctx, 'host> {
+    /// Create a new [`InternalMethodContext`].
+    pub(crate) fn new(context: &'ctx mut Context<'host>) -> Self {
+        Self {
+            context,
+            slot: Slot::new(),
+        }
+    }
+
+    /// Gets the [`Slot`] associated with this [`InternalMethodContext`].
+    #[inline]
+    pub(crate) fn slot(&mut self) -> &mut Slot {
+        &mut self.slot
+    }
+}
+
+impl<'host> Deref for InternalMethodContext<'_, 'host> {
+    type Target = Context<'host>;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        self.context
+    }
+}
+
+impl DerefMut for InternalMethodContext<'_, '_> {
+    #[inline]
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.context
+    }
+}
+
+impl<'context, 'host> From<&'context mut Context<'host>>
+    for InternalMethodContext<'context, 'host>
+{
+    #[inline]
+    fn from(context: &'context mut Context<'host>) -> Self {
+        Self::new(context)
+    }
+}
+
 impl JsObject {
     /// Internal method `[[GetPrototypeOf]]`
     ///
@@ -37,7 +90,10 @@ impl JsObject {
     ///
     /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof
     #[track_caller]
-    pub(crate) fn __get_prototype_of__(&self, context: &mut Context<'_>) -> JsResult<JsPrototype> {
+    pub(crate) fn __get_prototype_of__(
+        &self,
+        context: &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<JsPrototype> {
         let _timer = Profiler::global().start_event("Object::__get_prototype_of__", "object");
         (self.vtable().__get_prototype_of__)(self, context)
     }
@@ -53,7 +109,7 @@ impl JsObject {
     pub(crate) fn __set_prototype_of__(
         &self,
         val: JsPrototype,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<bool> {
         let _timer = Profiler::global().start_event("Object::__set_prototype_of__", "object");
         (self.vtable().__set_prototype_of__)(self, val, context)
@@ -67,7 +123,10 @@ impl JsObject {
     ///  - [ECMAScript reference][spec]
     ///
     /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible
-    pub(crate) fn __is_extensible__(&self, context: &mut Context<'_>) -> JsResult<bool> {
+    pub(crate) fn __is_extensible__(
+        &self,
+        context: &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<bool> {
         let _timer = Profiler::global().start_event("Object::__is_extensible__", "object");
         (self.vtable().__is_extensible__)(self, context)
     }
@@ -80,7 +139,10 @@ impl JsObject {
     ///  - [ECMAScript reference][spec]
     ///
     /// [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions
-    pub(crate) fn __prevent_extensions__(&self, context: &mut Context<'_>) -> JsResult<bool> {
+    pub(crate) fn __prevent_extensions__(
+        &self,
+        context: &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<bool> {
         let _timer = Profiler::global().start_event("Object::__prevent_extensions__", "object");
         (self.vtable().__prevent_extensions__)(self, context)
     }
@@ -96,7 +158,7 @@ impl JsObject {
     pub(crate) fn __get_own_property__(
         &self,
         key: &PropertyKey,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<Option<PropertyDescriptor>> {
         let _timer = Profiler::global().start_event("Object::__get_own_property__", "object");
         (self.vtable().__get_own_property__)(self, key, context)
@@ -114,7 +176,7 @@ impl JsObject {
         &self,
         key: &PropertyKey,
         desc: PropertyDescriptor,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<bool> {
         let _timer = Profiler::global().start_event("Object::__define_own_property__", "object");
         (self.vtable().__define_own_property__)(self, key, desc, context)
@@ -131,7 +193,7 @@ impl JsObject {
     pub(crate) fn __has_property__(
         &self,
         key: &PropertyKey,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<bool> {
         let _timer = Profiler::global().start_event("Object::__has_property__", "object");
         (self.vtable().__has_property__)(self, key, context)
@@ -149,7 +211,7 @@ impl JsObject {
         &self,
         key: &PropertyKey,
         receiver: JsValue,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<JsValue> {
         let _timer = Profiler::global().start_event("Object::__get__", "object");
         (self.vtable().__get__)(self, key, receiver, context)
@@ -168,7 +230,7 @@ impl JsObject {
         key: PropertyKey,
         value: JsValue,
         receiver: JsValue,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<bool> {
         let _timer = Profiler::global().start_event("Object::__set__", "object");
         (self.vtable().__set__)(self, key, value, receiver, context)
@@ -185,7 +247,7 @@ impl JsObject {
     pub(crate) fn __delete__(
         &self,
         key: &PropertyKey,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<bool> {
         let _timer = Profiler::global().start_event("Object::__delete__", "object");
         (self.vtable().__delete__)(self, key, context)
@@ -202,7 +264,7 @@ impl JsObject {
     #[track_caller]
     pub(crate) fn __own_property_keys__(
         &self,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<Vec<PropertyKey>> {
         let _timer = Profiler::global().start_event("Object::__own_property_keys__", "object");
         (self.vtable().__own_property_keys__)(self, context)
@@ -221,7 +283,7 @@ impl JsObject {
         &self,
         this: &JsValue,
         args: &[JsValue],
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<JsValue> {
         let _timer = Profiler::global().start_event("Object::__call__", "object");
         self.vtable()
@@ -244,7 +306,7 @@ impl JsObject {
         &self,
         args: &[JsValue],
         new_target: &Self,
-        context: &mut Context<'_>,
+        context: &mut InternalMethodContext<'_, '_>,
     ) -> JsResult<Self> {
         let _timer = Profiler::global().start_event("Object::__construct__", "object");
         self.vtable()
@@ -292,24 +354,60 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj
 #[derive(Clone, Copy)]
 #[allow(clippy::type_complexity)]
 pub(crate) struct InternalObjectMethods {
-    pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context<'_>) -> JsResult<JsPrototype>,
-    pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &mut Context<'_>) -> JsResult<bool>,
-    pub(crate) __is_extensible__: fn(&JsObject, &mut Context<'_>) -> JsResult<bool>,
-    pub(crate) __prevent_extensions__: fn(&JsObject, &mut Context<'_>) -> JsResult<bool>,
-    pub(crate) __get_own_property__:
-        fn(&JsObject, &PropertyKey, &mut Context<'_>) -> JsResult<Option<PropertyDescriptor>>,
-    pub(crate) __define_own_property__:
-        fn(&JsObject, &PropertyKey, PropertyDescriptor, &mut Context<'_>) -> JsResult<bool>,
-    pub(crate) __has_property__: fn(&JsObject, &PropertyKey, &mut Context<'_>) -> JsResult<bool>,
-    pub(crate) __get__: fn(&JsObject, &PropertyKey, JsValue, &mut Context<'_>) -> JsResult<JsValue>,
-    pub(crate) __set__:
-        fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context<'_>) -> JsResult<bool>,
-    pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context<'_>) -> JsResult<bool>,
-    pub(crate) __own_property_keys__: fn(&JsObject, &mut Context<'_>) -> JsResult<Vec<PropertyKey>>,
-    pub(crate) __call__:
-        Option<fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>>,
-    pub(crate) __construct__:
-        Option<fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult<JsObject>>,
+    pub(crate) __get_prototype_of__:
+        fn(&JsObject, &mut InternalMethodContext<'_, '_>) -> JsResult<JsPrototype>,
+    pub(crate) __set_prototype_of__:
+        fn(&JsObject, JsPrototype, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>,
+    pub(crate) __is_extensible__:
+        fn(&JsObject, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>,
+    pub(crate) __prevent_extensions__:
+        fn(&JsObject, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>,
+    pub(crate) __get_own_property__: fn(
+        &JsObject,
+        &PropertyKey,
+        &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<Option<PropertyDescriptor>>,
+    pub(crate) __define_own_property__: fn(
+        &JsObject,
+        &PropertyKey,
+        PropertyDescriptor,
+        &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<bool>,
+    pub(crate) __has_property__:
+        fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>,
+    pub(crate) __get__: fn(
+        &JsObject,
+        &PropertyKey,
+        JsValue,
+        &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<JsValue>,
+    pub(crate) __set__: fn(
+        &JsObject,
+        PropertyKey,
+        JsValue,
+        JsValue,
+        &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<bool>,
+    pub(crate) __delete__:
+        fn(&JsObject, &PropertyKey, &mut InternalMethodContext<'_, '_>) -> JsResult<bool>,
+    pub(crate) __own_property_keys__:
+        fn(&JsObject, &mut InternalMethodContext<'_, '_>) -> JsResult<Vec<PropertyKey>>,
+    pub(crate) __call__: Option<
+        fn(
+            &JsObject,
+            &JsValue,
+            &[JsValue],
+            &mut InternalMethodContext<'_, '_>,
+        ) -> JsResult<JsValue>,
+    >,
+    pub(crate) __construct__: Option<
+        fn(
+            &JsObject,
+            &[JsValue],
+            &JsObject,
+            &mut InternalMethodContext<'_, '_>,
+        ) -> JsResult<JsObject>,
+    >,
 }
 
 /// Abstract operation `OrdinaryGetPrototypeOf`.
@@ -321,7 +419,7 @@ pub(crate) struct InternalObjectMethods {
 #[allow(clippy::unnecessary_wraps)]
 pub(crate) fn ordinary_get_prototype_of(
     obj: &JsObject,
-    _context: &mut Context<'_>,
+    _context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsPrototype> {
     let _timer = Profiler::global().start_event("Object::ordinary_get_prototype_of", "object");
 
@@ -339,7 +437,7 @@ pub(crate) fn ordinary_get_prototype_of(
 pub(crate) fn ordinary_set_prototype_of(
     obj: &JsObject,
     val: JsPrototype,
-    _: &mut Context<'_>,
+    _: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Assert: Either Type(V) is Object or Type(V) is Null.
     // 2. Let current be O.[[Prototype]].
@@ -392,7 +490,10 @@ pub(crate) fn ordinary_set_prototype_of(
 ///
 /// [spec]: https://tc39.es/ecma262/#sec-ordinaryisextensible
 #[allow(clippy::unnecessary_wraps)]
-pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context<'_>) -> JsResult<bool> {
+pub(crate) fn ordinary_is_extensible(
+    obj: &JsObject,
+    _context: &mut InternalMethodContext<'_, '_>,
+) -> JsResult<bool> {
     // 1. Return O.[[Extensible]].
     Ok(obj.borrow().extensible)
 }
@@ -406,7 +507,7 @@ pub(crate) fn ordinary_is_extensible(obj: &JsObject, _context: &mut Context<'_>)
 #[allow(clippy::unnecessary_wraps)]
 pub(crate) fn ordinary_prevent_extensions(
     obj: &JsObject,
-    _context: &mut Context<'_>,
+    _context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Set O.[[Extensible]] to false.
     obj.borrow_mut().extensible = false;
@@ -425,7 +526,7 @@ pub(crate) fn ordinary_prevent_extensions(
 pub(crate) fn ordinary_get_own_property(
     obj: &JsObject,
     key: &PropertyKey,
-    _context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Option<PropertyDescriptor>> {
     let _timer = Profiler::global().start_event("Object::ordinary_get_own_property", "object");
     // 1. Assert: IsPropertyKey(P) is true.
@@ -442,7 +543,7 @@ pub(crate) fn ordinary_get_own_property(
     // 7. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
     // 8. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
     // 9. Return D.
-    Ok(obj.borrow().properties.get(key))
+    Ok(obj.borrow().properties.get_with_slot(key, context.slot()))
 }
 
 /// Abstract operation `OrdinaryDefineOwnProperty`.
@@ -455,9 +556,10 @@ pub(crate) fn ordinary_define_own_property(
     obj: &JsObject,
     key: &PropertyKey,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     let _timer = Profiler::global().start_event("Object::ordinary_define_own_property", "object");
+
     // 1. Let current be ? O.[[GetOwnProperty]](P).
     let current = obj.__get_own_property__(key, context)?;
 
@@ -470,6 +572,7 @@ pub(crate) fn ordinary_define_own_property(
         extensible,
         desc,
         current,
+        context.slot(),
     ))
 }
 
@@ -482,18 +585,23 @@ pub(crate) fn ordinary_define_own_property(
 pub(crate) fn ordinary_has_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     let _timer = Profiler::global().start_event("Object::ordinary_has_property", "object");
     // 1. Assert: IsPropertyKey(P) is true.
     // 2. Let hasOwn be ? O.[[GetOwnProperty]](P).
     // 3. If hasOwn is not undefined, return true.
     if obj.__get_own_property__(key, context)?.is_some() {
+        context.slot().attributes |= SlotAttributes::FOUND;
+
         Ok(true)
     } else {
         // 4. Let parent be ? O.[[GetPrototypeOf]]().
         let parent = obj.__get_prototype_of__(context)?;
 
+        context.slot().set_not_cachable_if_already_prototype();
+        context.slot().attributes |= SlotAttributes::PROTOTYPE;
+
         parent
             // 5. If parent is not null, then
             // a. Return ? parent.[[HasProperty]](P).
@@ -512,7 +620,7 @@ pub(crate) fn ordinary_get(
     obj: &JsObject,
     key: &PropertyKey,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
     let _timer = Profiler::global().start_event("Object::ordinary_get", "object");
     // 1. Assert: IsPropertyKey(P) is true.
@@ -522,6 +630,9 @@ pub(crate) fn ordinary_get(
         None => {
             // a. Let parent be ? O.[[GetPrototypeOf]]().
             if let Some(parent) = obj.__get_prototype_of__(context)? {
+                context.slot().set_not_cachable_if_already_prototype();
+                context.slot().attributes |= SlotAttributes::PROTOTYPE;
+
                 // c. Return ? parent.[[Get]](P, Receiver).
                 parent.__get__(key, receiver, context)
             }
@@ -530,20 +641,24 @@ pub(crate) fn ordinary_get(
                 Ok(JsValue::undefined())
             }
         }
-        Some(ref desc) => match desc.kind() {
-            // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
-            DescriptorKind::Data {
-                value: Some(value), ..
-            } => Ok(value.clone()),
-            // 5. Assert: IsAccessorDescriptor(desc) is true.
-            // 6. Let getter be desc.[[Get]].
-            DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
-                // 8. Return ? Call(getter, Receiver).
-                get.call(&receiver, &[], context)
+        Some(ref desc) => {
+            context.slot().attributes |= SlotAttributes::FOUND;
+
+            match desc.kind() {
+                // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]].
+                DescriptorKind::Data {
+                    value: Some(value), ..
+                } => Ok(value.clone()),
+                // 5. Assert: IsAccessorDescriptor(desc) is true.
+                // 6. Let getter be desc.[[Get]].
+                DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => {
+                    // 8. Return ? Call(getter, Receiver).
+                    get.call(&receiver, &[], context)
+                }
+                // 7. If getter is undefined, return undefined.
+                _ => Ok(JsValue::undefined()),
             }
-            // 7. If getter is undefined, return undefined.
-            _ => Ok(JsValue::undefined()),
-        },
+        }
     }
 }
 
@@ -558,7 +673,7 @@ pub(crate) fn ordinary_set(
     key: PropertyKey,
     value: JsValue,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     let _timer = Profiler::global().start_event("Object::ordinary_set", "object");
 
@@ -577,11 +692,20 @@ pub(crate) fn ordinary_set(
     // a. Let parent be ? O.[[GetPrototypeOf]]().
     // b. If parent is not null, then
     else if let Some(parent) = obj.__get_prototype_of__(context)? {
+        context.slot().set_not_cachable_if_already_prototype();
+        context.slot().attributes |= SlotAttributes::PROTOTYPE;
+
         // i. Return ? parent.[[Set]](P, V, Receiver).
         return parent.__set__(key, value, receiver, context);
     }
     // c. Else,
     else {
+        // It's not on prototype chain.
+        context
+            .slot()
+            .attributes
+            .remove(SlotAttributes::PROTOTYPE | SlotAttributes::NOT_CACHABLE);
+
         // i. Set ownDesc to the PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true,
         // [[Enumerable]]: true, [[Configurable]]: true }.
         PropertyDescriptor::builder()
@@ -604,6 +728,12 @@ pub(crate) fn ordinary_set(
             return Ok(false);
         };
 
+        // NOTE(HaledOdat): If the object and receiver are not the same then it's not inline cachable for now.
+        context.slot().attributes.set(
+            SlotAttributes::NOT_CACHABLE,
+            !JsObject::equals(obj, receiver),
+        );
+
         // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P).
         // d. If existingDescriptor is not undefined, then
         if let Some(ref existing_desc) = receiver.__get_own_property__(&key, context)? {
@@ -625,12 +755,15 @@ pub(crate) fn ordinary_set(
                 context,
             );
         }
+
         // e. Else
         // i. Assert: Receiver does not currently have a property P.
         // ii. Return ? CreateDataProperty(Receiver, P, V).
-        return receiver.create_data_property(key, value, context);
+        return receiver.create_data_property_with_slot(key, value, context);
     }
 
+    context.slot().attributes |= SlotAttributes::FOUND;
+
     // 4. Assert: IsAccessorDescriptor(ownDesc) is true.
     debug_assert!(own_desc.is_accessor_descriptor());
 
@@ -657,7 +790,7 @@ pub(crate) fn ordinary_set(
 pub(crate) fn ordinary_delete(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     let _timer = Profiler::global().start_event("Object::ordinary_delete", "object");
     // 1. Assert: IsPropertyKey(P) is true.
@@ -688,7 +821,7 @@ pub(crate) fn ordinary_delete(
 #[allow(clippy::unnecessary_wraps)]
 pub(crate) fn ordinary_own_property_keys(
     obj: &JsObject,
-    _context: &mut Context<'_>,
+    _context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Vec<PropertyKey>> {
     let _timer = Profiler::global().start_event("Object::ordinary_own_property_keys", "object");
     // 1. Let keys be a new empty List.
@@ -730,7 +863,7 @@ pub(crate) fn is_compatible_property_descriptor(
         Profiler::global().start_event("Object::is_compatible_property_descriptor", "object");
 
     // 1. Return ValidateAndApplyPropertyDescriptor(undefined, undefined, Extensible, Desc, Current).
-    validate_and_apply_property_descriptor(None, extensible, desc, current)
+    validate_and_apply_property_descriptor(None, extensible, desc, current, &mut Slot::new())
 }
 
 /// Abstract operation `ValidateAndApplyPropertyDescriptor`
@@ -744,6 +877,7 @@ pub(crate) fn validate_and_apply_property_descriptor(
     extensible: bool,
     desc: PropertyDescriptor,
     current: Option<PropertyDescriptor>,
+    slot: &mut Slot,
 ) -> bool {
     let _timer =
         Profiler::global().start_event("Object::validate_and_apply_property_descriptor", "object");
@@ -759,7 +893,7 @@ pub(crate) fn validate_and_apply_property_descriptor(
         // b. Assert: extensible is true.
 
         if let Some((obj, key)) = obj_and_key {
-            obj.borrow_mut().properties.insert(
+            obj.borrow_mut().properties.insert_with_slot(
                 key,
                 // c. If IsGenericDescriptor(Desc) is true or IsDataDescriptor(Desc) is true, then
                 if desc.is_generic_descriptor() || desc.is_data_descriptor() {
@@ -781,7 +915,9 @@ pub(crate) fn validate_and_apply_property_descriptor(
                     // its default value.
                     desc.into_accessor_defaulted()
                 },
+                slot,
             );
+            slot.attributes |= SlotAttributes::FOUND;
         }
 
         // e. Return true.
@@ -877,7 +1013,10 @@ pub(crate) fn validate_and_apply_property_descriptor(
         // a. For each field of Desc that is present, set the corresponding attribute of the
         // property named P of object O to the value of the field.
         current.fill_with(&desc);
-        obj.borrow_mut().properties.insert(key, current);
+        obj.borrow_mut()
+            .properties
+            .insert_with_slot(key, current, slot);
+        slot.attributes |= SlotAttributes::FOUND;
     }
 
     // 10. Return true.
diff --git a/boa_engine/src/object/internal_methods/module_namespace.rs b/boa_engine/src/object/internal_methods/module_namespace.rs
index 1575c8c10be..1ee0ab4450d 100644
--- a/boa_engine/src/object/internal_methods/module_namespace.rs
+++ b/boa_engine/src/object/internal_methods/module_namespace.rs
@@ -5,13 +5,13 @@ use crate::{
     module::BindingName,
     object::{JsObject, JsPrototype},
     property::{PropertyDescriptor, PropertyKey},
-    Context, JsNativeError, JsResult, JsValue,
+    JsNativeError, JsResult, JsValue,
 };
 
 use super::{
     immutable_prototype, ordinary_define_own_property, ordinary_delete, ordinary_get,
     ordinary_get_own_property, ordinary_has_property, ordinary_own_property_keys,
-    InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
+    InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS,
 };
 
 /// Definitions of the internal object methods for [**Module Namespace Exotic Objects**][spec].
@@ -39,7 +39,7 @@ pub(crate) static MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS: InternalObjectMethod
 #[allow(clippy::unnecessary_wraps)]
 fn module_namespace_exotic_get_prototype_of(
     _: &JsObject,
-    _: &mut Context<'_>,
+    _: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsPrototype> {
     // 1. Return null.
     Ok(None)
@@ -52,7 +52,7 @@ fn module_namespace_exotic_get_prototype_of(
 fn module_namespace_exotic_set_prototype_of(
     obj: &JsObject,
     val: JsPrototype,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Return ! SetImmutablePrototype(O, V).
     Ok(
@@ -65,7 +65,10 @@ fn module_namespace_exotic_set_prototype_of(
 ///
 /// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-isextensible
 #[allow(clippy::unnecessary_wraps)]
-fn module_namespace_exotic_is_extensible(_: &JsObject, _: &mut Context<'_>) -> JsResult<bool> {
+fn module_namespace_exotic_is_extensible(
+    _: &JsObject,
+    _: &mut InternalMethodContext<'_, '_>,
+) -> JsResult<bool> {
     // 1. Return false.
     Ok(false)
 }
@@ -74,7 +77,10 @@ fn module_namespace_exotic_is_extensible(_: &JsObject, _: &mut Context<'_>) -> J
 ///
 /// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-preventextensions
 #[allow(clippy::unnecessary_wraps)]
-fn module_namespace_exotic_prevent_extensions(_: &JsObject, _: &mut Context<'_>) -> JsResult<bool> {
+fn module_namespace_exotic_prevent_extensions(
+    _: &JsObject,
+    _: &mut InternalMethodContext<'_, '_>,
+) -> JsResult<bool> {
     Ok(true)
 }
 
@@ -84,7 +90,7 @@ fn module_namespace_exotic_prevent_extensions(_: &JsObject, _: &mut Context<'_>)
 fn module_namespace_exotic_get_own_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Option<PropertyDescriptor>> {
     // 1. If P is a Symbol, return OrdinaryGetOwnProperty(O, P).
     let key = match key {
@@ -128,7 +134,7 @@ fn module_namespace_exotic_define_own_property(
     obj: &JsObject,
     key: &PropertyKey,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If P is a Symbol, return ! OrdinaryDefineOwnProperty(O, P, Desc).
     if let PropertyKey::Symbol(_) = key {
@@ -164,7 +170,7 @@ fn module_namespace_exotic_define_own_property(
 fn module_namespace_exotic_has_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If P is a Symbol, return ! OrdinaryHasProperty(O, P).
     let key = match key {
@@ -193,7 +199,7 @@ fn module_namespace_exotic_get(
     obj: &JsObject,
     key: &PropertyKey,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
     // 1. If P is a Symbol, then
     //     a. Return ! OrdinaryGet(O, P, Receiver).
@@ -268,7 +274,7 @@ fn module_namespace_exotic_set(
     _key: PropertyKey,
     _value: JsValue,
     _receiver: JsValue,
-    _context: &mut Context<'_>,
+    _context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Return false.
     Ok(false)
@@ -280,7 +286,7 @@ fn module_namespace_exotic_set(
 fn module_namespace_exotic_delete(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. If P is a Symbol, then
     //     a. Return ! OrdinaryDelete(O, P).
@@ -308,7 +314,7 @@ fn module_namespace_exotic_delete(
 /// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-ownpropertykeys
 fn module_namespace_exotic_own_property_keys(
     obj: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Vec<PropertyKey>> {
     // 2. Let symbolKeys be OrdinaryOwnPropertyKeys(O).
     let symbol_keys = ordinary_own_property_keys(obj, context)?;
diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs
index 6874d8d5d9d..2bad3e295ee 100644
--- a/boa_engine/src/object/internal_methods/proxy.rs
+++ b/boa_engine/src/object/internal_methods/proxy.rs
@@ -1,14 +1,16 @@
 use crate::{
     builtins::{array, object::Object},
     error::JsNativeError,
-    object::{InternalObjectMethods, JsObject, JsPrototype},
+    object::{shape::slot::SlotAttributes, InternalObjectMethods, JsObject, JsPrototype},
     property::{PropertyDescriptor, PropertyKey},
     string::utf16,
     value::Type,
-    Context, JsResult, JsValue,
+    JsResult, JsValue,
 };
 use rustc_hash::FxHashSet;
 
+use super::InternalMethodContext;
+
 /// Definitions of the internal object methods for array exotic objects.
 ///
 /// More information:
@@ -53,7 +55,7 @@ pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods =
 /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof
 pub(crate) fn proxy_exotic_get_prototype_of(
     obj: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsPrototype> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -115,7 +117,7 @@ pub(crate) fn proxy_exotic_get_prototype_of(
 pub(crate) fn proxy_exotic_set_prototype_of(
     obj: &JsObject,
     val: JsPrototype,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -178,7 +180,7 @@ pub(crate) fn proxy_exotic_set_prototype_of(
 /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible
 pub(crate) fn proxy_exotic_is_extensible(
     obj: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -224,7 +226,7 @@ pub(crate) fn proxy_exotic_is_extensible(
 /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions
 pub(crate) fn proxy_exotic_prevent_extensions(
     obj: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -270,8 +272,10 @@ pub(crate) fn proxy_exotic_prevent_extensions(
 pub(crate) fn proxy_exotic_get_own_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Option<PropertyDescriptor>> {
+    context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
+
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
     // 3. Assert: Type(handler) is Object.
@@ -286,6 +290,7 @@ pub(crate) fn proxy_exotic_get_own_property(
     let Some(trap) = handler.get_method(utf16!("getOwnPropertyDescriptor"), context)? else {
         // 6. If trap is undefined, then
         // a. Return ? target.[[GetOwnProperty]](P).
+
         return target.__get_own_property__(key, context);
     };
 
@@ -394,8 +399,10 @@ pub(crate) fn proxy_exotic_define_own_property(
     obj: &JsObject,
     key: &PropertyKey,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
+    context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
+
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
     // 3. Assert: Type(handler) is Object.
@@ -505,8 +512,10 @@ pub(crate) fn proxy_exotic_define_own_property(
 pub(crate) fn proxy_exotic_has_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
+    context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
+
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
     // 3. Assert: Type(handler) is Object.
@@ -571,8 +580,11 @@ pub(crate) fn proxy_exotic_get(
     obj: &JsObject,
     key: &PropertyKey,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
+    // Proxy object can't be cached.
+    context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
+
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
     // 3. Assert: Type(handler) is Object.
@@ -640,8 +652,10 @@ pub(crate) fn proxy_exotic_set(
     key: PropertyKey,
     value: JsValue,
     receiver: JsValue,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
+    context.slot().attributes |= SlotAttributes::NOT_CACHABLE;
+
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
     // 3. Assert: Type(handler) is Object.
@@ -721,7 +735,7 @@ pub(crate) fn proxy_exotic_set(
 pub(crate) fn proxy_exotic_delete(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -787,7 +801,7 @@ pub(crate) fn proxy_exotic_delete(
 /// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys
 pub(crate) fn proxy_exotic_own_property_keys(
     obj: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Vec<PropertyKey>> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -923,7 +937,7 @@ fn proxy_exotic_call(
     obj: &JsObject,
     this: &JsValue,
     args: &[JsValue],
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsValue> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -948,7 +962,7 @@ fn proxy_exotic_call(
     // 8. Return ? Call(trap, handler, ยซ target, thisArgument, argArray ยป).
     trap.call(
         &handler.into(),
-        &[target.clone().into(), this.clone(), arg_array.into()],
+        &[target.into(), this.clone(), arg_array.into()],
         context,
     )
 }
@@ -963,7 +977,7 @@ fn proxy_exotic_construct(
     obj: &JsObject,
     args: &[JsValue],
     new_target: &JsObject,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<JsObject> {
     // 1. Let handler be O.[[ProxyHandler]].
     // 2. If handler is null, throw a TypeError exception.
@@ -991,11 +1005,7 @@ fn proxy_exotic_construct(
     // 9. Let newObj be ? Call(trap, handler, ยซ target, argArray, newTarget ยป).
     let new_obj = trap.call(
         &handler.into(),
-        &[
-            target.clone().into(),
-            arg_array.into(),
-            new_target.clone().into(),
-        ],
+        &[target.into(), arg_array.into(), new_target.clone().into()],
         context,
     )?;
 
diff --git a/boa_engine/src/object/internal_methods/string.rs b/boa_engine/src/object/internal_methods/string.rs
index 1a99da753bc..4f1dd9c5f30 100644
--- a/boa_engine/src/object/internal_methods/string.rs
+++ b/boa_engine/src/object/internal_methods/string.rs
@@ -2,10 +2,10 @@ use crate::{
     js_string,
     object::JsObject,
     property::{PropertyDescriptor, PropertyKey},
-    Context, JsResult,
+    JsResult,
 };
 
-use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
+use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
 
 /// Definitions of the internal object methods for string exotic objects.
 ///
@@ -29,7 +29,7 @@ pub(crate) static STRING_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = Intern
 pub(crate) fn string_exotic_get_own_property(
     obj: &JsObject,
     key: &PropertyKey,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Option<PropertyDescriptor>> {
     // 1. Assert: IsPropertyKey(P) is true.
     // 2. Let desc be OrdinaryGetOwnProperty(S, P).
@@ -54,7 +54,7 @@ pub(crate) fn string_exotic_define_own_property(
     obj: &JsObject,
     key: &PropertyKey,
     desc: PropertyDescriptor,
-    context: &mut Context<'_>,
+    context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<bool> {
     // 1. Assert: IsPropertyKey(P) is true.
     // 2. Let stringDesc be ! StringGetOwnProperty(S, P).
@@ -85,7 +85,7 @@ pub(crate) fn string_exotic_define_own_property(
 #[allow(clippy::unnecessary_wraps)]
 pub(crate) fn string_exotic_own_property_keys(
     obj: &JsObject,
-    _context: &mut Context<'_>,
+    _context: &mut InternalMethodContext<'_, '_>,
 ) -> JsResult<Vec<PropertyKey>> {
     let obj = obj.borrow();
 
diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs
index 2b583a4e758..3c444441054 100644
--- a/boa_engine/src/object/jsobject.rs
+++ b/boa_engine/src/object/jsobject.rs
@@ -3,7 +3,9 @@
 //! The `JsObject` is a garbage collected Object.
 
 use super::{
-    internal_methods::{InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS},
+    internal_methods::{
+        InternalMethodContext, InternalObjectMethods, ARRAY_EXOTIC_INTERNAL_METHODS,
+    },
     shape::RootShape,
     JsPrototype, NativeObject, Object, PrivateName, PropertyMap,
 };
@@ -819,6 +821,8 @@ Cannot both specify accessors and a value or writable attribute",
     where
         K: Into<PropertyKey>,
     {
+        let context = &mut InternalMethodContext::new(context);
+
         // 1. Assert: Type(target) is Object.
         // 2. Assert: excludedItems is a List of property keys.
         // 3. If source is undefined or null, return target.
diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs
index a2861c47016..1770fe8ca62 100644
--- a/boa_engine/src/object/operations.rs
+++ b/boa_engine/src/object/operations.rs
@@ -10,6 +10,8 @@ use crate::{
     Context, JsResult, JsSymbol, JsValue,
 };
 
+use super::internal_methods::InternalMethodContext;
+
 /// Object integrity level.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum IntegrityLevel {
@@ -55,7 +57,7 @@ impl JsObject {
     #[inline]
     pub fn is_extensible(&self, context: &mut Context<'_>) -> JsResult<bool> {
         // 1. Return ? O.[[IsExtensible]]().
-        self.__is_extensible__(context)
+        self.__is_extensible__(&mut InternalMethodContext::new(context))
     }
 
     /// Get property from object or throw.
@@ -71,7 +73,11 @@ impl JsObject {
         // 1. Assert: Type(O) is Object.
         // 2. Assert: IsPropertyKey(P) is true.
         // 3. Return ? O.[[Get]](P, O).
-        self.__get__(&key.into(), self.clone().into(), context)
+        self.__get__(
+            &key.into(),
+            self.clone().into(),
+            &mut InternalMethodContext::new(context),
+        )
     }
 
     /// set property of object or throw if bool flag is passed.
@@ -96,7 +102,12 @@ impl JsObject {
         // 2. Assert: IsPropertyKey(P) is true.
         // 3. Assert: Type(Throw) is Boolean.
         // 4. Let success be ? O.[[Set]](P, V, O).
-        let success = self.__set__(key.clone(), value.into(), self.clone().into(), context)?;
+        let success = self.__set__(
+            key.clone(),
+            value.into(),
+            self.clone().into(),
+            &mut InternalMethodContext::new(context),
+        )?;
         // 5. If success is false and Throw is true, throw a TypeError exception.
         if !success && throw {
             return Err(JsNativeError::typ()
@@ -119,6 +130,38 @@ impl JsObject {
         value: V,
         context: &mut Context<'_>,
     ) -> JsResult<bool>
+    where
+        K: Into<PropertyKey>,
+        V: Into<JsValue>,
+    {
+        // 1. Assert: Type(O) is Object.
+        // 2. Assert: IsPropertyKey(P) is true.
+        // 3. Let newDesc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }.
+        let new_desc = PropertyDescriptor::builder()
+            .value(value)
+            .writable(true)
+            .enumerable(true)
+            .configurable(true);
+        // 4. Return ? O.[[DefineOwnProperty]](P, newDesc).
+        self.__define_own_property__(
+            &key.into(),
+            new_desc.into(),
+            &mut InternalMethodContext::new(context),
+        )
+    }
+
+    /// Create data property
+    ///
+    /// More information:
+    ///  - [ECMAScript reference][spec]
+    ///
+    /// [spec]: https://tc39.es/ecma262/#sec-createdataproperty
+    pub(crate) fn create_data_property_with_slot<K, V>(
+        &self,
+        key: K,
+        value: V,
+        context: &mut InternalMethodContext<'_, '_>,
+    ) -> JsResult<bool>
     where
         K: Into<PropertyKey>,
         V: Into<JsValue>,
@@ -225,7 +268,11 @@ impl JsObject {
         // 1. Assert: Type(O) is Object.
         // 2. Assert: IsPropertyKey(P) is true.
         // 3. Let success be ? O.[[DefineOwnProperty]](P, desc).
-        let success = self.__define_own_property__(&key, desc.into(), context)?;
+        let success = self.__define_own_property__(
+            &key,
+            desc.into(),
+            &mut InternalMethodContext::new(context),
+        )?;
         // 4. If success is false, throw a TypeError exception.
         if !success {
             return Err(JsNativeError::typ()
@@ -250,7 +297,7 @@ impl JsObject {
         // 1. Assert: Type(O) is Object.
         // 2. Assert: IsPropertyKey(P) is true.
         // 3. Let success be ? O.[[Delete]](P).
-        let success = self.__delete__(&key, context)?;
+        let success = self.__delete__(&key, &mut InternalMethodContext::new(context))?;
         // 4. If success is false, throw a TypeError exception.
         if !success {
             return Err(JsNativeError::typ()
@@ -274,7 +321,8 @@ impl JsObject {
         // 1. Assert: Type(O) is Object.
         // 2. Assert: IsPropertyKey(P) is true.
         // 3. Return ? O.[[HasProperty]](P).
-        self.__has_property__(&key.into(), context)
+
+        self.__has_property__(&key.into(), &mut InternalMethodContext::new(context))
     }
 
     /// Check if object has an own property.
@@ -291,7 +339,7 @@ impl JsObject {
         // 1. Assert: Type(O) is Object.
         // 2. Assert: IsPropertyKey(P) is true.
         // 3. Let desc be ? O.[[GetOwnProperty]](P).
-        let desc = self.__get_own_property__(&key, context)?;
+        let desc = self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
         // 4. If desc is undefined, return false.
         // 5. Return true.
         Ok(desc.is_some())
@@ -322,7 +370,7 @@ impl JsObject {
         })?;
 
         // 3. Return ? F.[[Call]](V, argumentsList).
-        function.__call__(this, args, context)
+        function.__call__(this, args, &mut InternalMethodContext::new(context))
     }
 
     /// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
@@ -349,7 +397,7 @@ impl JsObject {
         let new_target = new_target.unwrap_or(self);
         // 2. If argumentsList is not present, set argumentsList to a new empty List.
         // 3. Return ? F.[[Construct]](argumentsList, newTarget).
-        self.__construct__(args, new_target, context)
+        self.__construct__(args, new_target, &mut InternalMethodContext::new(context))
     }
 
     /// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
@@ -367,14 +415,14 @@ impl JsObject {
         // 2. Assert: level is either sealed or frozen.
 
         // 3. Let status be ? O.[[PreventExtensions]]().
-        let status = self.__prevent_extensions__(context)?;
+        let status = self.__prevent_extensions__(&mut InternalMethodContext::new(context))?;
         // 4. If status is false, return false.
         if !status {
             return Ok(false);
         }
 
         // 5. Let keys be ? O.[[OwnPropertyKeys]]().
-        let keys = self.__own_property_keys__(context)?;
+        let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
 
         match level {
             // 6. If level is sealed, then
@@ -395,7 +443,8 @@ impl JsObject {
                 // b. For each element k of keys, do
                 for k in keys {
                     // i. Let currentDesc be ? O.[[GetOwnProperty]](k).
-                    let current_desc = self.__get_own_property__(&k, context)?;
+                    let current_desc =
+                        self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
                     // ii. If currentDesc is not undefined, then
                     if let Some(current_desc) = current_desc {
                         // 1. If IsAccessorDescriptor(currentDesc) is true, then
@@ -445,12 +494,13 @@ impl JsObject {
 
         // 5. NOTE: If the object is extensible, none of its properties are examined.
         // 6. Let keys be ? O.[[OwnPropertyKeys]]().
-        let keys = self.__own_property_keys__(context)?;
+        let keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
 
         // 7. For each element k of keys, do
         for k in keys {
             // a. Let currentDesc be ? O.[[GetOwnProperty]](k).
-            let current_desc = self.__get_own_property__(&k, context)?;
+            let current_desc =
+                self.__get_own_property__(&k, &mut InternalMethodContext::new(context))?;
             // b. If currentDesc is not undefined, then
             if let Some(current_desc) = current_desc {
                 // i. If currentDesc.[[Configurable]] is true, return false.
@@ -559,7 +609,7 @@ impl JsObject {
     ) -> JsResult<Vec<JsValue>> {
         // 1. Assert: Type(O) is Object.
         // 2. Let ownKeys be ? O.[[OwnPropertyKeys]]().
-        let own_keys = self.__own_property_keys__(context)?;
+        let own_keys = self.__own_property_keys__(&mut InternalMethodContext::new(context))?;
         // 3. Let properties be a new empty List.
         let mut properties = vec![];
 
@@ -574,7 +624,8 @@ impl JsObject {
 
             if let Some(key_str) = key_str {
                 // i. Let desc be ? O.[[GetOwnProperty]](key).
-                let desc = self.__get_own_property__(&key, context)?;
+                let desc =
+                    self.__get_own_property__(&key, &mut InternalMethodContext::new(context))?;
                 // ii. If desc is not undefined and desc.[[Enumerable]] is true, then
                 if let Some(desc) = desc {
                     if desc.expect_enumerable() {
@@ -625,7 +676,12 @@ impl JsObject {
 
         // 1. Assert: IsPropertyKey(P) is true.
         // 2. Let func be ? GetV(V, P).
-        match &self.__get__(&key.into(), self.clone().into(), context)? {
+
+        match &self.__get__(
+            &key.into(),
+            self.clone().into(),
+            &mut InternalMethodContext::new(context),
+        )? {
             // 3. If func is either undefined or null, return undefined.
             JsValue::Undefined | JsValue::Null => Ok(None),
             // 5. Return func.
@@ -1026,7 +1082,11 @@ impl JsObject {
 
         // 1. If argumentsList is not present, set argumentsList to a new empty List.
         // 2. Let func be ? GetV(V, P).
-        let func = self.__get__(&key.into(), this_value.clone(), context)?;
+        let func = self.__get__(
+            &key.into(),
+            this_value.clone(),
+            &mut InternalMethodContext::new(context),
+        )?;
 
         // 3. Return ? Call(func, V, argumentsList)
         func.call(&this_value, args, context)
@@ -1052,7 +1112,12 @@ impl JsValue {
         let o = self.to_object(context)?;
 
         // 2. Return ? O.[[Get]](P, V).
-        o.__get__(&key.into(), self.clone(), context)
+
+        o.__get__(
+            &key.into(),
+            self.clone(),
+            &mut InternalMethodContext::new(context),
+        )
     }
 
     /// Abstract operation `GetMethod ( V, P )`
@@ -1160,7 +1225,7 @@ impl JsValue {
                     self.type_of()
                 ))
             })?
-            .__call__(this, args, context)
+            .__call__(this, args, &mut InternalMethodContext::new(context))
     }
 
     /// Abstract operation `( V, P [ , argumentsList ] )`
@@ -1232,7 +1297,7 @@ impl JsValue {
         // 6. Repeat,
         loop {
             // a. Set O to ? O.[[GetPrototypeOf]]().
-            object = match object.__get_prototype_of__(context)? {
+            object = match object.__get_prototype_of__(&mut InternalMethodContext::new(context))? {
                 Some(obj) => obj,
                 // b. If O is null, return false.
                 None => return Ok(false),
diff --git a/boa_engine/src/object/property_map.rs b/boa_engine/src/object/property_map.rs
index 9a06080a130..6ef4a5c0871 100644
--- a/boa_engine/src/object/property_map.rs
+++ b/boa_engine/src/object/property_map.rs
@@ -277,6 +277,28 @@ impl PropertyMap {
         None
     }
 
+    /// Get the property with the given key from the [`PropertyMap`].
+    #[must_use]
+    pub(crate) fn get_with_slot(
+        &self,
+        key: &PropertyKey,
+        out_slot: &mut Slot,
+    ) -> Option<PropertyDescriptor> {
+        if let PropertyKey::Index(index) = key {
+            return self.indexed_properties.get(*index);
+        }
+        if let Some(slot) = self.shape.lookup(key) {
+            out_slot.index = slot.index;
+
+            // Remove all descriptor attributes, but keep inline caching bits.
+            out_slot.attributes =
+                (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | slot.attributes;
+            return Some(self.get_storage(slot));
+        }
+
+        None
+    }
+
     /// Get the property with the given key from the [`PropertyMap`].
     #[must_use]
     pub(crate) fn get_storage(&self, Slot { index, attributes }: Slot) -> PropertyDescriptor {
@@ -300,6 +322,17 @@ impl PropertyMap {
 
     /// Insert the given property descriptor with the given key [`PropertyMap`].
     pub fn insert(&mut self, key: &PropertyKey, property: PropertyDescriptor) -> bool {
+        let mut dummy_slot = Slot::new();
+        self.insert_with_slot(key, property, &mut dummy_slot)
+    }
+
+    /// Insert the given property descriptor with the given key [`PropertyMap`].
+    pub(crate) fn insert_with_slot(
+        &mut self,
+        key: &PropertyKey,
+        property: PropertyDescriptor,
+        out_slot: &mut Slot,
+    ) -> bool {
         if let PropertyKey::Index(index) = key {
             return self.indexed_properties.insert(*index, property);
         }
@@ -346,6 +379,9 @@ impl PropertyMap {
             } else {
                 self.storage[index] = property.expect_value().clone();
             }
+            out_slot.index = slot.index;
+            out_slot.attributes =
+                (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes;
             return true;
         }
 
@@ -364,6 +400,10 @@ impl PropertyMap {
             })
         );
 
+        out_slot.index = self.storage.len() as u32;
+        out_slot.attributes =
+            (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) | attributes;
+
         if attributes.is_accessor_descriptor() {
             self.storage.push(
                 property
diff --git a/boa_engine/src/object/shape/slot.rs b/boa_engine/src/object/shape/slot.rs
index 64fb3c8f43f..b5c01daddfb 100644
--- a/boa_engine/src/object/shape/slot.rs
+++ b/boa_engine/src/object/shape/slot.rs
@@ -10,6 +10,11 @@ bitflags! {
         const CONFIGURABLE = 0b0000_0100;
         const GET = 0b0000_1000;
         const SET = 0b0001_0000;
+
+        const INLINE_CACHE_BITS = 0b1110_0000;
+        const PROTOTYPE    = 0b0010_0000;
+        const FOUND        = 0b0100_0000;
+        const NOT_CACHABLE = 0b1000_0000;
     }
 }
 
@@ -36,6 +41,10 @@ impl SlotAttributes {
         // accessor take 2 positions in the storage to accomodate for the `get` and `set` fields.
         1 + u32::from(self.is_accessor_descriptor())
     }
+
+    pub(crate) const fn is_cachable(self) -> bool {
+        !self.contains(Self::NOT_CACHABLE) && self.contains(Self::FOUND)
+    }
 }
 
 /// Represents an [`u32`] index and it's slot attributes of an element in a object storage.
@@ -49,6 +58,17 @@ pub(crate) struct Slot {
 }
 
 impl Slot {
+    pub(crate) const fn new() -> Self {
+        Self {
+            index: 0,
+            attributes: SlotAttributes::empty(),
+        }
+    }
+
+    pub(crate) const fn is_cachable(self) -> bool {
+        self.attributes.is_cachable()
+    }
+
     /// Get the width of the slot.
     pub(crate) fn width(self) -> u32 {
         self.attributes.width()
@@ -74,4 +94,17 @@ impl Slot {
             attributes: new_attributes,
         }
     }
+
+    pub(crate) fn set_not_cachable_if_already_prototype(&mut self) {
+        // NOTE(HalidOdat): This is a bit hack to avoid conditional branches.
+        //
+        // Equivalent to:
+        // if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
+        //     slot.attributes |= SlotAttributes::NOT_CACHABLE;
+        // }
+        //
+        self.attributes |= SlotAttributes::from_bits_retain(
+            (self.attributes.bits() & SlotAttributes::PROTOTYPE.bits()) << 2,
+        );
+    }
 }
diff --git a/boa_engine/src/value/tests.rs b/boa_engine/src/value/tests.rs
index ff7d5f9a266..c25d096614c 100644
--- a/boa_engine/src/value/tests.rs
+++ b/boa_engine/src/value/tests.rs
@@ -2,6 +2,7 @@ use boa_macros::utf16;
 use indoc::indoc;
 
 use super::*;
+use crate::object::internal_methods::InternalMethodContext;
 use crate::{run_test_actions, TestAction};
 
 use std::collections::hash_map::DefaultHasher;
@@ -197,10 +198,13 @@ fn float_display() {
 
 #[test]
 fn string_length_is_not_enumerable() {
-    run_test_actions([TestAction::assert_context(|ctx| {
-        let object = JsValue::new("foo").to_object(ctx).unwrap();
+    run_test_actions([TestAction::assert_context(|context| {
+        let object = JsValue::new("foo").to_object(context).unwrap();
         let length_desc = object
-            .__get_own_property__(&PropertyKey::from("length"), ctx)
+            .__get_own_property__(
+                &PropertyKey::from("length"),
+                &mut InternalMethodContext::new(context),
+            )
             .unwrap()
             .unwrap();
         !length_desc.expect_enumerable()
@@ -209,16 +213,20 @@ fn string_length_is_not_enumerable() {
 
 #[test]
 fn string_length_is_in_utf16_codeunits() {
-    run_test_actions([TestAction::assert_context(|ctx| {
+    run_test_actions([TestAction::assert_context(|context| {
         // ๐Ÿ˜€ is one Unicode code point, but 2 UTF-16 code units
-        let object = JsValue::new("๐Ÿ˜€").to_object(ctx).unwrap();
+        let object = JsValue::new("๐Ÿ˜€").to_object(context).unwrap();
+
         let length_desc = object
-            .__get_own_property__(&PropertyKey::from("length"), ctx)
+            .__get_own_property__(
+                &PropertyKey::from("length"),
+                &mut InternalMethodContext::new(context),
+            )
             .unwrap()
             .unwrap();
         length_desc
             .expect_value()
-            .to_integer_or_infinity(ctx)
+            .to_integer_or_infinity(context)
             .unwrap()
             == IntegerOrInfinity::Integer(2)
     })]);
diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs
index c2cf7193c9b..d03d04d8363 100644
--- a/boa_engine/src/vm/code_block.rs
+++ b/boa_engine/src/vm/code_block.rs
@@ -12,7 +12,11 @@ use crate::{
     context::intrinsics::StandardConstructors,
     environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus},
     error::JsNativeError,
-    object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PROTOTYPE},
+    object::{
+        internal_methods::get_prototype_from_constructor,
+        shape::{slot::Slot, Shape},
+        JsObject, ObjectData, PROTOTYPE,
+    },
     property::PropertyDescriptor,
     string::utf16,
     vm::CallFrame,
@@ -20,7 +24,7 @@ use crate::{
 };
 use bitflags::bitflags;
 use boa_ast::function::FormalParameterList;
-use boa_gc::{empty_trace, Finalize, Gc, Trace};
+use boa_gc::{empty_trace, Finalize, Gc, GcRefCell, Trace};
 use boa_interner::Sym;
 use boa_profiler::Profiler;
 use std::{
@@ -93,6 +97,56 @@ unsafe impl Trace for CodeBlockFlags {
     empty_trace!();
 }
 
+/// An inline cache entry for a property access.
+#[derive(Clone, Debug, Trace, Finalize)]
+#[allow(dead_code)]
+pub struct InlineCache {
+    /// The property that is accessed.
+    pub(crate) name: JsString,
+
+    /// A pointer is kept to the shape to avoid the shape from being deallocated.
+    pub(crate) shape: GcRefCell<Option<Shape>>,
+
+    /// Cached shape ptr used to used to check if two shapes are the same.
+    ///
+    /// This is done to avoid calling `.borrow()` on [`GcRefCell`] and [`Option`] unwrapping.
+    #[unsafe_ignore_trace]
+    pub(crate) shape_ptr: Cell<usize>,
+
+    /// The [`Slot`] of the property.
+    #[unsafe_ignore_trace]
+    pub(crate) slot: Cell<Slot>,
+}
+
+impl InlineCache {
+    pub(crate) const fn new(name: JsString) -> Self {
+        Self {
+            name,
+            shape: GcRefCell::new(None),
+            shape_ptr: Cell::new(0),
+            slot: Cell::new(Slot::new()),
+        }
+    }
+
+    pub(crate) fn set(&self, shape: Shape, slot: Slot) {
+        let shape_ptr = shape.to_addr_usize();
+        {
+            let mut shape_borrowed = self.shape.borrow_mut();
+            *shape_borrowed = Some(shape);
+        }
+        self.shape_ptr.set(shape_ptr);
+        self.slot.set(slot);
+    }
+
+    pub(crate) fn slot(&self) -> Slot {
+        self.slot.get()
+    }
+
+    pub(crate) fn matches(&self, shape: &Shape) -> bool {
+        self.shape_ptr.get() == shape.to_addr_usize()
+    }
+}
+
 /// The internal representation of a JavaScript function.
 ///
 /// A `CodeBlock` is generated for each function compiled by the
@@ -118,7 +172,8 @@ pub struct CodeBlock {
     pub(crate) params: FormalParameterList,
 
     /// Bytecode
-    pub(crate) bytecode: Box<[u8]>,
+    #[unsafe_ignore_trace]
+    pub(crate) bytecode: Box<[Cell<u8>]>,
 
     /// Literals
     pub(crate) literals: Box<[JsValue]>,
@@ -140,6 +195,9 @@ pub struct CodeBlock {
     // TODO(#3034): Maybe changing this to Gc after garbage collection would be better than Rc.
     #[unsafe_ignore_trace]
     pub(crate) compile_environments: Box<[Rc<RefCell<CompileTimeEnvironment>>]>,
+
+    /// inline caching
+    pub(crate) ic: Box<[InlineCache]>,
 }
 
 /// ---- `CodeBlock` public API ----
@@ -161,6 +219,7 @@ impl CodeBlock {
             this_mode: ThisMode::Global,
             params: FormalParameterList::default(),
             compile_environments: Box::default(),
+            ic: Box::default(),
         }
     }
 
@@ -269,7 +328,7 @@ impl CodeBlock {
     /// Returns an empty `String` if no operands are present.
     #[cfg(any(feature = "trace", feature = "flowgraph"))]
     pub(crate) fn instruction_operands(&self, pc: &mut usize, interner: &Interner) -> String {
-        let opcode: Opcode = self.bytecode[*pc].into();
+        let opcode: Opcode = self.bytecode[*pc].get().into();
         *pc += size_of::<Opcode>();
         match opcode {
             Opcode::SetFunctionName => {
@@ -395,9 +454,22 @@ impl CodeBlock {
                     interner.resolve_expect(self.bindings[operand as usize].name().sym()),
                 )
             }
-            Opcode::GetPropertyByName
-            | Opcode::GetMethod
-            | Opcode::SetPropertyByName
+            Opcode::GetPropertyByName | Opcode::SetPropertyByName => {
+                let ic_index = self.read::<u32>(*pc);
+                *pc += size_of::<u32>();
+
+                let ic = &self.ic[ic_index as usize];
+                let slot = ic.slot();
+                format!(
+                    "{:04}: '{}', Shape: 0x{:x}, Slot: index: {}, attributes {:?}",
+                    ic_index,
+                    ic.name.to_std_string_escaped(),
+                    ic.shape_ptr.get(),
+                    slot.index,
+                    slot.attributes,
+                )
+            }
+            Opcode::GetMethod
             | Opcode::DefineOwnPropertyByName
             | Opcode::DefineClassStaticMethodByName
             | Opcode::DefineClassMethodByName
@@ -643,7 +715,7 @@ impl ToInternedString for CodeBlock {
         let mut pc = 0;
         let mut count = 0;
         while pc < self.bytecode.len() {
-            let opcode: Opcode = self.bytecode[pc].into();
+            let opcode: Opcode = self.bytecode[pc].get().into();
             let opcode = opcode.as_str();
             let previous_pc = pc;
             let operands = self.instruction_operands(&mut pc, interner);
diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs
index 6e632d66bb1..24c942cb2c5 100644
--- a/boa_engine/src/vm/flowgraph/mod.rs
+++ b/boa_engine/src/vm/flowgraph/mod.rs
@@ -32,7 +32,7 @@ impl CodeBlock {
 
         let mut pc = 0;
         while pc < self.bytecode.len() {
-            let opcode: Opcode = self.bytecode[pc].into();
+            let opcode: Opcode = self.bytecode[pc].get().into();
             let opcode_str = opcode.as_str();
             let previous_pc = pc;
 
@@ -447,8 +447,16 @@ impl CodeBlock {
                     graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
                     graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
                 }
-                Opcode::GetPropertyByName
-                | Opcode::GetMethod
+                Opcode::GetPropertyByName => {
+                    let ic_index = self.read::<u32>(pc);
+                    pc += size_of::<u32>();
+
+                    let ic = &self.ic[ic_index as usize];
+                    let label = format!("{opcode_str} '{}'", ic.name.to_std_string_escaped());
+                    graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
+                    graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
+                }
+                Opcode::GetMethod
                 | Opcode::SetPropertyByName
                 | Opcode::DefineOwnPropertyByName
                 | Opcode::DefineClassStaticMethodByName
diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs
index 27f81aaced3..ffc2305dc11 100644
--- a/boa_engine/src/vm/mod.rs
+++ b/boa_engine/src/vm/mod.rs
@@ -34,7 +34,11 @@ mod runtime_limits;
 pub mod flowgraph;
 
 pub use runtime_limits::RuntimeLimits;
-pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode};
+pub use {
+    call_frame::CallFrame,
+    code_block::{CodeBlock, InlineCache},
+    opcode::Opcode,
+};
 
 pub(crate) use {
     call_frame::GeneratorResumeKind,
@@ -164,7 +168,7 @@ impl Context<'_> {
             let frame = self.vm.frame_mut();
 
             let pc = frame.pc;
-            let opcode = Opcode::from(frame.code_block.bytecode[pc as usize]);
+            let opcode = Opcode::from(frame.code_block.bytecode[pc as usize].get());
             frame.pc += 1;
             opcode
         };
diff --git a/boa_engine/src/vm/opcode/call/mod.rs b/boa_engine/src/vm/opcode/call/mod.rs
index 4bdf52d7d94..70782af549f 100644
--- a/boa_engine/src/vm/opcode/call/mod.rs
+++ b/boa_engine/src/vm/opcode/call/mod.rs
@@ -2,7 +2,7 @@ use crate::{
     builtins::{function::FunctionKind, promise::PromiseCapability, Promise},
     error::JsNativeError,
     module::{ModuleKind, Referrer},
-    object::FunctionObjectBuilder,
+    object::{internal_methods::InternalMethodContext, FunctionObjectBuilder},
     vm::{opcode::Operation, CompletionType},
     Context, JsResult, JsValue, NativeFunction,
 };
@@ -68,7 +68,8 @@ impl Operation for CallEval {
                 context.vm.push(JsValue::Undefined);
             }
         } else {
-            let result = object.__call__(&this, &arguments, context)?;
+            let result =
+                object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?;
             context.vm.push(result);
         }
         Ok(CompletionType::Normal)
@@ -142,7 +143,8 @@ impl Operation for CallEvalSpread {
                 context.vm.push(JsValue::Undefined);
             }
         } else {
-            let result = object.__call__(&this, &arguments, context)?;
+            let result =
+                object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?;
             context.vm.push(result);
         }
         Ok(CompletionType::Normal)
@@ -193,7 +195,8 @@ impl Operation for Call {
             }
         };
 
-        let result = object.__call__(&this, &arguments, context)?;
+        let result =
+            object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?;
 
         context.vm.push(result);
         Ok(CompletionType::Normal)
@@ -246,7 +249,8 @@ impl Operation for CallSpread {
             }
         };
 
-        let result = object.__call__(&this, &arguments, context)?;
+        let result =
+            object.__call__(&this, &arguments, &mut InternalMethodContext::new(context))?;
 
         context.vm.push(result);
         Ok(CompletionType::Normal)
diff --git a/boa_engine/src/vm/opcode/define/class/getter.rs b/boa_engine/src/vm/opcode/define/class/getter.rs
index 0766d7f4aff..7a9265baa77 100644
--- a/boa_engine/src/vm/opcode/define/class/getter.rs
+++ b/boa_engine/src/vm/opcode/define/class/getter.rs
@@ -1,6 +1,6 @@
 use crate::{
     builtins::function::set_function_name,
-    object::CONSTRUCTOR,
+    object::{internal_methods::InternalMethodContext, CONSTRUCTOR},
     property::PropertyDescriptor,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult, JsString,
@@ -38,7 +38,7 @@ impl Operation for DefineClassStaticGetterByName {
             function_mut.set_class_object(class.clone());
         }
         let set = class
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::set)
             .cloned();
@@ -50,7 +50,7 @@ impl Operation for DefineClassStaticGetterByName {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -94,7 +94,7 @@ impl Operation for DefineClassGetterByName {
             function_mut.set_class_object(class);
         }
         let set = class_proto
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::set)
             .cloned();
@@ -106,7 +106,7 @@ impl Operation for DefineClassGetterByName {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -143,8 +143,9 @@ impl Operation for DefineClassStaticGetterByValue {
             function_mut.set_home_object(class.clone());
             function_mut.set_class_object(class.clone());
         }
+
         let set = class
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::set)
             .cloned();
@@ -200,7 +201,7 @@ impl Operation for DefineClassGetterByValue {
             function_mut.set_class_object(class);
         }
         let set = class_proto
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::set)
             .cloned();
@@ -212,7 +213,7 @@ impl Operation for DefineClassGetterByValue {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
diff --git a/boa_engine/src/vm/opcode/define/class/method.rs b/boa_engine/src/vm/opcode/define/class/method.rs
index 7601892cef0..a951f2e8af4 100644
--- a/boa_engine/src/vm/opcode/define/class/method.rs
+++ b/boa_engine/src/vm/opcode/define/class/method.rs
@@ -1,6 +1,6 @@
 use crate::{
     builtins::function::set_function_name,
-    object::CONSTRUCTOR,
+    object::{internal_methods::InternalMethodContext, CONSTRUCTOR},
     property::PropertyDescriptor,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult,
@@ -46,7 +46,7 @@ impl Operation for DefineClassStaticMethodByName {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -98,7 +98,7 @@ impl Operation for DefineClassMethodByName {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -196,7 +196,7 @@ impl Operation for DefineClassMethodByValue {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
diff --git a/boa_engine/src/vm/opcode/define/class/setter.rs b/boa_engine/src/vm/opcode/define/class/setter.rs
index 43f605a9c64..5a4a39ec0c6 100644
--- a/boa_engine/src/vm/opcode/define/class/setter.rs
+++ b/boa_engine/src/vm/opcode/define/class/setter.rs
@@ -1,6 +1,6 @@
 use crate::{
     builtins::function::set_function_name,
-    object::CONSTRUCTOR,
+    object::{internal_methods::InternalMethodContext, CONSTRUCTOR},
     property::PropertyDescriptor,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult, JsString,
@@ -38,7 +38,7 @@ impl Operation for DefineClassStaticSetterByName {
             function_mut.set_class_object(class.clone());
         }
         let get = class
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::get)
             .cloned();
@@ -51,7 +51,7 @@ impl Operation for DefineClassStaticSetterByName {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -95,7 +95,7 @@ impl Operation for DefineClassSetterByName {
             function_mut.set_class_object(class);
         }
         let get = class_proto
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::get)
             .cloned();
@@ -108,7 +108,7 @@ impl Operation for DefineClassSetterByName {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
 
         Ok(CompletionType::Normal)
@@ -147,7 +147,7 @@ impl Operation for DefineClassStaticSetterByValue {
             function_mut.set_class_object(class.clone());
         }
         let get = class
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::get)
             .cloned();
@@ -205,7 +205,7 @@ impl Operation for DefineClassSetterByValue {
             function_mut.set_class_object(class);
         }
         let get = class_proto
-            .__get_own_property__(&key, context)?
+            .__get_own_property__(&key, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::get)
             .cloned();
@@ -218,7 +218,7 @@ impl Operation for DefineClassSetterByValue {
                 .enumerable(false)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
 
         Ok(CompletionType::Normal)
diff --git a/boa_engine/src/vm/opcode/define/own_property.rs b/boa_engine/src/vm/opcode/define/own_property.rs
index 30de98a2a48..d1981ad7264 100644
--- a/boa_engine/src/vm/opcode/define/own_property.rs
+++ b/boa_engine/src/vm/opcode/define/own_property.rs
@@ -1,4 +1,5 @@
 use crate::{
+    object::internal_methods::InternalMethodContext,
     property::PropertyDescriptor,
     vm::{opcode::Operation, CompletionType},
     Context, JsNativeError, JsResult,
@@ -33,7 +34,7 @@ impl Operation for DefineOwnPropertyByName {
                 .enumerable(true)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -68,7 +69,7 @@ impl Operation for DefineOwnPropertyByValue {
                 .enumerable(true)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         if !success {
             return Err(JsNativeError::typ()
diff --git a/boa_engine/src/vm/opcode/delete/mod.rs b/boa_engine/src/vm/opcode/delete/mod.rs
index 22129fb35ba..fcac98b8d6a 100644
--- a/boa_engine/src/vm/opcode/delete/mod.rs
+++ b/boa_engine/src/vm/opcode/delete/mod.rs
@@ -1,5 +1,6 @@
 use crate::{
     error::JsNativeError,
+    object::internal_methods::InternalMethodContext,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult,
 };
@@ -22,8 +23,9 @@ impl Operation for DeletePropertyByName {
         let key = context.vm.frame().code_block.names[index as usize]
             .clone()
             .into();
-        let result = object.__delete__(&key, context)?;
-        if !result && context.vm.frame().code_block.strict() {
+
+        let result = object.__delete__(&key, &mut InternalMethodContext::new(context))?;
+        if !result && context.vm.frame().code_block().strict() {
             return Err(JsNativeError::typ()
                 .with_message("Cannot delete property")
                 .into());
@@ -49,8 +51,9 @@ impl Operation for DeletePropertyByValue {
         let value = context.vm.pop();
         let object = value.to_object(context)?;
         let property_key = key_value.to_property_key(context)?;
-        let result = object.__delete__(&property_key, context)?;
-        if !result && context.vm.frame().code_block.strict() {
+
+        let result = object.__delete__(&property_key, &mut InternalMethodContext::new(context))?;
+        if !result && context.vm.frame().code_block().strict() {
             return Err(JsNativeError::typ()
                 .with_message("Cannot delete property")
                 .into());
diff --git a/boa_engine/src/vm/opcode/environment/mod.rs b/boa_engine/src/vm/opcode/environment/mod.rs
index a0873ea91c2..f562b120f39 100644
--- a/boa_engine/src/vm/opcode/environment/mod.rs
+++ b/boa_engine/src/vm/opcode/environment/mod.rs
@@ -1,5 +1,6 @@
 use crate::{
     error::JsNativeError,
+    object::internal_methods::InternalMethodContext,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult, JsValue,
 };
@@ -52,7 +53,7 @@ impl Operation for Super {
         };
 
         let value = home_object
-            .map(|o| o.__get_prototype_of__(context))
+            .map(|o| o.__get_prototype_of__(&mut InternalMethodContext::new(context)))
             .transpose()?
             .flatten()
             .map_or_else(JsValue::null, JsValue::from);
@@ -87,7 +88,7 @@ impl Operation for SuperCallPrepare {
             .clone();
         let active_function = this_env.slots().function_object().clone();
         let super_constructor = active_function
-            .__get_prototype_of__(context)
+            .__get_prototype_of__(&mut InternalMethodContext::new(context))
             .expect("function object must have prototype");
 
         if let Some(constructor) = super_constructor {
@@ -133,7 +134,11 @@ impl Operation for SuperCall {
                 .into());
         };
 
-        let result = super_constructor.__construct__(&arguments, new_target, context)?;
+        let result = super_constructor.__construct__(
+            &arguments,
+            new_target,
+            &mut InternalMethodContext::new(context),
+        )?;
 
         let this_env = context
             .vm
@@ -189,7 +194,11 @@ impl Operation for SuperCallSpread {
                 .into());
         };
 
-        let result = super_constructor.__construct__(&arguments, new_target, context)?;
+        let result = super_constructor.__construct__(
+            &arguments,
+            new_target,
+            &mut InternalMethodContext::new(context),
+        )?;
 
         let this_env = context
             .vm
@@ -239,7 +248,7 @@ impl Operation for SuperCallDerived {
             .clone();
         let active_function = this_env.slots().function_object().clone();
         let super_constructor = active_function
-            .__get_prototype_of__(context)
+            .__get_prototype_of__(&mut InternalMethodContext::new(context))
             .expect("function object must have prototype")
             .expect("function object must have prototype");
 
@@ -249,7 +258,11 @@ impl Operation for SuperCallDerived {
                 .into());
         }
 
-        let result = super_constructor.__construct__(&arguments, &new_target, context)?;
+        let result = super_constructor.__construct__(
+            &arguments,
+            &new_target,
+            &mut InternalMethodContext::new(context),
+        )?;
 
         let this_env = context
             .vm
diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs
index 5028b343cfb..70f410b9147 100644
--- a/boa_engine/src/vm/opcode/get/property.rs
+++ b/boa_engine/src/vm/opcode/get/property.rs
@@ -1,4 +1,5 @@
 use crate::{
+    object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes},
     property::PropertyKey,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult, JsValue,
@@ -16,7 +17,7 @@ impl Operation for GetPropertyByName {
     const INSTRUCTION: &'static str = "INST - GetPropertyByName";
 
     fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
-        let index = context.vm.read::<u32>();
+        let ic_index = context.vm.read::<u32>() as usize;
 
         let receiver = context.vm.pop();
         let value = context.vm.pop();
@@ -26,11 +27,66 @@ impl Operation for GetPropertyByName {
             value.to_object(context)?
         };
 
-        let key = context.vm.frame().code_block.names[index as usize]
-            .clone()
-            .into();
+        let ic = &context.vm.frame().code_block().ic[ic_index];
+        let mut slot = ic.slot();
+        if slot.is_cachable() {
+            let object_borrowed = object.borrow();
+            if ic.matches(object_borrowed.shape()) {
+                // println!(
+                //     "GET: T: \"{}\" {}: {:?}",
+                //     ic.name.to_std_string_escaped(),
+                //     slot.index,
+                //     slot.attributes
+                // );
+                let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
+                    let prototype = object
+                        .borrow()
+                        .properties()
+                        .shape
+                        .prototype()
+                        .expect("prototype should have value");
+                    let prototype = prototype.borrow();
+                    prototype.properties().storage[slot.index as usize].clone()
+                } else {
+                    object_borrowed.properties().storage[slot.index as usize].clone()
+                };
+
+                drop(object_borrowed);
+                if slot.attributes.has_get() && result.is_object() {
+                    result = result.as_object().expect("should contain getter").call(
+                        &receiver,
+                        &[],
+                        context,
+                    )?;
+                }
+                context.vm.push(result);
+                return Ok(CompletionType::Normal);
+            }
+        }
+
+        let key: PropertyKey = ic.name.clone().into();
+
+        let context = &mut InternalMethodContext::new(context);
         let result = object.__get__(&key, receiver, context)?;
 
+        slot = *context.slot();
+
+        // println!(
+        //     "GET: F: \"{}\" {}: {:?} - {}",
+        //     key,
+        //     slot.index,
+        //     slot.attributes,
+        //     result.type_of()
+        // );
+
+        // Cache the property.
+        if slot.attributes.is_cachable() {
+            let ic = &context.vm.frame().code_block.ic[ic_index];
+            let object_borrowed = object.borrow();
+            let shape = object_borrowed.shape().clone();
+            ic.set(shape, slot);
+        }
+
         context.vm.push(result);
         Ok(CompletionType::Normal)
     }
@@ -75,7 +131,7 @@ impl Operation for GetPropertyByValue {
         }
 
         // Slow path:
-        let result = object.__get__(&key, receiver, context)?;
+        let result = object.__get__(&key, receiver, &mut InternalMethodContext::new(context))?;
 
         context.vm.push(result);
         Ok(CompletionType::Normal)
@@ -147,7 +203,7 @@ impl Operation for GetPropertyByValuePush {
         }
 
         // Slow path:
-        let result = object.__get__(&key, receiver, context)?;
+        let result = object.__get__(&key, receiver, &mut InternalMethodContext::new(context))?;
 
         context.vm.push(key);
         context.vm.push(result);
diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs
index 44622af89ea..fe8e8768743 100644
--- a/boa_engine/src/vm/opcode/mod.rs
+++ b/boa_engine/src/vm/opcode/mod.rs
@@ -727,7 +727,7 @@ generate_impl! {
         ///
         /// Like `object.name`
         ///
-        /// Operands: name_index: `u32`
+        /// Operands: ic_index: `u32`
         ///
         /// Stack: object, receiver **=>** value
         GetPropertyByName,
@@ -762,7 +762,7 @@ generate_impl! {
         ///
         /// Like `object.name = value`
         ///
-        /// Operands: name_index: `u32`
+        /// Operands: ic_index: `u32`
         ///
         /// Stack: object, receiver, value **=>** value
         SetPropertyByName,
diff --git a/boa_engine/src/vm/opcode/new/mod.rs b/boa_engine/src/vm/opcode/new/mod.rs
index 8e6c78a69a4..9cf9dfbc925 100644
--- a/boa_engine/src/vm/opcode/new/mod.rs
+++ b/boa_engine/src/vm/opcode/new/mod.rs
@@ -1,5 +1,6 @@
 use crate::{
     error::JsNativeError,
+    object::internal_methods::InternalMethodContext,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult,
 };
@@ -44,7 +45,9 @@ impl Operation for New {
                     .with_message("not a constructor")
                     .into()
             })
-            .and_then(|cons| cons.__construct__(&arguments, cons, context))?;
+            .and_then(|cons| {
+                cons.__construct__(&arguments, cons, &mut InternalMethodContext::new(context))
+            })?;
 
         context.vm.push(result);
         Ok(CompletionType::Normal)
@@ -97,7 +100,9 @@ impl Operation for NewSpread {
                     .with_message("not a constructor")
                     .into()
             })
-            .and_then(|cons| cons.__construct__(&arguments, cons, context))?;
+            .and_then(|cons| {
+                cons.__construct__(&arguments, cons, &mut InternalMethodContext::new(context))
+            })?;
 
         context.vm.push(result);
         Ok(CompletionType::Normal)
diff --git a/boa_engine/src/vm/opcode/push/class/private.rs b/boa_engine/src/vm/opcode/push/class/private.rs
index 31797f39fcb..474481295be 100644
--- a/boa_engine/src/vm/opcode/push/class/private.rs
+++ b/boa_engine/src/vm/opcode/push/class/private.rs
@@ -1,5 +1,5 @@
 use crate::{
-    object::PrivateElement,
+    object::{internal_methods::InternalMethodContext, PrivateElement},
     property::PropertyDescriptor,
     string::utf16,
     vm::{opcode::Operation, CompletionType},
@@ -31,7 +31,11 @@ impl Operation for PushClassPrivateMethod {
             .configurable(true)
             .build();
         method_object
-            .__define_own_property__(&utf16!("name").into(), desc, context)
+            .__define_own_property__(
+                &utf16!("name").into(),
+                desc,
+                &mut InternalMethodContext::new(context),
+            )
             .expect("failed to set name property on private method");
 
         let class = context.vm.pop();
diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs
index d647e7e8ae8..ce7a29b00bd 100644
--- a/boa_engine/src/vm/opcode/set/class_prototype.rs
+++ b/boa_engine/src/vm/opcode/set/class_prototype.rs
@@ -1,5 +1,7 @@
 use crate::{
-    object::{JsObject, ObjectData, CONSTRUCTOR, PROTOTYPE},
+    object::{
+        internal_methods::InternalMethodContext, JsObject, ObjectData, CONSTRUCTOR, PROTOTYPE,
+    },
     property::PropertyDescriptorBuilder,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult, JsValue,
@@ -61,7 +63,7 @@ impl Operation for SetClassPrototype {
                     .enumerable(false)
                     .configurable(true)
                     .build(),
-                context,
+                &mut InternalMethodContext::new(context),
             )
             .expect("cannot fail per spec");
 
diff --git a/boa_engine/src/vm/opcode/set/private.rs b/boa_engine/src/vm/opcode/set/private.rs
index b5bfe61d0c8..749a3b85fc2 100644
--- a/boa_engine/src/vm/opcode/set/private.rs
+++ b/boa_engine/src/vm/opcode/set/private.rs
@@ -1,5 +1,5 @@
 use crate::{
-    object::PrivateElement,
+    object::{internal_methods::InternalMethodContext, PrivateElement},
     property::PropertyDescriptor,
     string::utf16,
     vm::{opcode::Operation, CompletionType},
@@ -89,7 +89,11 @@ impl Operation for SetPrivateMethod {
             .configurable(true)
             .build();
         value
-            .__define_own_property__(&utf16!("name").into(), desc, context)
+            .__define_own_property__(
+                &utf16!("name").into(),
+                desc,
+                &mut InternalMethodContext::new(context),
+            )
             .expect("failed to set name property on private method");
 
         let object = context.vm.pop();
diff --git a/boa_engine/src/vm/opcode/set/property.rs b/boa_engine/src/vm/opcode/set/property.rs
index 245f3184619..606c9bca131 100644
--- a/boa_engine/src/vm/opcode/set/property.rs
+++ b/boa_engine/src/vm/opcode/set/property.rs
@@ -2,6 +2,7 @@ use boa_macros::utf16;
 
 use crate::{
     builtins::function::set_function_name,
+    object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes},
     property::{PropertyDescriptor, PropertyKey},
     vm::{opcode::Operation, CompletionType},
     Context, JsNativeError, JsResult, JsString, JsValue,
@@ -19,7 +20,7 @@ impl Operation for SetPropertyByName {
     const INSTRUCTION: &'static str = "INST - SetPropertyByName";
 
     fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
-        let index = context.vm.read::<u32>();
+        let ic_index = context.vm.read::<u32>() as usize;
 
         let value = context.vm.pop();
         let receiver = context.vm.pop();
@@ -30,16 +31,81 @@ impl Operation for SetPropertyByName {
             object.to_object(context)?
         };
 
-        let name: PropertyKey = context.vm.frame().code_block.names[index as usize]
-            .clone()
-            .into();
+        let ic = &context.vm.frame().code_block().ic[ic_index];
+        let mut slot = ic.slot();
+        if slot.is_cachable() {
+            let object_borrowed = object.borrow();
+            if ic.matches(object_borrowed.shape()) {
+                // println!(
+                //     "SET: T: \"{}\" {}: {:?}",
+                //     ic.name.to_std_string_escaped(),
+                //     slot.index,
+                //     slot.attributes
+                // );
+                let slot_index = slot.index as usize;
+
+                if slot.attributes.is_accessor_descriptor() {
+                    let result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
+                        let prototype = object_borrowed
+                            .properties()
+                            .shape
+                            .prototype()
+                            .expect("prototype should have value");
+                        let prototype = prototype.borrow();
+
+                        prototype.properties().storage[slot_index + 1].clone()
+                    } else {
+                        object_borrowed.properties().storage[slot_index + 1].clone()
+                    };
+
+                    drop(object_borrowed);
+                    if slot.attributes.has_set() && result.is_object() {
+                        result.as_object().expect("should contain getter").call(
+                            &receiver,
+                            &[value.clone()],
+                            context,
+                        )?;
+                    }
+                } else if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
+                    let prototype = object_borrowed
+                        .properties()
+                        .shape
+                        .prototype()
+                        .expect("prototype should have value");
+                    let mut prototype = prototype.borrow_mut();
+
+                    prototype.properties_mut().storage[slot_index] = value.clone();
+                } else {
+                    drop(object_borrowed);
+                    let mut object_borrowed = object.borrow_mut();
+                    object_borrowed.properties_mut().storage[slot_index] = value.clone();
+                }
+                context.vm.push(value);
+                return Ok(CompletionType::Normal);
+            }
+        }
 
+        let name: PropertyKey = ic.name.clone().into();
+
+        let context = &mut InternalMethodContext::new(context);
         let succeeded = object.__set__(name.clone(), value.clone(), receiver, context)?;
         if !succeeded && context.vm.frame().code_block.strict() {
             return Err(JsNativeError::typ()
                 .with_message(format!("cannot set non-writable property: {name}"))
                 .into());
         }
+
+        slot = *context.slot();
+
+        // println!("SET: F: \"{}\" {}: {:?}", name, slot.index, slot.attributes);
+
+        // Cache the property.
+        if succeeded && slot.is_cachable() {
+            let ic = &context.vm.frame().code_block.ic[ic_index];
+            let object_borrowed = object.borrow();
+            let shape = object_borrowed.shape().clone();
+            ic.set(shape, slot);
+        }
         context.vm.stack.push(value);
         Ok(CompletionType::Normal)
     }
@@ -138,7 +204,8 @@ impl Operation for SetPropertyByValue {
         }
 
         // Slow path:
-        let succeeded = object.__set__(key.clone(), value.clone(), receiver, context)?;
+        let succeeded =
+            object.__set__(key.clone(), value.clone(), receiver, &mut context.into())?;
         if !succeeded && context.vm.frame().code_block.strict() {
             return Err(JsNativeError::typ()
                 .with_message(format!("cannot set non-writable property: {key}"))
@@ -169,7 +236,7 @@ impl Operation for SetPropertyGetterByName {
             .clone()
             .into();
         let set = object
-            .__get_own_property__(&name, context)?
+            .__get_own_property__(&name, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::set)
             .cloned();
@@ -181,7 +248,7 @@ impl Operation for SetPropertyGetterByName {
                 .enumerable(true)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -204,8 +271,9 @@ impl Operation for SetPropertyGetterByValue {
         let object = context.vm.pop();
         let object = object.to_object(context)?;
         let name = key.to_property_key(context)?;
+
         let set = object
-            .__get_own_property__(&name, context)?
+            .__get_own_property__(&name, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::set)
             .cloned();
@@ -217,7 +285,7 @@ impl Operation for SetPropertyGetterByValue {
                 .enumerable(true)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -242,8 +310,9 @@ impl Operation for SetPropertySetterByName {
         let name = context.vm.frame().code_block.names[index as usize]
             .clone()
             .into();
+
         let get = object
-            .__get_own_property__(&name, context)?
+            .__get_own_property__(&name, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::get)
             .cloned();
@@ -255,7 +324,7 @@ impl Operation for SetPropertySetterByName {
                 .enumerable(true)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
@@ -278,8 +347,9 @@ impl Operation for SetPropertySetterByValue {
         let object = context.vm.pop();
         let object = object.to_object(context)?;
         let name = key.to_property_key(context)?;
+
         let get = object
-            .__get_own_property__(&name, context)?
+            .__get_own_property__(&name, &mut InternalMethodContext::new(context))?
             .as_ref()
             .and_then(PropertyDescriptor::get)
             .cloned();
@@ -291,7 +361,7 @@ impl Operation for SetPropertySetterByValue {
                 .enumerable(true)
                 .configurable(true)
                 .build(),
-            context,
+            &mut InternalMethodContext::new(context),
         )?;
         Ok(CompletionType::Normal)
     }
diff --git a/boa_engine/src/vm/opcode/set/prototype.rs b/boa_engine/src/vm/opcode/set/prototype.rs
index 733410e9e3e..4360834b39b 100644
--- a/boa_engine/src/vm/opcode/set/prototype.rs
+++ b/boa_engine/src/vm/opcode/set/prototype.rs
@@ -1,4 +1,5 @@
 use crate::{
+    object::internal_methods::InternalMethodContext,
     vm::{opcode::Operation, CompletionType},
     Context, JsResult,
 };
@@ -28,7 +29,7 @@ impl Operation for SetPrototype {
 
         let object = object.as_object().expect("object is not an object");
         object
-            .__set_prototype_of__(prototype, context)
+            .__set_prototype_of__(prototype, &mut InternalMethodContext::new(context))
             .expect("cannot fail per spec");
 
         Ok(CompletionType::Normal)