Commit 4a5418b5 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[builtins] Optimize {Map,Set}.prototype.size.

Add support for fast

  - get Map.prototype.size
  - get Set.prototype.size

by porting both the baseline implementation to the CodeStubAssembler and
inlining a fast-path into TurboFan (when the compiler can infer the fact
that the receiver is a proper JSCollection from the surrounding graph,
i.e. from feedback gathered by a dominating LOAD_IC).

R=yangguo@chromium.org

Bug: v8:5269, v8:5717
Change-Id: Ie003fd2551462591273bcb8487b80808dcc6cd82
Reviewed-on: https://chromium-review.googlesource.com/566438
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#46555}
parent 88c61f7b
......@@ -2920,7 +2920,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(prototype, "keys", Builtins::kMapPrototypeKeys, 0,
true);
SimpleInstallGetter(prototype, factory->InternalizeUtf8String("size"),
Builtins::kMapGetSize, false);
Builtins::kMapPrototypeGetSize, true);
SimpleInstallFunction(prototype, "values", Builtins::kMapPrototypeValues, 0,
true);
InstallSpeciesGetter(js_map_fun);
......@@ -2957,7 +2957,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(prototype, "forEach", Builtins::kSetPrototypeForEach,
1, false);
SimpleInstallGetter(prototype, factory->InternalizeUtf8String("size"),
Builtins::kSetGetSize, false);
Builtins::kSetPrototypeGetSize, true);
Handle<JSFunction> values = SimpleInstallFunction(
prototype, "values", Builtins::kSetPrototypeValues, 0, true);
JSObject::AddProperty(prototype, factory->keys_string(), values, DONT_ENUM);
......
......@@ -828,6 +828,15 @@ TF_BUILTIN(MapPrototypeEntries, CollectionsBuiltinsAssembler) {
context, Context::MAP_KEY_VALUE_ITERATOR_MAP_INDEX, receiver));
}
TF_BUILTIN(MapPrototypeGetSize, CollectionsBuiltinsAssembler) {
Node* const receiver = Parameter(Descriptor::kReceiver);
Node* const context = Parameter(Descriptor::kContext);
ThrowIfNotInstanceType(context, receiver, JS_MAP_TYPE,
"get Map.prototype.size");
Node* const table = LoadObjectField(receiver, JSMap::kTableOffset);
Return(LoadObjectField(table, OrderedHashMap::kNumberOfElementsOffset));
}
TF_BUILTIN(MapPrototypeForEach, CollectionsBuiltinsAssembler) {
const char* const kMethodName = "Map.prototype.forEach";
Node* const argc = Parameter(BuiltinDescriptor::kArgumentsCount);
......@@ -1002,6 +1011,15 @@ TF_BUILTIN(SetPrototypeEntries, CollectionsBuiltinsAssembler) {
context, Context::SET_KEY_VALUE_ITERATOR_MAP_INDEX, receiver));
}
TF_BUILTIN(SetPrototypeGetSize, CollectionsBuiltinsAssembler) {
Node* const receiver = Parameter(Descriptor::kReceiver);
Node* const context = Parameter(Descriptor::kContext);
ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE,
"get Set.prototype.size");
Node* const table = LoadObjectField(receiver, JSSet::kTableOffset);
Return(LoadObjectField(table, OrderedHashSet::kNumberOfElementsOffset));
}
TF_BUILTIN(SetPrototypeForEach, CollectionsBuiltinsAssembler) {
const char* const kMethodName = "Set.prototype.forEach";
Node* const argc = Parameter(BuiltinDescriptor::kArgumentsCount);
......
......@@ -9,17 +9,6 @@
namespace v8 {
namespace internal {
BUILTIN(MapGetSize) {
HandleScope scope(isolate);
const char* const kMethodName = "get Map.prototype.size";
CHECK_RECEIVER(JSMap, map, kMethodName);
Handle<OrderedHashMap> table(OrderedHashMap::cast(map->table()));
Handle<Object> size =
isolate->factory()->NewNumberFromInt(table->NumberOfElements());
return *size;
}
BUILTIN(MapClear) {
HandleScope scope(isolate);
const char* const kMethodName = "Map.prototype.clear";
......@@ -28,17 +17,6 @@ BUILTIN(MapClear) {
return isolate->heap()->undefined_value();
}
BUILTIN(SetGetSize) {
HandleScope scope(isolate);
const char* const kMethodName = "get Set.prototype.size";
CHECK_RECEIVER(JSSet, set, kMethodName);
Handle<OrderedHashSet> table(OrderedHashSet::cast(set->table()));
Handle<Object> size =
isolate->factory()->NewNumberFromInt(table->NumberOfElements());
return *size;
}
BUILTIN(SetClear) {
HandleScope scope(isolate);
const char* const kMethodName = "Set.prototype.clear";
......
......@@ -580,10 +580,11 @@ namespace internal {
TFJ(MapConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFJ(MapGet, 1, kKey) \
TFJ(MapHas, 1, kKey) \
CPP(MapGetSize) \
CPP(MapClear) \
/* ES #sec-map.prototype.entries */ \
TFJ(MapPrototypeEntries, 0) \
/* ES #sec-get-map.prototype.size */ \
TFJ(MapPrototypeGetSize, 0) \
/* ES #sec-map.prototype.forEach */ \
TFJ(MapPrototypeForEach, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES #sec-map.prototype.keys */ \
......@@ -872,10 +873,11 @@ namespace internal {
/* Set */ \
TFJ(SetConstructor, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFJ(SetHas, 1, kKey) \
CPP(SetGetSize) \
CPP(SetClear) \
/* ES #sec-set.prototype.entries */ \
TFJ(SetPrototypeEntries, 0) \
/* ES #sec-get-set.prototype.size */ \
TFJ(SetPrototypeGetSize, 0) \
/* ES #sec-set.prototype.foreach */ \
TFJ(SetPrototypeForEach, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES #sec-set.prototype.values */ \
......
......@@ -985,6 +985,30 @@ FieldAccess AccessBuilder::ForHashTableBaseCapacity() {
return access;
}
// static
FieldAccess AccessBuilder::ForOrderedHashMapNumberOfElements() {
// TODO(turbofan): This will be redundant with the HashTableBase
// methods above once the hash table unification is done.
FieldAccess const access = {
kTaggedBase, OrderedHashMap::kNumberOfElementsOffset,
MaybeHandle<Name>(), MaybeHandle<Map>(),
Type::UnsignedSmall(), MachineType::TaggedSigned(),
kNoWriteBarrier};
return access;
}
// static
FieldAccess AccessBuilder::ForOrderedHashSetNumberOfElements() {
// TODO(turbofan): This will be redundant with the HashTableBase
// methods above once the hash table unification is done.
FieldAccess const access = {
kTaggedBase, OrderedHashSet::kNumberOfElementsOffset,
MaybeHandle<Name>(), MaybeHandle<Map>(),
Type::UnsignedSmall(), MachineType::TaggedSigned(),
kNoWriteBarrier};
return access;
}
// static
FieldAccess AccessBuilder::ForDictionaryMaxNumberKey() {
FieldAccess access = {
......
......@@ -280,6 +280,12 @@ class V8_EXPORT_PRIVATE AccessBuilder final
static FieldAccess ForHashTableBaseNumberOfDeletedElement();
static FieldAccess ForHashTableBaseCapacity();
// Provides access to OrderedHashMap fields.
static FieldAccess ForOrderedHashMapNumberOfElements();
// Provides access to OrderedHashSet fields.
static FieldAccess ForOrderedHashSetNumberOfElements();
// Provides access to Dictionary fields.
static FieldAccess ForDictionaryMaxNumberKey();
static FieldAccess ForDictionaryNextEnumerationIndex();
......
......@@ -341,6 +341,50 @@ Reduction JSCallReducer::ReduceFunctionPrototypeHasInstance(Node* node) {
return Changed(node);
}
namespace {
bool HasInstanceTypeWitness(Node* receiver, Node* effect,
InstanceType instance_type) {
ZoneHandleSet<Map> receiver_maps;
NodeProperties::InferReceiverMapsResult result =
NodeProperties::InferReceiverMaps(receiver, effect, &receiver_maps);
switch (result) {
case NodeProperties::kNoReceiverMaps:
return false;
case NodeProperties::kReliableReceiverMaps:
case NodeProperties::kUnreliableReceiverMaps:
DCHECK_NE(0, receiver_maps.size());
for (size_t i = 0; i < receiver_maps.size(); ++i) {
if (receiver_maps[i]->instance_type() != instance_type) return false;
}
return true;
}
UNREACHABLE();
return false;
}
} // namespace
// ES #sec-get-map.prototype.size
Reduction JSCallReducer::ReduceMapPrototypeGetSize(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (HasInstanceTypeWitness(receiver, effect, JS_MAP_TYPE)) {
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionTable()),
receiver, effect, control);
Node* value = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForOrderedHashMapNumberOfElements()),
table, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
return NoChange();
}
Reduction JSCallReducer::ReduceObjectGetPrototype(Node* node, Node* object) {
Node* effect = NodeProperties::GetEffectInput(node);
......@@ -488,6 +532,26 @@ Reduction JSCallReducer::ReduceReflectGetPrototypeOf(Node* node) {
return ReduceObjectGetPrototype(node, target);
}
// ES #sec-get-set.prototype.size
Reduction JSCallReducer::ReduceSetPrototypeGetSize(Node* node) {
DCHECK_EQ(IrOpcode::kJSCall, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (HasInstanceTypeWitness(receiver, effect, JS_SET_TYPE)) {
Node* table = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSCollectionTable()),
receiver, effect, control);
Node* value = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForOrderedHashSetNumberOfElements()),
table, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
return NoChange();
}
bool CanInlineArrayIteratingBuiltin(Handle<Map> receiver_map) {
Isolate* const isolate = receiver_map->GetIsolate();
if (!receiver_map->prototype()->IsJSArray()) return false;
......@@ -995,6 +1059,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceFunctionPrototypeCall(node);
case Builtins::kFunctionPrototypeHasInstance:
return ReduceFunctionPrototypeHasInstance(node);
case Builtins::kMapPrototypeGetSize:
return ReduceMapPrototypeGetSize(node);
case Builtins::kNumberConstructor:
return ReduceNumberConstructor(node);
case Builtins::kObjectGetPrototypeOf:
......@@ -1009,6 +1075,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return ReduceReflectConstruct(node);
case Builtins::kReflectGetPrototypeOf:
return ReduceReflectGetPrototypeOf(node);
case Builtins::kSetPrototypeGetSize:
return ReduceSetPrototypeGetSize(node);
case Builtins::kArrayForEach:
return ReduceArrayForEach(function, node);
case Builtins::kReturnReceiver:
......
......@@ -59,6 +59,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceFunctionPrototypeApply(Node* node);
Reduction ReduceFunctionPrototypeCall(Node* node);
Reduction ReduceFunctionPrototypeHasInstance(Node* node);
Reduction ReduceMapPrototypeGetSize(Node* node);
Reduction ReduceObjectGetPrototype(Node* node, Node* object);
Reduction ReduceObjectGetPrototypeOf(Node* node);
Reduction ReduceObjectPrototypeGetProto(Node* node);
......@@ -66,6 +67,7 @@ class JSCallReducer final : public AdvancedReducer {
Reduction ReduceReflectApply(Node* node);
Reduction ReduceReflectConstruct(Node* node);
Reduction ReduceReflectGetPrototypeOf(Node* node);
Reduction ReduceSetPrototypeGetSize(Node* node);
Reduction ReduceArrayForEach(Handle<JSFunction> function, Node* node);
Reduction ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int arity, CallFrequency const& frequency);
......
......@@ -535,8 +535,8 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
// Map builtins.
case Builtins::kMapConstructor:
case Builtins::kMapGet:
case Builtins::kMapGetSize:
case Builtins::kMapPrototypeEntries:
case Builtins::kMapPrototypeGetSize:
case Builtins::kMapPrototypeKeys:
case Builtins::kMapPrototypeValues:
// Math builtins.
......@@ -590,8 +590,8 @@ bool BuiltinHasNoSideEffect(Builtins::Name id) {
case Builtins::kNumberPrototypeValueOf:
// Set builtins.
case Builtins::kSetConstructor:
case Builtins::kSetGetSize:
case Builtins::kSetPrototypeEntries:
case Builtins::kSetPrototypeGetSize:
case Builtins::kSetPrototypeValues:
// String builtins. Strings are immutable.
case Builtins::kStringFromCharCode:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment