[compiler] Support Object.create(null) inlining in TF
In the ideal case, this will speed up Object.create(null) by ~10x.
Drive-by-fix: Spread usage of new IsSpecialReceiverMap() and
IsSpecialReceiverInstanceType(InstanceType) helpers.
BUG=v8:5788
Review-Url: https://codereview.chromium.org/2622723003
Cr-Commit-Position: refs/heads/master@{#42321}
diff --git a/src/compiler/access-builder.cc b/src/compiler/access-builder.cc
index 6ad041d..752a6cf 100644
--- a/src/compiler/access-builder.cc
+++ b/src/compiler/access-builder.cc
@@ -843,6 +843,68 @@
return access;
}
+// static
+FieldAccess AccessBuilder::ForHashTableBaseNumberOfElements() {
+ FieldAccess access = {
+ kTaggedBase,
+ FixedArray::OffsetOfElementAt(HashTableBase::kNumberOfElementsIndex),
+ MaybeHandle<Name>(),
+ MaybeHandle<Map>(),
+ Type::SignedSmall(),
+ MachineType::TaggedSigned(),
+ kNoWriteBarrier};
+ return access;
+}
+
+// static
+FieldAccess AccessBuilder::ForHashTableBaseNumberOfDeletedElement() {
+ FieldAccess access = {
+ kTaggedBase, FixedArray::OffsetOfElementAt(
+ HashTableBase::kNumberOfDeletedElementsIndex),
+ MaybeHandle<Name>(), MaybeHandle<Map>(), Type::SignedSmall(),
+ MachineType::TaggedSigned(), kNoWriteBarrier};
+ return access;
+}
+
+// static
+FieldAccess AccessBuilder::ForHashTableBaseCapacity() {
+ FieldAccess access = {
+ kTaggedBase,
+ FixedArray::OffsetOfElementAt(HashTableBase::kCapacityIndex),
+ MaybeHandle<Name>(),
+ MaybeHandle<Map>(),
+ Type::SignedSmall(),
+ MachineType::TaggedSigned(),
+ kNoWriteBarrier};
+ return access;
+}
+
+// static
+FieldAccess AccessBuilder::ForDictionaryMaxNumberKey() {
+ FieldAccess access = {
+ kTaggedBase,
+ FixedArray::OffsetOfElementAt(NameDictionary::kMaxNumberKeyIndex),
+ MaybeHandle<Name>(),
+ MaybeHandle<Map>(),
+ Type::Any(),
+ MachineType::AnyTagged(),
+ kNoWriteBarrier};
+ return access;
+}
+
+// static
+FieldAccess AccessBuilder::ForNextEnumerationIndex() {
+ FieldAccess access = {
+ kTaggedBase,
+ FixedArray::OffsetOfElementAt(NameDictionary::kNextEnumerationIndexIndex),
+ MaybeHandle<Name>(),
+ MaybeHandle<Map>(),
+ Type::SignedSmall(),
+ MachineType::TaggedSigned(),
+ kNoWriteBarrier};
+ return access;
+}
+
} // namespace compiler
} // namespace internal
} // namespace v8
diff --git a/src/compiler/access-builder.h b/src/compiler/access-builder.h
index 1929040..8e2d6d7 100644
--- a/src/compiler/access-builder.h
+++ b/src/compiler/access-builder.h
@@ -241,6 +241,15 @@
static ElementAccess ForTypedArrayElement(ExternalArrayType type,
bool is_external);
+ // Provides access to HashTable fields.
+ static FieldAccess ForHashTableBaseNumberOfElements();
+ static FieldAccess ForHashTableBaseNumberOfDeletedElement();
+ static FieldAccess ForHashTableBaseCapacity();
+
+ // Provides access to Dictionary fields.
+ static FieldAccess ForDictionaryMaxNumberKey();
+ static FieldAccess ForNextEnumerationIndex();
+
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(AccessBuilder);
};
diff --git a/src/compiler/js-builtin-reducer.cc b/src/compiler/js-builtin-reducer.cc
index 6698408..5e0e01b 100644
--- a/src/compiler/js-builtin-reducer.cc
+++ b/src/compiler/js-builtin-reducer.cc
@@ -4,6 +4,7 @@
#include "src/compiler/js-builtin-reducer.h"
+#include "src/base/bits.h"
#include "src/compilation-dependencies.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/js-graph.h"
@@ -1485,6 +1486,99 @@
return NoChange();
}
+// ES6 section #sec-object.create Object.create(proto, properties)
+Reduction JSBuiltinReducer::ReduceObjectCreate(Node* node) {
+ // We need exactly target, receiver and value parameters.
+ int arg_count = node->op()->ValueInputCount();
+ if (arg_count != 3) return NoChange();
+ Node* effect = NodeProperties::GetEffectInput(node);
+ Node* control = NodeProperties::GetControlInput(node);
+ Node* prototype = NodeProperties::GetValueInput(node, 2);
+ Type* prototype_type = NodeProperties::GetType(prototype);
+ Handle<Map> instance_map;
+ if (!prototype_type->IsHeapConstant()) return NoChange();
+ Handle<HeapObject> prototype_const =
+ prototype_type->AsHeapConstant()->Value();
+ if (!prototype_const->IsNull(isolate()) && !prototype_const->IsJSReceiver()) {
+ return NoChange();
+ }
+ instance_map = Map::GetObjectCreateMap(prototype_const);
+ Node* properties = jsgraph()->EmptyFixedArrayConstant();
+ if (instance_map->is_dictionary_map()) {
+ // Allocated an empty NameDictionary as backing store for the properties.
+ Handle<Map> map(isolate()->heap()->hash_table_map(), isolate());
+ int capacity =
+ NameDictionary::ComputeCapacity(NameDictionary::kInitialCapacity);
+ DCHECK(base::bits::IsPowerOfTwo32(capacity));
+ int length = NameDictionary::EntryToIndex(capacity);
+ int size = NameDictionary::SizeFor(length);
+
+ effect = graph()->NewNode(
+ common()->BeginRegion(RegionObservability::kNotObservable), effect);
+
+ Node* value = effect =
+ graph()->NewNode(simplified()->Allocate(NOT_TENURED),
+ jsgraph()->Constant(size), effect, control);
+ effect =
+ graph()->NewNode(simplified()->StoreField(AccessBuilder::ForMap()),
+ value, jsgraph()->HeapConstant(map), effect, control);
+
+ // Initialize FixedArray fields.
+ effect = graph()->NewNode(
+ simplified()->StoreField(AccessBuilder::ForFixedArrayLength()), value,
+ jsgraph()->SmiConstant(length), effect, control);
+ // Initialize HashTable fields.
+ effect =
+ graph()->NewNode(simplified()->StoreField(
+ AccessBuilder::ForHashTableBaseNumberOfElements()),
+ value, jsgraph()->SmiConstant(0), effect, control);
+ effect = graph()->NewNode(
+ simplified()->StoreField(
+ AccessBuilder::ForHashTableBaseNumberOfDeletedElement()),
+ value, jsgraph()->SmiConstant(0), effect, control);
+ effect = graph()->NewNode(
+ simplified()->StoreField(AccessBuilder::ForHashTableBaseCapacity()),
+ value, jsgraph()->SmiConstant(capacity), effect, control);
+ // Initialize Dictionary fields.
+ effect = graph()->NewNode(
+ simplified()->StoreField(AccessBuilder::ForDictionaryMaxNumberKey()),
+ value, jsgraph()->UndefinedConstant(), effect, control);
+ effect = graph()->NewNode(
+ simplified()->StoreField(AccessBuilder::ForNextEnumerationIndex()),
+ value, jsgraph()->SmiConstant(PropertyDetails::kInitialIndex), effect,
+ control);
+
+ properties = effect =
+ graph()->NewNode(common()->FinishRegion(), value, effect);
+ }
+
+ int const instance_size = instance_map->instance_size();
+ dependencies()->AssumeInitialMapCantChange(instance_map);
+
+ // Emit code to allocate the JSObject instance for the given
+ // {instance_map}.
+ effect = graph()->NewNode(
+ common()->BeginRegion(RegionObservability::kNotObservable), effect);
+ Node* value = effect =
+ graph()->NewNode(simplified()->Allocate(NOT_TENURED),
+ jsgraph()->Constant(instance_size), effect, control);
+ effect =
+ graph()->NewNode(simplified()->StoreField(AccessBuilder::ForMap()), value,
+ jsgraph()->HeapConstant(instance_map), effect, control);
+ effect = graph()->NewNode(
+ simplified()->StoreField(AccessBuilder::ForJSObjectProperties()), value,
+ properties, effect, control);
+ effect = graph()->NewNode(
+ simplified()->StoreField(AccessBuilder::ForJSObjectElements()), value,
+ jsgraph()->EmptyFixedArrayConstant(), effect, control);
+
+ value = effect = graph()->NewNode(common()->FinishRegion(), value, effect);
+
+ // replace it
+ ReplaceWithValue(node, value, effect, control);
+ return Replace(value);
+}
+
// ES6 section 21.1.2.1 String.fromCharCode ( ...codeUnits )
Reduction JSBuiltinReducer::ReduceStringFromCharCode(Node* node) {
JSCallReduction r(node);
@@ -1990,6 +2084,9 @@
case kNumberParseInt:
reduction = ReduceNumberParseInt(node);
break;
+ case kObjectCreate:
+ reduction = ReduceObjectCreate(node);
+ break;
case kStringFromCharCode:
reduction = ReduceStringFromCharCode(node);
break;
diff --git a/src/compiler/js-builtin-reducer.h b/src/compiler/js-builtin-reducer.h
index 050fbb7..295da8d 100644
--- a/src/compiler/js-builtin-reducer.h
+++ b/src/compiler/js-builtin-reducer.h
@@ -99,6 +99,7 @@
Reduction ReduceNumberIsNaN(Node* node);
Reduction ReduceNumberIsSafeInteger(Node* node);
Reduction ReduceNumberParseInt(Node* node);
+ Reduction ReduceObjectCreate(Node* node);
Reduction ReduceStringCharAt(Node* node);
Reduction ReduceStringCharCodeAt(Node* node);
Reduction ReduceStringFromCharCode(Node* node);
diff --git a/src/lookup.cc b/src/lookup.cc
index ed38909..ab8d47e 100644
--- a/src/lookup.cc
+++ b/src/lookup.cc
@@ -73,7 +73,7 @@
JSReceiver* holder = *holder_;
Map* map = holder->map();
- if (map->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE) {
+ if (map->IsSpecialReceiverMap()) {
state_ = IsElement() ? LookupInSpecialHolder<true>(map, holder)
: LookupInSpecialHolder<false>(map, holder);
if (IsFound()) return;
diff --git a/src/lookup.h b/src/lookup.h
index e0b40c4..5f7a293 100644
--- a/src/lookup.h
+++ b/src/lookup.h
@@ -288,7 +288,7 @@
void NextInternal(Map* map, JSReceiver* holder);
template <bool is_element>
inline State LookupInHolder(Map* map, JSReceiver* holder) {
- return map->instance_type() <= LAST_SPECIAL_RECEIVER_TYPE
+ return map->IsSpecialReceiverMap()
? LookupInSpecialHolder<is_element>(map, holder)
: LookupInRegularHolder<is_element>(map, holder);
}
diff --git a/src/objects-inl.h b/src/objects-inl.h
index e1778f3..3a2b782 100644
--- a/src/objects-inl.h
+++ b/src/objects-inl.h
@@ -2219,6 +2219,9 @@
}
}
+inline bool IsSpecialReceiverInstanceType(InstanceType instance_type) {
+ return instance_type <= LAST_SPECIAL_RECEIVER_TYPE;
+}
int JSObject::GetInternalFieldCount(Map* map) {
int instance_size = map->instance_size();
@@ -4946,6 +4949,12 @@
bool Map::IsJSTypedArrayMap() { return instance_type() == JS_TYPED_ARRAY_TYPE; }
bool Map::IsJSDataViewMap() { return instance_type() == JS_DATA_VIEW_TYPE; }
+bool Map::IsSpecialReceiverMap() {
+ bool result = IsSpecialReceiverInstanceType(instance_type());
+ DCHECK_IMPLIES(!result,
+ !has_named_interceptor() && !is_access_check_needed());
+ return result;
+}
bool Map::CanOmitMapChecks() {
return is_stable() && FLAG_omit_map_checks_for_leaf_maps;
diff --git a/src/objects.cc b/src/objects.cc
index fa2dcdb..589acb7 100644
--- a/src/objects.cc
+++ b/src/objects.cc
@@ -4724,6 +4724,36 @@
map->UpdateDescriptors(*new_descriptors, layout_descriptor);
}
+// static
+Handle<Map> Map::GetObjectCreateMap(Handle<HeapObject> prototype) {
+ Isolate* isolate = prototype->GetIsolate();
+ Handle<Map> map(isolate->native_context()->object_function()->initial_map(),
+ isolate);
+ if (map->prototype() == *prototype) return map;
+ if (prototype->IsNull(isolate)) {
+ return isolate->slow_object_with_null_prototype_map();
+ }
+ if (prototype->IsJSObject()) {
+ Handle<JSObject> js_prototype = Handle<JSObject>::cast(prototype);
+ if (!js_prototype->map()->is_prototype_map()) {
+ JSObject::OptimizeAsPrototype(js_prototype, FAST_PROTOTYPE);
+ }
+ Handle<PrototypeInfo> info =
+ Map::GetOrCreatePrototypeInfo(js_prototype, isolate);
+ // TODO(verwaest): Use inobject slack tracking for this map.
+ if (info->HasObjectCreateMap()) {
+ map = handle(info->ObjectCreateMap(), isolate);
+ } else {
+ map = Map::CopyInitialMap(map);
+ Map::SetPrototype(map, prototype, FAST_PROTOTYPE);
+ PrototypeInfo::SetObjectCreateMap(info, map);
+ }
+ return map;
+ }
+
+ return Map::TransitionToPrototype(map, prototype, REGULAR_PROTOTYPE);
+}
+
template <class T>
static int AppendUniqueCallbacks(Handle<TemplateList> callbacks,
Handle<typename T::Array> array,
@@ -8226,8 +8256,8 @@
// Wrapped string elements aren't explicitly stored in the elements backing
// store, but are loaded indirectly from the underlying string.
return !IsStringWrapperElementsKind(elements_kind()) &&
- instance_type() > LAST_SPECIAL_RECEIVER_TYPE &&
- !has_hidden_prototype() && !is_dictionary_map();
+ !IsSpecialReceiverMap() && !has_hidden_prototype() &&
+ !is_dictionary_map();
}
MUST_USE_RESULT Maybe<bool> FastGetOwnValuesOrEntries(
diff --git a/src/objects.h b/src/objects.h
index e7e79fb..64d717f 100644
--- a/src/objects.h
+++ b/src/objects.h
@@ -6287,6 +6287,8 @@
Code* LookupInCodeCache(Name* name, Code::Flags code);
+ static Handle<Map> GetObjectCreateMap(Handle<HeapObject> prototype);
+
// Computes a hash value for this map, to be used in HashTables and such.
int Hash();
@@ -6311,6 +6313,8 @@
inline bool IsJSTypedArrayMap();
inline bool IsJSDataViewMap();
+ inline bool IsSpecialReceiverMap();
+
inline bool CanOmitMapChecks();
static void AddDependentCode(Handle<Map> map,
@@ -7094,7 +7098,8 @@
V(Number, isSafeInteger, NumberIsSafeInteger) \
V(Number, parseFloat, NumberParseFloat) \
V(Number, parseInt, NumberParseInt) \
- V(Number.prototype, toString, NumberToString)
+ V(Number.prototype, toString, NumberToString) \
+ V(Object, create, ObjectCreate)
#define ATOMIC_FUNCTIONS_WITH_ID_LIST(V) \
V(Atomics, load, AtomicsLoad) \
diff --git a/src/runtime/runtime-array.cc b/src/runtime/runtime-array.cc
index ad374d2..a9cbc20 100644
--- a/src/runtime/runtime-array.cc
+++ b/src/runtime/runtime-array.cc
@@ -496,8 +496,7 @@
// If the receiver is not a special receiver type, and the length is a valid
// element index, perform fast operation tailored to specific ElementsKinds.
- if (object->map()->instance_type() > LAST_SPECIAL_RECEIVER_TYPE &&
- len < kMaxUInt32 &&
+ if (!object->map()->IsSpecialReceiverMap() && len < kMaxUInt32 &&
JSObject::PrototypeHasNoElements(isolate, JSObject::cast(*object))) {
Handle<JSObject> obj = Handle<JSObject>::cast(object);
ElementsAccessor* elements = obj->GetElementsAccessor();
@@ -595,8 +594,7 @@
// If the receiver is not a special receiver type, and the length is a valid
// element index, perform fast operation tailored to specific ElementsKinds.
- if (object->map()->instance_type() > LAST_SPECIAL_RECEIVER_TYPE &&
- len < kMaxUInt32 &&
+ if (!object->map()->IsSpecialReceiverMap() && len < kMaxUInt32 &&
JSObject::PrototypeHasNoElements(isolate, JSObject::cast(*object))) {
Handle<JSObject> obj = Handle<JSObject>::cast(object);
ElementsAccessor* elements = obj->GetElementsAccessor();
diff --git a/src/runtime/runtime-object.cc b/src/runtime/runtime-object.cc
index d1e57c0..e3518d3 100644
--- a/src/runtime/runtime-object.cc
+++ b/src/runtime/runtime-object.cc
@@ -222,30 +222,8 @@
// function's initial map from the current native context.
// TODO(bmeurer): Use a dedicated cache for Object.create; think about
// slack tracking for Object.create.
- Handle<Map> map(isolate->native_context()->object_function()->initial_map(),
- isolate);
- if (map->prototype() != *prototype) {
- if (prototype->IsNull(isolate)) {
- map = isolate->slow_object_with_null_prototype_map();
- } else if (prototype->IsJSObject()) {
- Handle<JSObject> js_prototype = Handle<JSObject>::cast(prototype);
- if (!js_prototype->map()->is_prototype_map()) {
- JSObject::OptimizeAsPrototype(js_prototype, FAST_PROTOTYPE);
- }
- Handle<PrototypeInfo> info =
- Map::GetOrCreatePrototypeInfo(js_prototype, isolate);
- // TODO(verwaest): Use inobject slack tracking for this map.
- if (info->HasObjectCreateMap()) {
- map = handle(info->ObjectCreateMap(), isolate);
- } else {
- map = Map::CopyInitialMap(map);
- Map::SetPrototype(map, prototype, FAST_PROTOTYPE);
- PrototypeInfo::SetObjectCreateMap(info, map);
- }
- } else {
- map = Map::TransitionToPrototype(map, prototype, REGULAR_PROTOTYPE);
- }
- }
+ Handle<Map> map =
+ Map::GetObjectCreateMap(Handle<HeapObject>::cast(prototype));
bool is_dictionary_map = map->is_dictionary_map();
Handle<FixedArray> object_properties;
diff --git a/src/value-serializer.cc b/src/value-serializer.cc
index ad9de20..132b5ad 100644
--- a/src/value-serializer.cc
+++ b/src/value-serializer.cc
@@ -401,7 +401,7 @@
// Eliminate callable and exotic objects, which should not be serialized.
InstanceType instance_type = receiver->map()->instance_type();
- if (receiver->IsCallable() || (instance_type <= LAST_SPECIAL_RECEIVER_TYPE &&
+ if (receiver->IsCallable() || (IsSpecialReceiverInstanceType(instance_type) &&
instance_type != JS_SPECIAL_API_OBJECT_TYPE)) {
ThrowDataCloneError(MessageTemplate::kDataCloneError, receiver);
return Nothing<bool>();