From 3681768f9eb207ce1b6a3f5b4b8fad5a7d69ce24 Mon Sep 17 00:00:00 2001 From: Haled Odat <8566042+HalidOdat@users.noreply.github.com> Date: Tue, 5 Dec 2023 00:35:15 +0100 Subject: [PATCH] Implement inline caching tests and cleanup --- .../engine/src/object/internal_methods/mod.rs | 7 - core/engine/src/object/property_map.rs | 5 +- core/engine/src/object/shape/mod.rs | 13 + .../src/object/shape/shared_shape/mod.rs | 10 + core/engine/src/object/shape/slot.rs | 10 + core/engine/src/object/shape/unique_shape.rs | 10 + core/engine/src/vm/code_block.rs | 46 +-- core/engine/src/vm/inline_cache/mod.rs | 61 ++++ core/engine/src/vm/inline_cache/tests.rs | 310 ++++++++++++++++++ core/engine/src/vm/mod.rs | 5 +- core/engine/src/vm/opcode/get/property.rs | 53 ++- core/engine/src/vm/opcode/set/property.rs | 78 ++--- 12 files changed, 480 insertions(+), 128 deletions(-) create mode 100644 core/engine/src/vm/inline_cache/mod.rs create mode 100644 core/engine/src/vm/inline_cache/tests.rs diff --git a/core/engine/src/object/internal_methods/mod.rs b/core/engine/src/object/internal_methods/mod.rs index 8fb32e0d737..e37d8e8c3e3 100644 --- a/core/engine/src/object/internal_methods/mod.rs +++ b/core/engine/src/object/internal_methods/mod.rs @@ -577,8 +577,6 @@ pub(crate) fn ordinary_has_property( // 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]](). @@ -627,8 +625,6 @@ pub(crate) fn ordinary_get( } } Some(ref desc) => { - context.slot().attributes |= SlotAttributes::FOUND; - match desc.kind() { // 4. If IsDataDescriptor(desc) is true, return desc.[[Value]]. DescriptorKind::Data { @@ -747,8 +743,6 @@ pub(crate) fn ordinary_set( 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()); @@ -902,7 +896,6 @@ pub(crate) fn validate_and_apply_property_descriptor( }, slot, ); - slot.attributes |= SlotAttributes::FOUND; } // e. Return true. diff --git a/core/engine/src/object/property_map.rs b/core/engine/src/object/property_map.rs index d7352453ccf..53c14512b7b 100644 --- a/core/engine/src/object/property_map.rs +++ b/core/engine/src/object/property_map.rs @@ -291,8 +291,9 @@ impl PropertyMap { 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; + out_slot.attributes = (out_slot.attributes & SlotAttributes::INLINE_CACHE_BITS) + | slot.attributes + | SlotAttributes::FOUND; return Some(self.get_storage(slot)); } diff --git a/core/engine/src/object/shape/mod.rs b/core/engine/src/object/shape/mod.rs index 472a8b4a159..ace8cab7eaf 100644 --- a/core/engine/src/object/shape/mod.rs +++ b/core/engine/src/object/shape/mod.rs @@ -251,6 +251,19 @@ impl WeakShape { WeakShape::None => 0, } } + + /// Return location in memory of the [`Shape`]. + /// + /// Returns `0` if the shape has been freed. + #[inline] + #[must_use] + pub(crate) fn upgrade(&self) -> Option { + match self { + WeakShape::Shared(shape) => Some(shape.upgrade()?.into()), + WeakShape::Unique(shape) => Some(shape.upgrade()?.into()), + WeakShape::None => None, + } + } } impl From<&Shape> for WeakShape { diff --git a/core/engine/src/object/shape/shared_shape/mod.rs b/core/engine/src/object/shape/shared_shape/mod.rs index 0dc58542c30..e4dabcea702 100644 --- a/core/engine/src/object/shape/shared_shape/mod.rs +++ b/core/engine/src/object/shape/shared_shape/mod.rs @@ -499,6 +499,16 @@ impl WeakSharedShape { ptr as usize }) } + + /// Upgrade returns a [`SharedShape`] pointer for the internal value if the pointer is still live, + /// or [`None`] if the value was already garbage collected. + #[inline] + #[must_use] + pub(crate) fn upgrade(&self) -> Option { + Some(SharedShape { + inner: self.inner.upgrade()?, + }) + } } impl From<&SharedShape> for WeakSharedShape { diff --git a/core/engine/src/object/shape/slot.rs b/core/engine/src/object/shape/slot.rs index cc95eba01c4..ead6b0c1f85 100644 --- a/core/engine/src/object/shape/slot.rs +++ b/core/engine/src/object/shape/slot.rs @@ -45,6 +45,11 @@ impl SlotAttributes { pub(crate) const fn is_cachable(self) -> bool { !self.contains(Self::NOT_CACHABLE) && self.contains(Self::FOUND) } + + #[cfg(test)] + pub(crate) const fn in_prototype(self) -> bool { + self.contains(Self::PROTOTYPE) + } } /// Represents an [`u32`] index and it's slot attributes of an element in a object storage. @@ -69,6 +74,11 @@ impl Slot { self.attributes.is_cachable() } + #[cfg(test)] + pub(crate) const fn in_prototype(self) -> bool { + self.attributes.in_prototype() + } + /// Get the width of the slot. pub(crate) fn width(self) -> u32 { self.attributes.width() diff --git a/core/engine/src/object/shape/unique_shape.rs b/core/engine/src/object/shape/unique_shape.rs index ca6167c62d2..971b009aa26 100644 --- a/core/engine/src/object/shape/unique_shape.rs +++ b/core/engine/src/object/shape/unique_shape.rs @@ -258,6 +258,16 @@ impl WeakUniqueShape { ptr as usize }) } + + /// Upgrade returns a [`UniqueShape`] pointer for the internal value if the pointer is still live, + /// or [`None`] if the value was already garbage collected. + #[inline] + #[must_use] + pub(crate) fn upgrade(&self) -> Option { + Some(UniqueShape { + inner: self.inner.upgrade()?, + }) + } } impl From<&UniqueShape> for WeakUniqueShape { diff --git a/core/engine/src/vm/code_block.rs b/core/engine/src/vm/code_block.rs index 1ceec0f8ae7..0c700c2c3f7 100644 --- a/core/engine/src/vm/code_block.rs +++ b/core/engine/src/vm/code_block.rs @@ -8,20 +8,17 @@ use crate::{ OrdinaryObject, }, environments::{BindingLocator, CompileTimeEnvironment}, - object::{ - shape::{slot::Slot, Shape, WeakShape}, - JsObject, - }, + object::JsObject, Context, JsBigInt, JsString, JsValue, }; use bitflags::bitflags; use boa_ast::function::FormalParameterList; -use boa_gc::{empty_trace, Finalize, Gc, GcRefCell, Trace}; +use boa_gc::{empty_trace, Finalize, Gc, Trace}; use boa_profiler::Profiler; use std::{cell::Cell, fmt::Display, mem::size_of, rc::Rc}; use thin_vec::ThinVec; -use super::{Instruction, InstructionIterator}; +use super::{InlineCache, Instruction, InstructionIterator}; /// This represents whether a value can be read from [`CodeBlock`] code. /// @@ -123,43 +120,6 @@ pub(crate) enum Constant { CompileTimeEnvironment(#[unsafe_ignore_trace] Rc), } -/// An inline cache entry for a property access. -#[derive(Clone, Debug, Trace, Finalize)] -pub(crate) struct InlineCache { - /// The property that is accessed. - pub(crate) name: JsString, - - /// A pointer is kept to the shape to avoid the shape from being deallocated. - pub(crate) shape: GcRefCell, - - /// The [`Slot`] of the property. - #[unsafe_ignore_trace] - pub(crate) slot: Cell, -} - -impl InlineCache { - pub(crate) const fn new(name: JsString) -> Self { - Self { - name, - shape: GcRefCell::new(WeakShape::None), - slot: Cell::new(Slot::new()), - } - } - - pub(crate) fn set(&self, shape: &Shape, slot: Slot) { - *self.shape.borrow_mut() = shape.into(); - self.slot.set(slot); - } - - pub(crate) fn slot(&self) -> Slot { - self.slot.get() - } - - pub(crate) fn matches(&self, shape: &Shape) -> bool { - self.shape.borrow().to_addr_usize() == shape.to_addr_usize() - } -} - /// The internal representation of a JavaScript function. /// /// A `CodeBlock` is generated for each function compiled by the diff --git a/core/engine/src/vm/inline_cache/mod.rs b/core/engine/src/vm/inline_cache/mod.rs new file mode 100644 index 00000000000..257fafdaa11 --- /dev/null +++ b/core/engine/src/vm/inline_cache/mod.rs @@ -0,0 +1,61 @@ +use std::cell::Cell; + +use boa_gc::GcRefCell; +use boa_macros::{Finalize, Trace}; + +use crate::{ + object::shape::{slot::Slot, Shape, WeakShape}, + JsString, +}; + +#[cfg(test)] +mod tests; + +/// An inline cache entry for a property access. +#[derive(Clone, Debug, Trace, Finalize)] +pub(crate) struct InlineCache { + /// The property that is accessed. + pub(crate) name: JsString, + + /// A pointer is kept to the shape to avoid the shape from being deallocated. + pub(crate) shape: GcRefCell, + + /// The [`Slot`] of the property. + #[unsafe_ignore_trace] + pub(crate) slot: Cell, +} + +impl InlineCache { + pub(crate) const fn new(name: JsString) -> Self { + Self { + name, + shape: GcRefCell::new(WeakShape::None), + slot: Cell::new(Slot::new()), + } + } + + pub(crate) fn set(&self, shape: &Shape, slot: Slot) { + *self.shape.borrow_mut() = shape.into(); + self.slot.set(slot); + } + + pub(crate) fn slot(&self) -> Slot { + self.slot.get() + } + + /// Returns true, if the [`InlineCache`]'s shape matches with the given shape. + /// + /// Otherwise we reset the internal weak reference to [`WeakShape::None`], + /// so it can be deallocated by the GC. + pub(crate) fn match_or_reset(&self, shape: &Shape) -> Option<(Shape, Slot)> { + let mut old = self.shape.borrow_mut(); + + let old_upgraded = old.upgrade(); + if old_upgraded.as_ref().map_or(0, Shape::to_addr_usize) == shape.to_addr_usize() { + return old_upgraded.map(|shape| (shape, self.slot())); + } + + *old = WeakShape::None; + None + } +} diff --git a/core/engine/src/vm/inline_cache/tests.rs b/core/engine/src/vm/inline_cache/tests.rs new file mode 100644 index 00000000000..05b3e97175d --- /dev/null +++ b/core/engine/src/vm/inline_cache/tests.rs @@ -0,0 +1,310 @@ +use crate::{ + builtins::OrdinaryObject, + js_string, + object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes}, + property::{PropertyDescriptor, PropertyKey}, + Context, +}; + +#[test] +fn get_own_property_internal_method() { + let context = &mut Context::default(); + + let o = context + .intrinsics() + .templates() + .ordinary_object() + .create(OrdinaryObject, Vec::default()); + + let property: PropertyKey = js_string!("prop").into(); + let value = 100; + + o.set(property.clone(), value, true, context) + .expect("should not fail"); + + let context = &mut InternalMethodContext::new(context); + + assert_eq!(context.slot().index, 0); + assert_eq!(context.slot().attributes, SlotAttributes::empty()); + + o.__get_own_property__(&property, context) + .expect("should not fail"); + + assert!( + !context.slot().in_prototype(), + "Since it's an owned property, the prototype bit should not be set" + ); + + assert!( + context.slot().is_cachable(), + "Since it's an owned property, this should be cachable" + ); + + let shape = o.borrow().shape().clone(); + + let slot = shape.lookup(&property); + + assert!(slot.is_some(), "the property should be found in the object"); + + let slot = slot.expect("the property should be found in the object"); + + assert_eq!(context.slot().index, slot.index); +} + +#[test] +fn get_internal_method() { + let context = &mut Context::default(); + + let o = context + .intrinsics() + .templates() + .ordinary_object() + .create(OrdinaryObject, Vec::default()); + + let property: PropertyKey = js_string!("prop").into(); + let value = 100; + + o.set(property.clone(), value, true, context) + .expect("should not fail"); + + let context = &mut InternalMethodContext::new(context); + + assert_eq!(context.slot().index, 0); + assert_eq!(context.slot().attributes, SlotAttributes::empty()); + + o.__get__(&property, o.clone().into(), context) + .expect("should not fail"); + + assert!( + !context.slot().in_prototype(), + "Since it's an owned property, the prototype bit should not be set" + ); + + assert!( + context.slot().is_cachable(), + "Since it's an owned property, this should be cachable" + ); + + let shape = o.borrow().shape().clone(); + + let slot = shape.lookup(&property); + + assert!(slot.is_some(), "the property should be found in the object"); + + let slot = slot.expect("the property should be found in the object"); + + assert_eq!(context.slot().index, slot.index); +} + +#[test] +fn get_internal_method_in_prototype() { + let context = &mut Context::default(); + + let o = context + .intrinsics() + .templates() + .ordinary_object() + .create(OrdinaryObject, Vec::default()); + + let property: PropertyKey = js_string!("prop").into(); + let value = 100; + + let prototype = context.intrinsics().constructors().object().prototype(); + + prototype + .set(property.clone(), value, true, context) + .expect("should not fail"); + + let context = &mut InternalMethodContext::new(context); + + assert_eq!(context.slot().index, 0); + assert_eq!(context.slot().attributes, SlotAttributes::empty()); + + o.__get__(&property, o.clone().into(), context) + .expect("should not fail"); + + assert!( + context.slot().in_prototype(), + "Since it's an prototype property, the prototype bit should not be set" + ); + + assert!( + context.slot().is_cachable(), + "Since it's an prototype property, this should be cachable" + ); + + let shape = prototype.borrow().shape().clone(); + + let slot = shape.lookup(&property); + + assert!(slot.is_some(), "the property should be found in the object"); + + let slot = slot.expect("the property should be found in the object"); + + assert_eq!(context.slot().index, slot.index); +} + +#[test] +fn define_own_property_internal_method_non_existant_property() { + let context = &mut Context::default(); + + let o = context + .intrinsics() + .templates() + .ordinary_object() + .create(OrdinaryObject, Vec::default()); + + let property: PropertyKey = js_string!("prop").into(); + let value = 100; + + o.set(property.clone(), value, true, context) + .expect("should not fail"); + + let context = &mut InternalMethodContext::new(context); + + assert_eq!(context.slot().index, 0); + assert_eq!(context.slot().attributes, SlotAttributes::empty()); + + o.__define_own_property__( + &property, + PropertyDescriptor::builder() + .value(value) + .writable(true) + .configurable(true) + .enumerable(true) + .build(), + context, + ) + .expect("should not fail"); + + assert!( + !context.slot().in_prototype(), + "Since it's an owned property, the prototype bit should not be set" + ); + + assert!( + context.slot().is_cachable(), + "Since it's an owned property, this should be cachable" + ); + + let shape = o.borrow().shape().clone(); + + let slot = shape.lookup(&property); + + assert!(slot.is_some(), "the property should be found in the object"); + + let slot = slot.expect("the property should be found in the object"); + + assert_eq!(context.slot().index, slot.index); +} + +#[test] +fn define_own_property_internal_method_existing_property_property() { + let context = &mut Context::default(); + + let o = context + .intrinsics() + .templates() + .ordinary_object() + .create(OrdinaryObject, Vec::default()); + + let property: PropertyKey = js_string!("prop").into(); + let value = 100; + + o.set(property.clone(), value, true, context) + .expect("should not fail"); + + o.__define_own_property__( + &property, + PropertyDescriptor::builder() + .value(value) + .writable(true) + .configurable(true) + .enumerable(true) + .build(), + &mut context.into(), + ) + .expect("should not fail"); + + let context = &mut InternalMethodContext::new(context); + + assert_eq!(context.slot().index, 0); + assert_eq!(context.slot().attributes, SlotAttributes::empty()); + + o.__define_own_property__( + &property, + PropertyDescriptor::builder() + .value(value + 100) + .writable(true) + .configurable(true) + .enumerable(true) + .build(), + context, + ) + .expect("should not fail"); + + assert!( + !context.slot().in_prototype(), + "Since it's an owned property, the prototype bit should not be set" + ); + + assert!( + context.slot().is_cachable(), + "Since it's an owned property, this should be cachable" + ); + + let shape = o.borrow().shape().clone(); + + let slot = shape.lookup(&property); + + assert!(slot.is_some(), "the property should be found in the object"); + + let slot = slot.expect("the property should be found in the object"); + + assert_eq!(context.slot().index, slot.index); +} + +#[test] +fn set_internal_method() { + let context = &mut Context::default(); + + let o = context + .intrinsics() + .templates() + .ordinary_object() + .create(OrdinaryObject, Vec::default()); + + let property: PropertyKey = js_string!("prop").into(); + let value = 100; + + o.set(property.clone(), value, true, context) + .expect("should not fail"); + + let context = &mut InternalMethodContext::new(context); + + assert_eq!(context.slot().index, 0); + assert_eq!(context.slot().attributes, SlotAttributes::empty()); + + o.__set__(property.clone(), value.into(), o.clone().into(), context) + .expect("should not fail"); + + assert!( + !context.slot().in_prototype(), + "Since it's an owned property, the prototype bit should not be set" + ); + + assert!( + context.slot().is_cachable(), + "Since it's an owned property, this should be cachable" + ); + + let shape = o.borrow().shape().clone(); + + let slot = shape.lookup(&property); + + assert!(slot.is_some(), "the property should be found in the object"); + + let slot = slot.expect("the property should be found in the object"); + + assert_eq!(context.slot().index, slot.index); +} diff --git a/core/engine/src/vm/mod.rs b/core/engine/src/vm/mod.rs index 9c54f049051..9711ced2279 100644 --- a/core/engine/src/vm/mod.rs +++ b/core/engine/src/vm/mod.rs @@ -19,13 +19,15 @@ use crate::sys::time::Instant; mod call_frame; mod code_block; mod completion_record; +mod inline_cache; mod opcode; - mod runtime_limits; #[cfg(feature = "flowgraph")] pub mod flowgraph; +pub(crate) use inline_cache::InlineCache; + // TODO: see if this can be exposed on all features. #[allow(unused_imports)] pub(crate) use opcode::{Instruction, InstructionIterator, Opcode, VaryingOperandKind}; @@ -39,7 +41,6 @@ pub(crate) use { call_frame::CallFrameFlags, code_block::{ create_function_object, create_function_object_fast, CodeBlockFlags, Constant, Handler, - InlineCache, }, completion_record::CompletionRecord, opcode::BindingOpcode, diff --git a/core/engine/src/vm/opcode/get/property.rs b/core/engine/src/vm/opcode/get/property.rs index ae193c81cdd..dbe4602086c 100644 --- a/core/engine/src/vm/opcode/get/property.rs +++ b/core/engine/src/vm/opcode/get/property.rs @@ -23,45 +23,38 @@ impl GetPropertyByName { }; let ic = &context.vm.frame().code_block().ic[index]; - let mut slot = ic.slot(); - if slot.is_cachable() { - let object_borrowed = object.borrow(); - if ic.matches(object_borrowed.shape()) { - let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { - let prototype = object - .borrow() - .properties() - .shape - .prototype() - .expect("prototype should have value"); - let prototype = prototype.borrow(); - prototype.properties().storage[slot.index as usize].clone() - } else { - object_borrowed.properties().storage[slot.index as usize].clone() - }; - - drop(object_borrowed); - if slot.attributes.has_get() && result.is_object() { - result = result.as_object().expect("should contain getter").call( - &receiver, - &[], - context, - )?; - } - context.vm.push(result); - return Ok(CompletionType::Normal); + let object_borrowed = object.borrow(); + if let Some((shape, slot)) = ic.match_or_reset(object_borrowed.shape()) { + let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { + let prototype = 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); } + drop(object_borrowed); + let key: PropertyKey = ic.name.clone().into(); let context = &mut InternalMethodContext::new(context); let result = object.__get__(&key, receiver, context)?; - slot = *context.slot(); - // Cache the property. - if slot.attributes.is_cachable() { + let slot = *context.slot(); + if slot.is_cachable() { let ic = &context.vm.frame().code_block.ic[index]; let object_borrowed = object.borrow(); let shape = object_borrowed.shape(); diff --git a/core/engine/src/vm/opcode/set/property.rs b/core/engine/src/vm/opcode/set/property.rs index 07ac703679c..cb99f85aedf 100644 --- a/core/engine/src/vm/opcode/set/property.rs +++ b/core/engine/src/vm/opcode/set/property.rs @@ -27,52 +27,43 @@ impl SetPropertyByName { }; let ic = &context.vm.frame().code_block().ic[index]; - let mut slot = ic.slot(); - if slot.is_cachable() { - let object_borrowed = object.borrow(); - if ic.matches(object_borrowed.shape()) { - let slot_index = slot.index as usize; - - if slot.attributes.is_accessor_descriptor() { - let result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { - let prototype = object_borrowed - .properties() - .shape - .prototype() - .expect("prototype should have value"); - let prototype = prototype.borrow(); - - prototype.properties().storage[slot_index + 1].clone() - } else { - object_borrowed.properties().storage[slot_index + 1].clone() - }; - - drop(object_borrowed); - if slot.attributes.has_set() && result.is_object() { - result.as_object().expect("should contain getter").call( - &receiver, - &[value.clone()], - context, - )?; - } - } else if slot.attributes.contains(SlotAttributes::PROTOTYPE) { - let prototype = object_borrowed - .properties() - .shape - .prototype() - .expect("prototype should have value"); - let mut prototype = prototype.borrow_mut(); - - prototype.properties_mut().storage[slot_index] = value.clone(); + + let object_borrowed = object.borrow(); + if let Some((shape, slot)) = ic.match_or_reset(object_borrowed.shape()) { + let slot_index = slot.index as usize; + + if slot.attributes.is_accessor_descriptor() { + let result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) { + let prototype = shape.prototype().expect("prototype should have value"); + let prototype = prototype.borrow(); + + prototype.properties().storage[slot_index + 1].clone() } else { - drop(object_borrowed); - let mut object_borrowed = object.borrow_mut(); - object_borrowed.properties_mut().storage[slot_index] = value.clone(); + 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, + )?; } - context.vm.push(value); - return Ok(CompletionType::Normal); + } else if slot.attributes.contains(SlotAttributes::PROTOTYPE) { + let prototype = 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); } + drop(object_borrowed); let name: PropertyKey = ic.name.clone().into(); @@ -84,9 +75,8 @@ impl SetPropertyByName { .into()); } - slot = *context.slot(); - // Cache the property. + let slot = *context.slot(); if succeeded && slot.is_cachable() { let ic = &context.vm.frame().code_block.ic[index]; let object_borrowed = object.borrow();