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) {
const TNode<Object> table =
LoadObjectField(CAST(receiver), JSMap::kTableOffset);
TNode<Smi> index =
CAST(CallBuiltin(Builtin::kFindOrderedHashSetEntry, context, table, key));
TVARIABLE(IntPtrT, entry_start_position, IntPtrConstant(0));
Label if_key_smi(this), if_key_string(this), if_key_heap_number(this),
if_key_bigint(this), entry_found(this), not_found(this), done(this);
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);
}
Label if_found(this), if_not_found(this);
Branch(SmiGreaterThanOrEqual(index, SmiConstant(0)), &if_found,
&if_not_found);
BIND(&entry_found);
BIND(&if_found);
Return(TrueConstant());
BIND(&not_found);
BIND(&if_not_found);
Return(FalseConstant());
}
......@@ -2435,6 +2397,23 @@ TF_BUILTIN(FindOrderedHashMapEntry, CollectionsBuiltinsAssembler) {
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 {
public:
explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
......
......@@ -855,6 +855,7 @@ namespace internal {
ASM(RegExpExperimentalTrampoline, CCall) \
\
/* Set */ \
TFS(FindOrderedHashSetEntry, kTable, kKey) \
TFJ(SetConstructor, kDontAdaptArgumentsSentinel) \
TFJ(SetPrototypeHas, kJSArgcReceiverSlots + 1, kReceiver, kKey) \
TFJ(SetPrototypeAdd, kJSArgcReceiverSlots + 1, kReceiver, kKey) \
......
......@@ -224,6 +224,7 @@ class EffectControlLinearizer {
void LowerStoreSignedSmallElement(Node* node);
Node* LowerFindOrderedHashMapEntry(Node* node);
Node* LowerFindOrderedHashMapEntryForInt32Key(Node* node);
Node* LowerFindOrderedHashSetEntry(Node* node);
void LowerTransitionAndStoreElement(Node* node);
void LowerTransitionAndStoreNumberElement(Node* node);
void LowerTransitionAndStoreNonNumberElement(Node* node);
......@@ -1335,6 +1336,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kFindOrderedHashMapEntryForInt32Key:
result = LowerFindOrderedHashMapEntryForInt32Key(node);
break;
case IrOpcode::kFindOrderedHashSetEntry:
result = LowerFindOrderedHashSetEntry(node);
break;
case IrOpcode::kTransitionAndStoreNumberElement:
LowerTransitionAndStoreNumberElement(node);
break;
......@@ -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) {
// See v8::internal::ComputeUnseededHash()
value = __ Int32Add(__ Word32Xor(value, __ Int32Constant(0xFFFFFFFF)),
......
......@@ -4869,6 +4869,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node,
return ReduceMapPrototypeGet(node);
case Builtin::kMapPrototypeHas:
return ReduceMapPrototypeHas(node);
case Builtin::kSetPrototypeHas:
return ReduceSetPrototypeHas(node);
case Builtin::kRegExpPrototypeTest:
return ReduceRegExpPrototypeTest(node);
case Builtin::kReturnReceiver:
......@@ -7486,7 +7488,8 @@ Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) {
effect, control);
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,
jsgraph()->MinusOneConstant());
......@@ -7514,7 +7517,22 @@ Reduction JSCallReducer::ReduceMapPrototypeGet(Node* node) {
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.
JSCallNode n(node);
if (n.ArgumentCount() != 1) return NoChange();
......@@ -7522,9 +7540,11 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
Node* key = NodeProperties::GetValueInput(node, 2);
InstanceType instance_type = InstanceTypeForCollectionKind(collection_kind);
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAre(JS_MAP_TYPE)) {
if (!inference.HaveMaps() ||
!inference.AllOfInstanceTypesAre(instance_type)) {
return NoChange();
}
......@@ -7533,7 +7553,8 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
effect, control);
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,
jsgraph()->MinusOneConstant());
......@@ -7543,19 +7564,13 @@ Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
return Replace(value);
}
namespace {
InstanceType InstanceTypeForCollectionKind(CollectionKind kind) {
switch (kind) {
case CollectionKind::kMap:
return JS_MAP_TYPE;
case CollectionKind::kSet:
return JS_SET_TYPE;
}
UNREACHABLE();
Reduction JSCallReducer::ReduceMapPrototypeHas(Node* node) {
return ReduceCollectionPrototypeHas(node, CollectionKind::kMap);
}
} // namespace
Reduction JSCallReducer::ReduceSetPrototypeHas(Node* node) {
return ReduceCollectionPrototypeHas(node, CollectionKind::kSet);
}
Reduction JSCallReducer::ReduceCollectionIteration(
Node* node, CollectionKind collection_kind, IterationKind iteration_kind) {
......
......@@ -201,6 +201,9 @@ class V8_EXPORT_PRIVATE JSCallReducer final : public AdvancedReducer {
Reduction ReduceMapPrototypeHas(Node* node);
Reduction ReduceMapPrototypeGet(Node* node);
Reduction ReduceSetPrototypeHas(Node* node);
Reduction ReduceCollectionPrototypeHas(Node* node,
CollectionKind collection_kind);
Reduction ReduceCollectionIteration(Node* node,
CollectionKind collection_kind,
IterationKind iteration_kind);
......
......@@ -425,6 +425,7 @@
V(FastApiCall) \
V(FindOrderedHashMapEntry) \
V(FindOrderedHashMapEntryForInt32Key) \
V(FindOrderedHashSetEntry) \
V(InitializeImmutableInObject) \
V(LoadDataViewElement) \
V(LoadElement) \
......
......@@ -4024,6 +4024,11 @@ class RepresentationSelector {
return;
}
case IrOpcode::kFindOrderedHashSetEntry:
VisitBinop<T>(node, UseInfo::AnyTagged(),
MachineRepresentation::kTaggedSigned);
return;
case IrOpcode::kFastApiCall: {
VisitFastApiCall<T>(node, lowering);
return;
......
......@@ -951,6 +951,13 @@ struct SimplifiedOperatorGlobalCache final {
FindOrderedHashMapEntryForInt32KeyOperator
kFindOrderedHashMapEntryForInt32Key;
struct FindOrderedHashSetEntryOperator final : public Operator {
FindOrderedHashSetEntryOperator()
: Operator(IrOpcode::kFindOrderedHashSetEntry, Operator::kEliminatable,
"FindOrderedHashSetEntry", 2, 1, 1, 1, 1, 0) {}
};
FindOrderedHashSetEntryOperator kFindOrderedHashSetEntry;
template <CheckForMinusZeroMode kMode>
struct ChangeFloat64ToTaggedOperator final
: public Operator1<CheckForMinusZeroMode> {
......@@ -1229,11 +1236,20 @@ SimplifiedOperatorBuilder::SimplifiedOperatorBuilder(Zone* zone)
PURE_OP_LIST(GET_FROM_CACHE)
EFFECT_DEPENDENT_OP_LIST(GET_FROM_CACHE)
CHECKED_OP_LIST(GET_FROM_CACHE)
GET_FROM_CACHE(FindOrderedHashMapEntry)
GET_FROM_CACHE(FindOrderedHashMapEntryForInt32Key)
GET_FROM_CACHE(LoadFieldByIndex)
#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, \
value_output_count) \
const Operator* SimplifiedOperatorBuilder::Name( \
......
......@@ -838,8 +838,8 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* StringToUpperCaseIntl();
const Operator* StringSubstring();
const Operator* FindOrderedHashMapEntry();
const Operator* FindOrderedHashMapEntryForInt32Key();
const Operator* FindOrderedCollectionEntry(CollectionKind collection_kind);
const Operator* SpeculativeToNumber(NumberOperationHint hint,
const FeedbackSource& feedback);
......
......@@ -2374,6 +2374,10 @@ Type Typer::Visitor::TypeFindOrderedHashMapEntryForInt32Key(Node* node) {
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::TypeAssertType(Node* node) { UNREACHABLE(); }
......
......@@ -1251,6 +1251,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 1, Type::Signed32());
CheckTypeIs(node, Type::SignedSmall());
break;
case IrOpcode::kFindOrderedHashSetEntry:
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, Type::SignedSmall());
break;
case IrOpcode::kArgumentsLength:
case IrOpcode::kRestLength:
CheckTypeIs(node, TypeCache::Get()->kArgumentsLengthType);
......
......@@ -1107,6 +1107,7 @@ static bool TransitivelyCalledBuiltinHasNoSideEffect(Builtin caller,
case Builtin::kExtractFastJSArray:
case Builtin::kFastNewObject:
case Builtin::kFindOrderedHashMapEntry:
case Builtin::kFindOrderedHashSetEntry:
case Builtin::kFlatMapIntoArray:
case Builtin::kFlattenIntoArray:
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