Commit 4c81827c authored by snek's avatar snek Committed by V8 LUCI CQ

optimize Set#has

Code for map methods was added a really long time ago but no one ever
brought that to set. Adds new common lowering for both collections and
updates the SetPrototypeHas builtin. My initial testing shows this to
be as much as 50x faster in some cases.

Change-Id: Ifea5be01c9e51013d57ac00bd817759ceace6669
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3709246Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Reviewed-by: 's avatarMaya Lekova <mslekova@chromium.org>
Commit-Queue: snek <snek@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81330}
parent 543acf34
...@@ -2169,55 +2169,17 @@ TF_BUILTIN(SetPrototypeHas, CollectionsBuiltinsAssembler) { ...@@ -2169,55 +2169,17 @@ TF_BUILTIN(SetPrototypeHas, CollectionsBuiltinsAssembler) {
const TNode<Object> table = const TNode<Object> table =
LoadObjectField(CAST(receiver), JSMap::kTableOffset); LoadObjectField(CAST(receiver), JSMap::kTableOffset);
TNode<Smi> index =
CAST(CallBuiltin(Builtin::kFindOrderedHashSetEntry, context, table, key));
TVARIABLE(IntPtrT, entry_start_position, IntPtrConstant(0)); Label if_found(this), if_not_found(this);
Label if_key_smi(this), if_key_string(this), if_key_heap_number(this), Branch(SmiGreaterThanOrEqual(index, SmiConstant(0)), &if_found,
if_key_bigint(this), entry_found(this), not_found(this), done(this); &if_not_found);
GotoIf(TaggedIsSmi(key), &if_key_smi);
TNode<Map> key_map = LoadMap(CAST(key));
TNode<Uint16T> key_instance_type = LoadMapInstanceType(key_map);
GotoIf(IsStringInstanceType(key_instance_type), &if_key_string);
GotoIf(IsHeapNumberMap(key_map), &if_key_heap_number);
GotoIf(IsBigIntInstanceType(key_instance_type), &if_key_bigint);
FindOrderedHashTableEntryForOtherKey<OrderedHashSet>(
CAST(table), CAST(key), &entry_start_position, &entry_found, &not_found);
BIND(&if_key_smi);
{
FindOrderedHashTableEntryForSmiKey<OrderedHashSet>(
CAST(table), CAST(key), &entry_start_position, &entry_found,
&not_found);
}
BIND(&if_key_string);
{
FindOrderedHashTableEntryForStringKey<OrderedHashSet>(
CAST(table), CAST(key), &entry_start_position, &entry_found,
&not_found);
}
BIND(&if_key_heap_number);
{
FindOrderedHashTableEntryForHeapNumberKey<OrderedHashSet>(
CAST(table), CAST(key), &entry_start_position, &entry_found,
&not_found);
}
BIND(&if_key_bigint);
{
FindOrderedHashTableEntryForBigIntKey<OrderedHashSet>(
CAST(table), CAST(key), &entry_start_position, &entry_found,
&not_found);
}
BIND(&entry_found); BIND(&if_found);
Return(TrueConstant()); Return(TrueConstant());
BIND(&not_found); BIND(&if_not_found);
Return(FalseConstant()); Return(FalseConstant());
} }
...@@ -2435,6 +2397,23 @@ TF_BUILTIN(FindOrderedHashMapEntry, CollectionsBuiltinsAssembler) { ...@@ -2435,6 +2397,23 @@ TF_BUILTIN(FindOrderedHashMapEntry, CollectionsBuiltinsAssembler) {
Return(SmiConstant(-1)); Return(SmiConstant(-1));
} }
TF_BUILTIN(FindOrderedHashSetEntry, CollectionsBuiltinsAssembler) {
const auto table = Parameter<OrderedHashSet>(Descriptor::kTable);
const auto key = Parameter<Object>(Descriptor::kKey);
TVARIABLE(IntPtrT, entry_start_position, IntPtrConstant(0));
Label entry_found(this), not_found(this);
TryLookupOrderedHashTableIndex<OrderedHashSet>(
table, key, &entry_start_position, &entry_found, &not_found);
BIND(&entry_found);
Return(SmiTag(entry_start_position.value()));
BIND(&not_found);
Return(SmiConstant(-1));
}
class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler { class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
public: public:
explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state) explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
......
...@@ -855,6 +855,7 @@ namespace internal { ...@@ -855,6 +855,7 @@ namespace internal {
ASM(RegExpExperimentalTrampoline, CCall) \ ASM(RegExpExperimentalTrampoline, CCall) \
\ \
/* Set */ \ /* Set */ \
TFS(FindOrderedHashSetEntry, kTable, kKey) \
TFJ(SetConstructor, kDontAdaptArgumentsSentinel) \ TFJ(SetConstructor, kDontAdaptArgumentsSentinel) \
TFJ(SetPrototypeHas, kJSArgcReceiverSlots + 1, kReceiver, kKey) \ TFJ(SetPrototypeHas, kJSArgcReceiverSlots + 1, kReceiver, kKey) \
TFJ(SetPrototypeAdd, kJSArgcReceiverSlots + 1, kReceiver, kKey) \ TFJ(SetPrototypeAdd, kJSArgcReceiverSlots + 1, kReceiver, kKey) \
......
...@@ -224,6 +224,7 @@ class EffectControlLinearizer { ...@@ -224,6 +224,7 @@ class EffectControlLinearizer {
void LowerStoreSignedSmallElement(Node* node); void LowerStoreSignedSmallElement(Node* node);
Node* LowerFindOrderedHashMapEntry(Node* node); Node* LowerFindOrderedHashMapEntry(Node* node);
Node* LowerFindOrderedHashMapEntryForInt32Key(Node* node); Node* LowerFindOrderedHashMapEntryForInt32Key(Node* node);
Node* LowerFindOrderedHashSetEntry(Node* node);
void LowerTransitionAndStoreElement(Node* node); void LowerTransitionAndStoreElement(Node* node);
void LowerTransitionAndStoreNumberElement(Node* node); void LowerTransitionAndStoreNumberElement(Node* node);
void LowerTransitionAndStoreNonNumberElement(Node* node); void LowerTransitionAndStoreNonNumberElement(Node* node);
...@@ -1335,6 +1336,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node, ...@@ -1335,6 +1336,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kFindOrderedHashMapEntryForInt32Key: case IrOpcode::kFindOrderedHashMapEntryForInt32Key:
result = LowerFindOrderedHashMapEntryForInt32Key(node); result = LowerFindOrderedHashMapEntryForInt32Key(node);
break; break;
case IrOpcode::kFindOrderedHashSetEntry:
result = LowerFindOrderedHashSetEntry(node);
break;
case IrOpcode::kTransitionAndStoreNumberElement: case IrOpcode::kTransitionAndStoreNumberElement:
LowerTransitionAndStoreNumberElement(node); LowerTransitionAndStoreNumberElement(node);
break; break;
...@@ -6338,6 +6342,23 @@ Node* EffectControlLinearizer::LowerFindOrderedHashMapEntry(Node* node) { ...@@ -6338,6 +6342,23 @@ Node* EffectControlLinearizer::LowerFindOrderedHashMapEntry(Node* node) {
} }
} }
Node* EffectControlLinearizer::LowerFindOrderedHashSetEntry(Node* node) {
Node* table = NodeProperties::GetValueInput(node, 0);
Node* key = NodeProperties::GetValueInput(node, 1);
{
Callable const callable =
Builtins::CallableFor(isolate(), Builtin::kFindOrderedHashSetEntry);
Operator::Properties const properties = node->op()->properties();
CallDescriptor::Flags const flags = CallDescriptor::kNoFlags;
auto call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(),
callable.descriptor().GetStackParameterCount(), flags, properties);
return __ Call(call_descriptor, __ HeapConstant(callable.code()), table,
key, __ NoContextConstant());
}
}
Node* EffectControlLinearizer::ComputeUnseededHash(Node* value) { Node* EffectControlLinearizer::ComputeUnseededHash(Node* value) {
// See v8::internal::ComputeUnseededHash() // See v8::internal::ComputeUnseededHash()
value = __ Int32Add(__ Word32Xor(value, __ Int32Constant(0xFFFFFFFF)), value = __ Int32Add(__ Word32Xor(value, __ Int32Constant(0xFFFFFFFF)),
......
...@@ -4869,6 +4869,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node, ...@@ -4869,6 +4869,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
return ReduceMapPrototypeGet(node); return ReduceMapPrototypeGet(node);
case Builtin::kMapPrototypeHas: case Builtin::kMapPrototypeHas:
return ReduceMapPrototypeHas(node); return ReduceMapPrototypeHas(node);
case Builtin::kSetPrototypeHas:
return ReduceSetPrototypeHas(node);
case Builtin::kRegExpPrototypeTest: case Builtin::kRegExpPrototypeTest:
return ReduceRegExpPrototypeTest(node); return ReduceRegExpPrototypeTest(node);
case Builtin::kReturnReceiver: case Builtin::kReturnReceiver:
...@@ -7486,7 +7488,8 @@ Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) { ...@@ -7486,7 +7488,8 @@ Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) {
effect, control); effect, control);
Node* entry = effect = graph()->NewNode( Node* entry = effect = graph()->NewNode(
simplified()->FindOrderedHashMapEntry(), table, key, effect, control); simplified()->FindOrderedCollectionEntry(CollectionKind::kMap), table,
key, effect, control);
Node* check = graph()->NewNode(simplified()->NumberEqual(), entry, Node* check = graph()->NewNode(simplified()->NumberEqual(), entry,
jsgraph()->MinusOneConstant()); jsgraph()->MinusOneConstant());
...@@ -7514,7 +7517,22 @@ Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) { ...@@ -7514,7 +7517,22 @@ Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) {
return Replace(value); return Replace(value);
} }
Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) { namespace {
InstanceType InstanceTypeForCollectionKind(CollectionKind kind) {
switch (kind) {
case CollectionKind::kMap:
return JS_MAP_TYPE;
case CollectionKind::kSet:
return JS_SET_TYPE;
}
UNREACHABLE();
}
} // namespace
Reduction JSCallReducer::ReduceCollectionPrototypeHas(
Node* node, CollectionKind collection_kind) {
// We only optimize if we have target, receiver and key parameters. // We only optimize if we have target, receiver and key parameters.
JSCallNode n(node); JSCallNode n(node);
if (n.ArgumentCount() != 1) return NoChange(); if (n.ArgumentCount() != 1) return NoChange();
...@@ -7522,9 +7540,11 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) { ...@@ -7522,9 +7540,11 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
Effect effect{NodeProperties::GetEffectInput(node)}; Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)}; Control control{NodeProperties::GetControlInput(node)};
Node* key = NodeProperties::GetValueInput(node, 2); Node* key = NodeProperties::GetValueInput(node, 2);
InstanceType instance_type = InstanceTypeForCollectionKind(collection_kind);
MapInference inference(broker(), receiver, effect); MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_MAP_TYPE)) { if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(instance_type)) {
return NoChange(); return NoChange();
} }
...@@ -7533,7 +7553,8 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) { ...@@ -7533,7 +7553,8 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
effect, control); effect, control);
Node* index = effect = graph()->NewNode( Node* index = effect = graph()->NewNode(
simplified()->FindOrderedHashMapEntry(), table, key, effect, control); simplified()->FindOrderedCollectionEntry(collection_kind), table, key,
effect, control);
Node* value = graph()->NewNode(simplified()->NumberEqual(), index, Node* value = graph()->NewNode(simplified()->NumberEqual(), index,
jsgraph()->MinusOneConstant()); jsgraph()->MinusOneConstant());
...@@ -7543,19 +7564,13 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) { ...@@ -7543,19 +7564,13 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
return Replace(value); return Replace(value);
} }
namespace { Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
return ReduceCollectionPrototypeHas(node, CollectionKind::kMap);
InstanceType InstanceTypeForCollectionKind(CollectionKind kind) {
switch (kind) {
case CollectionKind::kMap:
return JS_MAP_TYPE;
case CollectionKind::kSet:
return JS_SET_TYPE;
}
UNREACHABLE();
} }
} // namespace Reduction JSCallReducer::ReduceSetPrototypeHas(Node* node) {
return ReduceCollectionPrototypeHas(node, CollectionKind::kSet);
}
Reduction JSCallReducer::ReduceCollectionIteration( Reduction JSCallReducer::ReduceCollectionIteration(
Node* node, CollectionKind collection_kind, IterationKind iteration_kind) { Node* node, CollectionKind collection_kind, IterationKind iteration_kind) {
......
...@@ -201,6 +201,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer { ...@@ -201,6 +201,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceMapPrototypeHas(Node* node); Reduction ReduceMapPrototypeHas(Node* node);
Reduction ReduceMapPrototypeGet(Node* node); Reduction ReduceMapPrototypeGet(Node* node);
Reduction ReduceSetPrototypeHas(Node* node);
Reduction ReduceCollectionPrototypeHas(Node* node,
CollectionKind collection_kind);
Reduction ReduceCollectionIteration(Node* node, Reduction ReduceCollectionIteration(Node* node,
CollectionKind collection_kind, CollectionKind collection_kind,
IterationKind iteration_kind); IterationKind iteration_kind);
......
...@@ -425,6 +425,7 @@ ...@@ -425,6 +425,7 @@
V(FastApiCall) \ V(FastApiCall) \
V(FindOrderedHashMapEntry) \ V(FindOrderedHashMapEntry) \
V(FindOrderedHashMapEntryForInt32Key) \ V(FindOrderedHashMapEntryForInt32Key) \
V(FindOrderedHashSetEntry) \
V(InitializeImmutableInObject) \ V(InitializeImmutableInObject) \
V(LoadDataViewElement) \ V(LoadDataViewElement) \
V(LoadElement) \ V(LoadElement) \
......
...@@ -4024,6 +4024,11 @@ class RepresentationSelector { ...@@ -4024,6 +4024,11 @@ class RepresentationSelector {
return; return;
} }
case IrOpcode::kFindOrderedHashSetEntry:
VisitBinop<T>(node, UseInfo::AnyTagged(),
MachineRepresentation::kTaggedSigned);
return;
case IrOpcode::kFastApiCall: { case IrOpcode::kFastApiCall: {
VisitFastApiCall<T>(node, lowering); VisitFastApiCall<T>(node, lowering);
return; return;
......
...@@ -951,6 +951,13 @@ struct SimplifiedOperatorGlobalCache final { ...@@ -951,6 +951,13 @@ struct SimplifiedOperatorGlobalCache final {
FindOrderedHashMapEntryForInt32KeyOperator FindOrderedHashMapEntryForInt32KeyOperator
kFindOrderedHashMapEntryForInt32Key; kFindOrderedHashMapEntryForInt32Key;
struct FindOrderedHashSetEntryOperator final : public Operator {
FindOrderedHashSetEntryOperator()
: Operator(IrOpcode::kFindOrderedHashSetEntry, Operator::kEliminatable,
"FindOrderedHashSetEntry", 2, 1, 1, 1, 1, 0) {}
};
FindOrderedHashSetEntryOperator kFindOrderedHashSetEntry;
template <CheckForMinusZeroMode kMode> template <CheckForMinusZeroMode kMode>
struct ChangeFloat64ToTaggedOperator final struct ChangeFloat64ToTaggedOperator final
: public Operator1<CheckForMinusZeroMode> { : public Operator1<CheckForMinusZeroMode> {
...@@ -1229,11 +1236,20 @@ SimplifiedOperatorBuilder::SimplifiedOperatorBuilder(Zone* zone) ...@@ -1229,11 +1236,20 @@ SimplifiedOperatorBuilder::SimplifiedOperatorBuilder(Zone* zone)
PURE_OP_LIST(GET_FROM_CACHE) PURE_OP_LIST(GET_FROM_CACHE)
EFFECT_DEPENDENT_OP_LIST(GET_FROM_CACHE) EFFECT_DEPENDENT_OP_LIST(GET_FROM_CACHE)
CHECKED_OP_LIST(GET_FROM_CACHE) CHECKED_OP_LIST(GET_FROM_CACHE)
GET_FROM_CACHE(FindOrderedHashMapEntry)
GET_FROM_CACHE(FindOrderedHashMapEntryForInt32Key) GET_FROM_CACHE(FindOrderedHashMapEntryForInt32Key)
GET_FROM_CACHE(LoadFieldByIndex) GET_FROM_CACHE(LoadFieldByIndex)
#undef GET_FROM_CACHE #undef GET_FROM_CACHE
const Operator* SimplifiedOperatorBuilder::FindOrderedCollectionEntry(
CollectionKind collection_kind) {
switch (collection_kind) {
case CollectionKind::kMap:
return &cache_.kFindOrderedHashMapEntry;
case CollectionKind::kSet:
return &cache_.kFindOrderedHashSetEntry;
}
}
#define GET_FROM_CACHE_WITH_FEEDBACK(Name, value_input_count, \ #define GET_FROM_CACHE_WITH_FEEDBACK(Name, value_input_count, \
value_output_count) \ value_output_count) \
const Operator* SimplifiedOperatorBuilder::Name( \ const Operator* SimplifiedOperatorBuilder::Name( \
......
...@@ -838,8 +838,8 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final ...@@ -838,8 +838,8 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* StringToUpperCaseIntl(); const Operator* StringToUpperCaseIntl();
const Operator* StringSubstring(); const Operator* StringSubstring();
const Operator* FindOrderedHashMapEntry();
const Operator* FindOrderedHashMapEntryForInt32Key(); const Operator* FindOrderedHashMapEntryForInt32Key();
const Operator* FindOrderedCollectionEntry(CollectionKind collection_kind);
const Operator* SpeculativeToNumber(NumberOperationHint hint, const Operator* SpeculativeToNumber(NumberOperationHint hint,
const FeedbackSource& feedback); const FeedbackSource& feedback);
......
...@@ -2374,6 +2374,10 @@ Type Typer::Visitor::TypeFindOrderedHashMapEntryForInt32Key(Node* node) { ...@@ -2374,6 +2374,10 @@ Type Typer::Visitor::TypeFindOrderedHashMapEntryForInt32Key(Node* node) {
return Type::Range(-1.0, FixedArray::kMaxLength, zone()); return Type::Range(-1.0, FixedArray::kMaxLength, zone());
} }
Type Typer::Visitor::TypeFindOrderedHashSetEntry(Node* node) {
return Type::Range(-1.0, FixedArray::kMaxLength, zone());
}
Type Typer::Visitor::TypeRuntimeAbort(Node* node) { UNREACHABLE(); } Type Typer::Visitor::TypeRuntimeAbort(Node* node) { UNREACHABLE(); }
Type Typer::Visitor::TypeAssertType(Node* node) { UNREACHABLE(); } Type Typer::Visitor::TypeAssertType(Node* node) { UNREACHABLE(); }
......
...@@ -1251,6 +1251,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) { ...@@ -1251,6 +1251,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 1, Type::Signed32()); CheckValueInputIs(node, 1, Type::Signed32());
CheckTypeIs(node, Type::SignedSmall()); CheckTypeIs(node, Type::SignedSmall());
break; break;
case IrOpcode::kFindOrderedHashSetEntry:
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::SignedSmall());
break;
case IrOpcode::kArgumentsLength: case IrOpcode::kArgumentsLength:
case IrOpcode::kRestLength: case IrOpcode::kRestLength:
CheckTypeIs(node, TypeCache::Get()->kArgumentsLengthType); CheckTypeIs(node, TypeCache::Get()->kArgumentsLengthType);
......
...@@ -1107,6 +1107,7 @@ static bool TransitivelyCalledBuiltinHasNoSideEffect(Builtin caller, ...@@ -1107,6 +1107,7 @@ static bool TransitivelyCalledBuiltinHasNoSideEffect(Builtin caller,
case Builtin::kExtractFastJSArray: case Builtin::kExtractFastJSArray:
case Builtin::kFastNewObject: case Builtin::kFastNewObject:
case Builtin::kFindOrderedHashMapEntry: case Builtin::kFindOrderedHashMapEntry:
case Builtin::kFindOrderedHashSetEntry:
case Builtin::kFlatMapIntoArray: case Builtin::kFlatMapIntoArray:
case Builtin::kFlattenIntoArray: case Builtin::kFlattenIntoArray:
case Builtin::kGetProperty: case Builtin::kGetProperty:
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
(function() {
const map = new Map();
map.set(true, true);
for (let i = 0; i < 10000; i += 1) {
map.set(i, i);
map.set(`${i} number`, i);
}
function foo(x) {
return map.has(x);
}
%PrepareFunctionForOptimization(foo);
assertFalse(foo(1.5));
assertTrue(foo(1));
assertFalse(foo('1.5 number'));
assertTrue(foo('1 number'));
assertFalse(foo(false));
assertTrue(foo(true));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(1.5));
assertTrue(foo(1));
assertFalse(foo('1.5 number'));
assertTrue(foo('1 number'));
assertFalse(foo(false));
assertTrue(foo(true));
})();
(function() {
const set = new Set();
set.add(true);
for (let i = 0; i < 10000; i += 1) {
set.add(i);
set.add(`${i} number`);
}
function foo(x) {
return set.has(x);
}
%PrepareFunctionForOptimization(foo);
assertFalse(foo(1.5));
assertTrue(foo(1));
assertFalse(foo('1.5 number'));
assertTrue(foo('1 number'));
assertFalse(foo(false));
assertTrue(foo(true));
%OptimizeFunctionOnNextCall(foo);
assertFalse(foo(1.5));
assertTrue(foo(1));
assertFalse(foo('1.5 number'));
assertTrue(foo('1 number'));
assertFalse(foo(false));
assertTrue(foo(true));
})();
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