[compiler] Concurrently read properties from dictionary objects

Replace GetOwnDictionaryPropertyFromHeap with
TryGetOwnDictionaryPropertyFromHeap which will return {} if we are
trying to read out of bounds of the heap or the object. This is done so
that we can concurrently use the method.

We introduce a new compilation dependency (DependOnPropertyValueSame)
which checks that the background thread indeed read the correct value.

Bug: v8:7790
Change-Id: Ia5e308faf1f65add638cd271995f4f33416fbd15
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2930480
Commit-Queue: Santiago Aboy Solanes <solanes@chromium.org>
Reviewed-by: Georg Neis <neis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#75248}
This commit is contained in:
Santiago Aboy Solanes 2021-06-18 14:47:26 +01:00 committed by V8 LUCI CQ
parent 919fa26613
commit 35931e83bd
16 changed files with 235 additions and 53 deletions

View File

@ -645,7 +645,7 @@ PropertyAccessInfo AccessInfoFactory::ComputeDictionaryProtoAccessInfo(
}
auto get_accessors = [&]() {
return JSObject::DictionaryPropertyAt(holder, dictionary_index);
return JSObject::DictionaryPropertyAt(isolate(), holder, dictionary_index);
};
Handle<Map> holder_map = broker()->CanonicalPersistentHandle(holder->map());
return AccessorAccessInfoHelper(isolate(), zone(), broker(), this,

View File

@ -4,10 +4,12 @@
#include "src/compiler/compilation-dependencies.h"
#include "src/base/optional.h"
#include "src/compiler/compilation-dependency.h"
#include "src/execution/protectors.h"
#include "src/handles/handles-inl.h"
#include "src/objects/allocation-site-inl.h"
#include "src/objects/internal-index.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-function-inl.h"
#include "src/objects/objects-inl.h"
@ -282,6 +284,61 @@ class OwnConstantDataPropertyDependency final : public CompilationDependency {
ObjectRef const value_;
};
class OwnConstantDictionaryPropertyDependency final
: public CompilationDependency {
public:
OwnConstantDictionaryPropertyDependency(JSHeapBroker* broker,
const JSObjectRef& holder,
InternalIndex index,
const ObjectRef& value)
: broker_(broker),
holder_(holder),
map_(holder.map()),
index_(index),
value_(value) {
// We depend on map() being cached.
STATIC_ASSERT(ref_traits<JSObject>::ref_serialization_kind !=
RefSerializationKind::kNeverSerialized);
}
bool IsValid() const override {
if (holder_.object()->map() != *map_.object()) {
TRACE_BROKER_MISSING(broker_,
"Map change detected in " << holder_.object());
return false;
}
base::Optional<Object> maybe_value = JSObject::DictionaryPropertyAt(
holder_.object(), index_, broker_->isolate()->heap());
if (!maybe_value) {
TRACE_BROKER_MISSING(
broker_, holder_.object()
<< "has a value that might not safe to read at index "
<< index_.as_int());
return false;
}
if (*maybe_value != *value_.object()) {
TRACE_BROKER_MISSING(broker_, "Constant property value changed in "
<< holder_.object()
<< " at InternalIndex "
<< index_.as_int());
return false;
}
return true;
}
void Install(Handle<Code> code) const override {}
private:
JSHeapBroker* const broker_;
JSObjectRef const holder_;
MapRef const map_;
InternalIndex const index_;
ObjectRef const value_;
};
class TransitionDependency final : public CompilationDependency {
public:
explicit TransitionDependency(const MapRef& map) : map_(map) {
@ -728,6 +785,12 @@ void CompilationDependencies::DependOnOwnConstantDataProperty(
broker_, holder, map, representation, index, value));
}
void CompilationDependencies::DependOnOwnConstantDictionaryProperty(
const JSObjectRef& holder, InternalIndex index, const ObjectRef& value) {
RecordDependency(zone_->New<OwnConstantDictionaryPropertyDependency>(
broker_, holder, index, value));
}
bool CompilationDependencies::Commit(Handle<Code> code) {
// Dependencies are context-dependent. In the future it may be possible to
// restore them in the consumer native context, but for now they are

View File

@ -100,14 +100,20 @@ class V8_EXPORT_PRIVATE CompilationDependencies : public ZoneObject {
void DependOnOwnConstantElement(const JSObjectRef& holder, uint32_t index,
const ObjectRef& element);
// Record the assumption that the {value} read from {holder} on the background
// thread is the correct value for a given property.
// Record the assumption that the {value} read from {holder} at {index} on the
// background thread is the correct value for a given property.
void DependOnOwnConstantDataProperty(const JSObjectRef& holder,
const MapRef& map,
Representation representation,
FieldIndex index,
const ObjectRef& value);
// Record the assumption that the {value} read from {holder} at {index} on the
// background thread is the correct value for a given dictionary property.
void DependOnOwnConstantDictionaryProperty(const JSObjectRef& holder,
InternalIndex index,
const ObjectRef& value);
// For each given map, depend on the stability of (the maps of) all prototypes
// up to (and including) the {last_prototype}.
template <class MapContainer>

View File

@ -10,6 +10,8 @@
#include "src/api/api-inl.h"
#include "src/ast/modules.h"
#include "src/base/optional.h"
#include "src/base/platform/platform.h"
#include "src/codegen/code-factory.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/graph-reducer.h"
@ -607,12 +609,18 @@ base::Optional<ObjectRef> GetOwnFastDataPropertyFromHeap(
return MakeRef(broker, *possibly_wrapped);
}
ObjectRef GetOwnDictionaryPropertyFromHeap(JSHeapBroker* broker,
Handle<JSObject> receiver,
InternalIndex dict_index) {
Handle<Object> constant =
JSObject::DictionaryPropertyAt(receiver, dict_index);
return MakeRef(broker, constant);
// Tries to get the property at {dict_index}. If we are within bounds of the
// object, we are guaranteed to see valid heap words even if the data is wrong.
base::Optional<ObjectRef> GetOwnDictionaryPropertyFromHeap(
JSHeapBroker* broker, Handle<JSObject> receiver, InternalIndex dict_index) {
DisallowGarbageCollection no_gc;
// DictionaryPropertyAt will check that we are within the bounds of the
// object.
base::Optional<Object> maybe_constant = JSObject::DictionaryPropertyAt(
receiver, dict_index, broker->isolate()->heap());
DCHECK_IMPLIES(broker->IsMainThread(), maybe_constant);
if (!maybe_constant) return {};
return TryMakeRef(broker, maybe_constant.value());
}
} // namespace
@ -674,7 +682,8 @@ ObjectData* JSObjectData::GetOwnDictionaryProperty(JSHeapBroker* broker,
}
ObjectRef property = GetOwnDictionaryPropertyFromHeap(
broker, Handle<JSObject>::cast(object()), dict_index);
broker, Handle<JSObject>::cast(object()), dict_index)
.value();
ObjectData* result(property.data());
own_properties_.insert(std::make_pair(dict_index.as_int(), result));
return result;
@ -4029,12 +4038,18 @@ base::Optional<ObjectRef> JSObjectRef::GetOwnFastDataProperty(
return TryMakeRef<Object>(broker(), property);
}
ObjectRef JSObjectRef::GetOwnDictionaryProperty(
InternalIndex index, SerializationPolicy policy) const {
base::Optional<ObjectRef> JSObjectRef::GetOwnDictionaryProperty(
InternalIndex index, CompilationDependencies* dependencies,
SerializationPolicy policy) const {
CHECK(index.is_found());
if (data_->should_access_heap()) {
return GetOwnDictionaryPropertyFromHeap(
broker(), Handle<JSObject>::cast(object()), index);
if (data_->should_access_heap() || broker()->is_concurrent_inlining()) {
base::Optional<ObjectRef> result =
GetOwnDictionaryPropertyFromHeap(broker(), object(), index);
if (dependencies != nullptr && result.has_value()) {
dependencies->DependOnOwnConstantDictionaryProperty(*this, index,
*result);
}
return result;
}
ObjectData* property =
data()->AsJSObject()->GetOwnDictionaryProperty(broker(), index, policy);

View File

@ -354,9 +354,10 @@ class JSObjectRef : public JSReceiverRef {
// Return the value of the dictionary property at {index} in the dictionary
// if {index} is known to be an own data property of the object.
ObjectRef GetOwnDictionaryProperty(
InternalIndex index, SerializationPolicy policy =
SerializationPolicy::kAssumeSerialized) const;
base::Optional<ObjectRef> GetOwnDictionaryProperty(
InternalIndex index, CompilationDependencies* dependencies,
SerializationPolicy policy =
SerializationPolicy::kAssumeSerialized) const;
// When concurrent inlining is enabled, reads the elements through a direct
// relaxed read. This is to ease the transition to unserialized (or

View File

@ -5,6 +5,7 @@
#include "src/compiler/js-native-context-specialization.h"
#include "src/api/api-inl.h"
#include "src/base/optional.h"
#include "src/builtins/accessors.h"
#include "src/codegen/code-factory.h"
#include "src/codegen/string-constants.h"
@ -1283,12 +1284,20 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
}
// Generate the actual property access.
ValueEffectControl continuation = BuildPropertyAccess(
base::Optional<ValueEffectControl> continuation = BuildPropertyAccess(
lookup_start_object, receiver, value, context, frame_state, effect,
control, feedback.name(), if_exceptions, access_info, access_mode);
value = continuation.value();
effect = continuation.effect();
control = continuation.control();
if (!continuation) {
// At this point we maybe have added nodes into the graph (e.g. via
// NewNode or BuildCheckMaps) in some cases but we haven't connected them
// to End since we haven't called ReplaceWithValue. Since they are nodes
// which are not connected with End, they will be removed by graph
// trimming.
return NoChange();
}
value = continuation->value();
effect = continuation->effect();
control = continuation->control();
} else {
// The final states for every polymorphic branch. We join them with
// Merge+Phi+EffectPhi at the bottom.
@ -1406,13 +1415,21 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess(
}
// Generate the actual property access.
ValueEffectControl continuation = BuildPropertyAccess(
base::Optional<ValueEffectControl> continuation = BuildPropertyAccess(
this_lookup_start_object, this_receiver, this_value, context,
frame_state, this_effect, this_control, feedback.name(),
if_exceptions, access_info, access_mode);
values.push_back(continuation.value());
effects.push_back(continuation.effect());
controls.push_back(continuation.control());
if (!continuation) {
// At this point we maybe have added nodes into the graph (e.g. via
// NewNode or BuildCheckMaps) in some cases but we haven't connected
// them to End since we haven't called ReplaceWithValue. Since they are
// nodes which are not connected with End, they will be removed by graph
// trimming.
return NoChange();
}
values.push_back(continuation->value());
effects.push_back(continuation->effect());
controls.push_back(continuation->control());
}
DCHECK_NULL(fallthrough_control);
@ -2340,7 +2357,7 @@ Node* JSNativeContextSpecialization::InlineApiCall(
graph()->NewNode(common()->Call(call_descriptor), index, inputs);
}
JSNativeContextSpecialization::ValueEffectControl
base::Optional<JSNativeContextSpecialization::ValueEffectControl>
JSNativeContextSpecialization::BuildPropertyLoad(
Node* lookup_start_object, Node* receiver, Node* context, Node* frame_state,
Node* effect, Node* control, NameRef const& name,
@ -2381,7 +2398,10 @@ JSNativeContextSpecialization::BuildPropertyLoad(
access_info.IsDictionaryProtoDataConstant());
PropertyAccessBuilder access_builder(jsgraph(), broker(), dependencies());
if (access_info.IsDictionaryProtoDataConstant()) {
value = access_builder.FoldLoadDictPrototypeConstant(access_info);
auto maybe_value =
access_builder.FoldLoadDictPrototypeConstant(access_info);
if (!maybe_value) return {};
value = maybe_value.value();
} else {
value = access_builder.BuildLoadDataField(
name, access_info, lookup_start_object, &effect, &control);
@ -2410,7 +2430,7 @@ JSNativeContextSpecialization::BuildPropertyTest(
return ValueEffectControl(value, effect, control);
}
JSNativeContextSpecialization::ValueEffectControl
base::Optional<JSNativeContextSpecialization::ValueEffectControl>
JSNativeContextSpecialization::BuildPropertyAccess(
Node* lookup_start_object, Node* receiver, Node* value, Node* context,
Node* frame_state, Node* effect, Node* control, NameRef const& name,

View File

@ -6,6 +6,7 @@
#define V8_COMPILER_JS_NATIVE_CONTEXT_SPECIALIZATION_H_
#include "src/base/flags.h"
#include "src/base/optional.h"
#include "src/compiler/graph-reducer.h"
#include "src/compiler/js-heap-broker.h"
#include "src/deoptimizer/deoptimize-reason.h"
@ -150,18 +151,17 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final
Node* control_;
};
// Construct the appropriate subgraph for property access.
ValueEffectControl BuildPropertyAccess(
// Construct the appropriate subgraph for property access. Return {} if the
// property access couldn't be built.
base::Optional<ValueEffectControl> BuildPropertyAccess(
Node* lookup_start_object, Node* receiver, Node* value, Node* context,
Node* frame_state, Node* effect, Node* control, NameRef const& name,
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info,
AccessMode access_mode);
ValueEffectControl BuildPropertyLoad(Node* lookup_start_object,
Node* receiver, Node* context,
Node* frame_state, Node* effect,
Node* control, NameRef const& name,
ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info);
base::Optional<ValueEffectControl> BuildPropertyLoad(
Node* lookup_start_object, Node* receiver, Node* context,
Node* frame_state, Node* effect, Node* control, NameRef const& name,
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info);
ValueEffectControl BuildPropertyStore(Node* receiver, Node* value,
Node* context, Node* frame_state,

View File

@ -4,17 +4,19 @@
#include "src/compiler/property-access-builder.h"
#include "src/base/optional.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/simplified-operator.h"
#include "src/objects/heap-number.h"
#include "src/objects/lookup.h"
#include "src/execution/isolate-inl.h"
#include "src/objects/field-index-inl.h"
#include "src/objects/heap-number.h"
#include "src/objects/internal-index.h"
#include "src/objects/lookup.h"
#include "src/objects/property-details.h"
namespace v8 {
namespace internal {
@ -148,15 +150,17 @@ MachineRepresentation PropertyAccessBuilder::ConvertRepresentation(
}
}
Node* PropertyAccessBuilder::FoldLoadDictPrototypeConstant(
base::Optional<Node*> PropertyAccessBuilder::FoldLoadDictPrototypeConstant(
PropertyAccessInfo const& access_info) {
DCHECK(V8_DICT_PROPERTY_CONST_TRACKING_BOOL);
DCHECK(access_info.IsDictionaryProtoDataConstant());
JSObjectRef holder =
MakeRef(broker(), access_info.holder().ToHandleChecked());
InternalIndex index = access_info.dictionary_index();
base::Optional<ObjectRef> value =
holder.GetOwnDictionaryProperty(access_info.dictionary_index());
holder.GetOwnDictionaryProperty(index, dependencies());
if (!value) return {};
for (Handle<Map> map : access_info.lookup_start_object_maps()) {
// Non-JSReceivers that passed AccessInfoFactory::ComputePropertyAccessInfo
@ -168,7 +172,7 @@ Node* PropertyAccessBuilder::FoldLoadDictPrototypeConstant(
Map::GetConstructorFunction(
*map, *broker()->target_native_context().object())
.value();
map = handle(constructor.initial_map(), isolate());
map = MakeRef(broker(), constructor.initial_map()).object();
DCHECK(map->IsJSObjectMap());
}
dependencies()->DependOnConstantInDictionaryPrototypeChain(

View File

@ -7,6 +7,7 @@
#include <vector>
#include "src/base/optional.h"
#include "src/codegen/machine-type.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/node.h"
@ -64,9 +65,11 @@ class PropertyAccessBuilder {
Node* lookup_start_object, Node** effect,
Node** control);
// Loads a constant value from a prototype object in dictionary mode and
// constant-folds it.
Node* FoldLoadDictPrototypeConstant(PropertyAccessInfo const& access_info);
// Tries to load a constant value from a prototype object in dictionary mode
// and constant-folds it. Returns {} if the constant couldn't be safely
// retrieved.
base::Optional<Node*> FoldLoadDictPrototypeConstant(
PropertyAccessInfo const& access_info);
// Builds the load for data-field access for minimorphic loads that use
// dynamic map checks. These cannot depend on any information from the maps.

View File

@ -3091,7 +3091,7 @@ SerializerForBackgroundCompilation::ProcessMapForNamedPropertyAccess(
access_info.field_representation(),
access_info.field_index(), nullptr, policy)
: holder->GetOwnDictionaryProperty(
access_info.dictionary_index(), policy);
access_info.dictionary_index(), nullptr, policy);
if (constant.has_value()) {
result_hints->AddConstant(constant->object(), zone(), broker());
}

View File

@ -5,6 +5,7 @@
#ifndef V8_OBJECTS_DICTIONARY_INL_H_
#define V8_OBJECTS_DICTIONARY_INL_H_
#include "src/base/optional.h"
#include "src/execution/isolate-utils-inl.h"
#include "src/numbers/hash-seed-inl.h"
#include "src/objects/dictionary.h"
@ -41,6 +42,24 @@ Object Dictionary<Derived, Shape>::ValueAt(PtrComprCageBase cage_base,
Derived::kEntryValueIndex);
}
template <typename Derived, typename Shape>
base::Optional<Object> Dictionary<Derived, Shape>::TryValueAt(
InternalIndex entry) {
#if DEBUG
Isolate* isolate;
GetIsolateFromHeapObject(*this, &isolate);
DCHECK_NE(isolate, nullptr);
SLOW_DCHECK(!isolate->heap()->IsPendingAllocation(*this));
#endif // DEBUG
// We can read length() in a non-atomic way since we are reading an
// initialized object which is not pending allocation.
if (DerivedHashTable::EntryToIndex(entry) + Derived::kEntryValueIndex >=
this->length()) {
return {};
}
return ValueAt(entry);
}
template <typename Derived, typename Shape>
void Dictionary<Derived, Shape>::ValueAtPut(InternalIndex entry, Object value) {
this->set(DerivedHashTable::EntryToIndex(entry) + Derived::kEntryValueIndex,

View File

@ -6,6 +6,7 @@
#define V8_OBJECTS_DICTIONARY_H_
#include "src/base/export-template.h"
#include "src/base/optional.h"
#include "src/common/globals.h"
#include "src/objects/hash-table.h"
#include "src/objects/property-array.h"
@ -37,9 +38,10 @@ class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) Dictionary
public:
using Key = typename Shape::Key;
// Returns the value at entry.
inline Object ValueAt(InternalIndex entry);
inline Object ValueAt(PtrComprCageBase cage_base, InternalIndex entry);
// Returns {} if we would be reading out of the bounds of the object.
inline base::Optional<Object> TryValueAt(InternalIndex entry);
// Set the value for entry.
inline void ValueAtPut(InternalIndex entry, Object value);

View File

@ -5,7 +5,7 @@
#include "src/objects/js-objects.h"
#include "src/api/api-arguments-inl.h"
#include "src/base/logging.h"
#include "src/base/optional.h"
#include "src/common/globals.h"
#include "src/date/date.h"
#include "src/execution/arguments.h"
@ -27,6 +27,7 @@
#include "src/objects/field-type.h"
#include "src/objects/fixed-array.h"
#include "src/objects/heap-number.h"
#include "src/objects/heap-object.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/lookup.h"
@ -4174,10 +4175,10 @@ Handle<Object> JSObject::FastPropertyAt(Handle<JSObject> object,
}
// static
Handle<Object> JSObject::DictionaryPropertyAt(Handle<JSObject> object,
Handle<Object> JSObject::DictionaryPropertyAt(Isolate* isolate,
Handle<JSObject> object,
InternalIndex dict_index) {
Isolate* isolate = object->GetIsolate();
DCHECK_EQ(ThreadId::Current(), isolate->thread_id());
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
SwissNameDictionary dict = object->property_dictionary_swiss();
return handle(dict.ValueAt(dict_index), isolate);
@ -4187,6 +4188,27 @@ Handle<Object> JSObject::DictionaryPropertyAt(Handle<JSObject> object,
}
}
// static
base::Optional<Object> JSObject::DictionaryPropertyAt(Handle<JSObject> object,
InternalIndex dict_index,
Heap* heap) {
Object backing_store = object->raw_properties_or_hash(kRelaxedLoad);
if (!backing_store.IsHeapObject()) return {};
if (heap->IsPendingAllocation(HeapObject::cast(backing_store))) return {};
base::Optional<Object> maybe_obj;
if (V8_ENABLE_SWISS_NAME_DICTIONARY_BOOL) {
if (!backing_store.IsSwissNameDictionary()) return {};
maybe_obj = SwissNameDictionary::cast(backing_store).TryValueAt(dict_index);
} else {
if (!backing_store.IsNameDictionary()) return {};
maybe_obj = NameDictionary::cast(backing_store).TryValueAt(dict_index);
}
if (!maybe_obj) return {};
return maybe_obj.value();
}
// TODO(cbruni/jkummerow): Consider moving this into elements.cc.
bool JSObject::HasEnumerableElements() {
// TODO(cbruni): cleanup

View File

@ -642,8 +642,15 @@ class JSObject : public TorqueGeneratedJSObject<JSObject, JSReceiver> {
const char* reason);
// Access property in dictionary mode object at the given dictionary index.
static Handle<Object> DictionaryPropertyAt(Handle<JSObject> object,
static Handle<Object> DictionaryPropertyAt(Isolate* isolate,
Handle<JSObject> object,
InternalIndex dict_index);
// Same as above, but it will return {} if we would be reading out of the
// bounds of the object or if the dictionary is pending allocation. Use this
// version for concurrent access.
static base::Optional<Object> DictionaryPropertyAt(Handle<JSObject> object,
InternalIndex dict_index,
Heap* heap);
// Access fast-case object properties at index.
static Handle<Object> FastPropertyAt(Handle<JSObject> object,

View File

@ -8,6 +8,7 @@
#include <algorithm>
#include "src/base/macros.h"
#include "src/base/optional.h"
#include "src/execution/isolate-utils-inl.h"
#include "src/heap/heap.h"
#include "src/objects/fixed-array-inl.h"
@ -304,6 +305,22 @@ Object SwissNameDictionary::ValueAt(InternalIndex entry) {
return ValueAtRaw(entry.as_int());
}
base::Optional<Object> SwissNameDictionary::TryValueAt(InternalIndex entry) {
#if DEBUG
Isolate* isolate;
GetIsolateFromHeapObject(*this, &isolate);
DCHECK_NE(isolate, nullptr);
SLOW_DCHECK(!isolate->heap()->IsPendingAllocation(*this));
#endif // DEBUG
// We can read Capacity() in a non-atomic way since we are reading an
// initialized object which is not pending allocation.
if (static_cast<unsigned>(entry.as_int()) >=
static_cast<unsigned>(Capacity())) {
return {};
}
return ValueAtRaw(entry.as_int());
}
PropertyDetails SwissNameDictionary::DetailsAt(int entry) {
// GetCtrl(entry) does a bounds check for |entry| value.
DCHECK(IsFull(GetCtrl(entry)));

View File

@ -8,6 +8,7 @@
#include <cstdint>
#include "src/base/export-template.h"
#include "src/base/optional.h"
#include "src/common/globals.h"
#include "src/objects/fixed-array.h"
#include "src/objects/internal-index.h"
@ -101,6 +102,8 @@ class V8_EXPORT_PRIVATE SwissNameDictionary : public HeapObject {
inline Object KeyAt(InternalIndex entry);
inline Name NameAt(InternalIndex entry);
inline Object ValueAt(InternalIndex entry);
// Returns {} if we would be reading out of the bounds of the object.
inline base::Optional<Object> TryValueAt(InternalIndex entry);
inline PropertyDetails DetailsAt(InternalIndex entry);
inline void ValueAtPut(InternalIndex entry, Object value);