From 32fc0acfef4bb859f7bd30a28d834ef8f793e8bc Mon Sep 17 00:00:00 2001 From: Matt Gardner Date: Fri, 22 Feb 2019 13:07:39 -0800 Subject: [PATCH] Optimize `in` operator This change implements optimizations for the `in` operator for packed array elements and object properties. It adds a new feedback slot kind and an IC path similar to KeyedLoadIC for handling the lookups. TurboFan uses the feedback to optimize based on the maps and keys. For more details see: https://docs.google.com/document/d/1tIfzywY8AeNVcy_sen-5Xev21MeZwjcU8QhSdzHvXig This can provide 10x performance improvements of on loops of the form: for (let i = 0; i < ary.length; ++i) { if (i in ary) { ... } } Bug: v8:8733 Change-Id: I766bf865a547a059e5bce5399bb6112e5d9a85c8 Reviewed-on: https://chromium-review.googlesource.com/c/1432598 Reviewed-by: Ulan Degenbaev Reviewed-by: Igor Sheludko Reviewed-by: Ross McIlroy Reviewed-by: Benedikt Meurer Reviewed-by: Toon Verwaest Commit-Queue: Matt Gardner Cr-Commit-Position: refs/heads/master@{#59843} --- src/builtins/builtins-definitions.h | 6 + src/builtins/builtins-handler-gen.cc | 45 ++ src/builtins/builtins-ic-gen.cc | 3 + src/code-stub-assembler.cc | 156 +++--- src/code-stub-assembler.h | 17 +- src/compiler/access-info.cc | 15 +- src/compiler/access-info.h | 2 +- src/compiler/bytecode-graph-builder.cc | 4 +- src/compiler/js-call-reducer.cc | 5 +- .../js-native-context-specialization.cc | 142 ++++- .../js-native-context-specialization.h | 5 + src/compiler/js-operator.cc | 13 +- src/compiler/js-operator.h | 2 +- src/feedback-vector-inl.h | 2 + src/feedback-vector.cc | 23 +- src/feedback-vector.h | 9 + src/heap/object-stats.cc | 1 + src/ic/accessor-assembler.cc | 492 ++++++++++++------ src/ic/accessor-assembler.h | 33 +- src/ic/ic.cc | 187 +++++-- src/ic/ic.h | 16 +- src/interpreter/bytecode-array-builder.cc | 11 +- src/interpreter/bytecode-array-builder.h | 1 - src/interpreter/bytecode-generator.cc | 10 +- src/interpreter/bytecodes.h | 2 +- src/interpreter/interpreter-generator.cc | 12 +- src/objects-printer.cc | 21 +- src/runtime/runtime-object.cc | 24 + src/runtime/runtime.h | 7 +- .../bytecode_expectations/IfConditions.golden | 10 +- test/cctest/interpreter/test-interpreter.cc | 11 +- test/cctest/test-api-interceptors.cc | 198 ++++++- test/mjsunit/arguments.js | 11 + test/mjsunit/keyed-has-ic-module-export.js | 9 + test/mjsunit/keyed-has-ic-module-import.js | 70 +++ test/mjsunit/keyed-has-ic.js | 402 ++++++++++++++ .../bytecode-array-builder-unittest.cc | 2 +- .../interpreter/bytecodes-unittest.cc | 2 +- 38 files changed, 1619 insertions(+), 362 deletions(-) create mode 100644 test/mjsunit/keyed-has-ic-module-export.js create mode 100644 test/mjsunit/keyed-has-ic-module-import.js create mode 100644 test/mjsunit/keyed-has-ic.js diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 6e5e70211b..ab4fea02de 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -251,6 +251,10 @@ namespace internal { TFH(ElementsTransitionAndStore_GrowNoTransitionHandleCOW, StoreTransition) \ TFH(ElementsTransitionAndStore_NoTransitionIgnoreOOB, StoreTransition) \ TFH(ElementsTransitionAndStore_NoTransitionHandleCOW, StoreTransition) \ + TFH(KeyedHasIC_PolymorphicName, LoadWithVector) \ + TFH(KeyedHasIC_SloppyArguments, LoadWithVector) \ + TFH(HasIndexedInterceptorIC, LoadWithVector) \ + TFH(HasIC_Slow, LoadWithVector) \ \ /* Microtask helpers */ \ TFS(EnqueueMicrotask, kMicrotask) \ @@ -626,6 +630,8 @@ namespace internal { TFH(LoadGlobalICInsideTypeofTrampoline, LoadGlobal) \ TFH(CloneObjectIC, CloneObjectWithVector) \ TFH(CloneObjectIC_Slow, CloneObjectWithVector) \ + TFH(KeyedHasIC, LoadWithVector) \ + TFH(KeyedHasIC_Megamorphic, LoadWithVector) \ \ /* IterableToList */ \ /* ES #sec-iterabletolist */ \ diff --git a/src/builtins/builtins-handler-gen.cc b/src/builtins/builtins-handler-gen.cc index 2ab39af276..00602203b3 100644 --- a/src/builtins/builtins-handler-gen.cc +++ b/src/builtins/builtins-handler-gen.cc @@ -493,5 +493,50 @@ TF_BUILTIN(LoadIndexedInterceptorIC, CodeStubAssembler) { vector); } +TF_BUILTIN(KeyedHasIC_SloppyArguments, CodeStubAssembler) { + Node* receiver = Parameter(Descriptor::kReceiver); + Node* key = Parameter(Descriptor::kName); + Node* slot = Parameter(Descriptor::kSlot); + Node* vector = Parameter(Descriptor::kVector); + Node* context = Parameter(Descriptor::kContext); + + Label miss(this); + + Node* result = HasKeyedSloppyArguments(receiver, key, &miss); + Return(result); + + BIND(&miss); + { + Comment("Miss"); + TailCallRuntime(Runtime::kKeyedHasIC_Miss, context, receiver, key, slot, + vector); + } +} + +TF_BUILTIN(HasIndexedInterceptorIC, CodeStubAssembler) { + Node* receiver = Parameter(Descriptor::kReceiver); + Node* key = Parameter(Descriptor::kName); + Node* slot = Parameter(Descriptor::kSlot); + Node* vector = Parameter(Descriptor::kVector); + Node* context = Parameter(Descriptor::kContext); + + Label if_keyispositivesmi(this), if_keyisinvalid(this); + Branch(TaggedIsPositiveSmi(key), &if_keyispositivesmi, &if_keyisinvalid); + BIND(&if_keyispositivesmi); + TailCallRuntime(Runtime::kHasElementWithInterceptor, context, receiver, key); + + BIND(&if_keyisinvalid); + TailCallRuntime(Runtime::kKeyedHasIC_Miss, context, receiver, key, slot, + vector); +} + +TF_BUILTIN(HasIC_Slow, CodeStubAssembler) { + Node* receiver = Parameter(Descriptor::kReceiver); + Node* name = Parameter(Descriptor::kName); + Node* context = Parameter(Descriptor::kContext); + + TailCallRuntime(Runtime::kHasProperty, context, receiver, name); +} + } // namespace internal } // namespace v8 diff --git a/src/builtins/builtins-ic-gen.cc b/src/builtins/builtins-ic-gen.cc index 94d75a8f32..ce944784ea 100644 --- a/src/builtins/builtins-ic-gen.cc +++ b/src/builtins/builtins-ic-gen.cc @@ -40,6 +40,9 @@ IC_BUILTIN(KeyedStoreICTrampoline) IC_BUILTIN(StoreInArrayLiteralIC) IC_BUILTIN(CloneObjectIC) IC_BUILTIN(CloneObjectIC_Slow) +IC_BUILTIN(KeyedHasIC) +IC_BUILTIN(KeyedHasIC_Megamorphic) +IC_BUILTIN(KeyedHasIC_PolymorphicName) IC_BUILTIN_PARAM(LoadGlobalIC, LoadGlobalIC, NOT_INSIDE_TYPEOF) IC_BUILTIN_PARAM(LoadGlobalICInsideTypeof, LoadGlobalIC, INSIDE_TYPEOF) diff --git a/src/code-stub-assembler.cc b/src/code-stub-assembler.cc index c510a28b1a..6ba5e8665f 100644 --- a/src/code-stub-assembler.cc +++ b/src/code-stub-assembler.cc @@ -10123,8 +10123,9 @@ TNode CodeStubAssembler::TryToIntptr(Node* key, Label* miss) { return var_intptr_key.value(); } -Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key, - Node* value, Label* bailout) { +Node* CodeStubAssembler::EmitKeyedSloppyArguments( + Node* receiver, Node* key, Node* value, Label* bailout, + ArgumentsAccessMode access_mode) { // Mapped arguments are actual arguments. Unmapped arguments are values added // to the arguments object after it was created for the call. Mapped arguments // are stored in the context at indexes given by elements[key + 2]. Unmapped @@ -10151,8 +10152,6 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key, // index into the context array given at elements[0]. Return the value at // context[t]. - bool is_load = value == nullptr; - GotoIfNot(TaggedIsSmi(key), bailout); key = SmiUntag(key); GotoIf(IntPtrLessThan(key, IntPtrConstant(0)), bailout); @@ -10161,8 +10160,11 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key, TNode elements_length = LoadAndUntagFixedArrayBaseLength(elements); VARIABLE(var_result, MachineRepresentation::kTagged); - if (!is_load) { + if (access_mode == ArgumentsAccessMode::kStore) { var_result.Bind(value); + } else { + DCHECK(access_mode == ArgumentsAccessMode::kLoad || + access_mode == ArgumentsAccessMode::kHas); } Label if_mapped(this), if_unmapped(this), end(this, &var_result); Node* intptr_two = IntPtrConstant(2); @@ -10178,10 +10180,14 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key, { TNode mapped_index_intptr = SmiUntag(CAST(mapped_index)); TNode the_context = CAST(LoadFixedArrayElement(elements, 0)); - if (is_load) { + if (access_mode == ArgumentsAccessMode::kLoad) { Node* result = LoadContextElement(the_context, mapped_index_intptr); CSA_ASSERT(this, WordNotEqual(result, TheHoleConstant())); var_result.Bind(result); + } else if (access_mode == ArgumentsAccessMode::kHas) { + CSA_ASSERT(this, Word32BinaryNot(IsTheHole(LoadContextElement( + the_context, mapped_index_intptr)))); + var_result.Bind(TrueConstant()); } else { StoreContextElement(the_context, mapped_index_intptr, value); } @@ -10198,17 +10204,31 @@ Node* CodeStubAssembler::EmitKeyedSloppyArguments(Node* receiver, Node* key, TNode backing_store_length = LoadAndUntagFixedArrayBaseLength(backing_store); - GotoIf(UintPtrGreaterThanOrEqual(key, backing_store_length), bailout); - - // The key falls into unmapped range. - if (is_load) { + if (access_mode == ArgumentsAccessMode::kHas) { + Label out_of_bounds(this); + GotoIf(UintPtrGreaterThanOrEqual(key, backing_store_length), + &out_of_bounds); Node* result = LoadFixedArrayElement(backing_store, key); - GotoIf(WordEqual(result, TheHoleConstant()), bailout); - var_result.Bind(result); + var_result.Bind( + SelectBooleanConstant(WordNotEqual(result, TheHoleConstant()))); + Goto(&end); + + BIND(&out_of_bounds); + var_result.Bind(FalseConstant()); + Goto(&end); } else { - StoreFixedArrayElement(backing_store, key, value); + GotoIf(UintPtrGreaterThanOrEqual(key, backing_store_length), bailout); + + // The key falls into unmapped range. + if (access_mode == ArgumentsAccessMode::kLoad) { + Node* result = LoadFixedArrayElement(backing_store, key); + GotoIf(WordEqual(result, TheHoleConstant()), bailout); + var_result.Bind(result); + } else { + StoreFixedArrayElement(backing_store, key, value); + } + Goto(&end); } - Goto(&end); } BIND(&end); @@ -11012,63 +11032,63 @@ void CodeStubAssembler::BranchIfNumberRelationalComparison( TVARIABLE(Float64T, var_left_float); TVARIABLE(Float64T, var_right_float); - Branch(TaggedIsSmi(left), - [&] { - TNode smi_left = CAST(left); + Branch( + TaggedIsSmi(left), + [&] { + TNode smi_left = CAST(left); - Branch(TaggedIsSmi(right), - [&] { - TNode smi_right = CAST(right); + Branch( + TaggedIsSmi(right), + [&] { + TNode smi_right = CAST(right); - // Both {left} and {right} are Smi, so just perform a fast - // Smi comparison. - switch (op) { - case Operation::kEqual: - BranchIfSmiEqual(smi_left, smi_right, if_true, - if_false); - break; - case Operation::kLessThan: - BranchIfSmiLessThan(smi_left, smi_right, if_true, - if_false); - break; - case Operation::kLessThanOrEqual: - BranchIfSmiLessThanOrEqual(smi_left, smi_right, if_true, - if_false); - break; - case Operation::kGreaterThan: - BranchIfSmiLessThan(smi_right, smi_left, if_true, - if_false); - break; - case Operation::kGreaterThanOrEqual: - BranchIfSmiLessThanOrEqual(smi_right, smi_left, if_true, - if_false); - break; - default: - UNREACHABLE(); - } - }, - [&] { - CSA_ASSERT(this, IsHeapNumber(right)); - var_left_float = SmiToFloat64(smi_left); - var_right_float = LoadHeapNumberValue(right); - Goto(&do_float_comparison); - }); - }, - [&] { - CSA_ASSERT(this, IsHeapNumber(left)); - var_left_float = LoadHeapNumberValue(left); + // Both {left} and {right} are Smi, so just perform a fast + // Smi comparison. + switch (op) { + case Operation::kEqual: + BranchIfSmiEqual(smi_left, smi_right, if_true, if_false); + break; + case Operation::kLessThan: + BranchIfSmiLessThan(smi_left, smi_right, if_true, if_false); + break; + case Operation::kLessThanOrEqual: + BranchIfSmiLessThanOrEqual(smi_left, smi_right, if_true, + if_false); + break; + case Operation::kGreaterThan: + BranchIfSmiLessThan(smi_right, smi_left, if_true, if_false); + break; + case Operation::kGreaterThanOrEqual: + BranchIfSmiLessThanOrEqual(smi_right, smi_left, if_true, + if_false); + break; + default: + UNREACHABLE(); + } + }, + [&] { + CSA_ASSERT(this, IsHeapNumber(right)); + var_left_float = SmiToFloat64(smi_left); + var_right_float = LoadHeapNumberValue(right); + Goto(&do_float_comparison); + }); + }, + [&] { + CSA_ASSERT(this, IsHeapNumber(left)); + var_left_float = LoadHeapNumberValue(left); - Branch(TaggedIsSmi(right), - [&] { - var_right_float = SmiToFloat64(right); - Goto(&do_float_comparison); - }, - [&] { - CSA_ASSERT(this, IsHeapNumber(right)); - var_right_float = LoadHeapNumberValue(right); - Goto(&do_float_comparison); - }); - }); + Branch( + TaggedIsSmi(right), + [&] { + var_right_float = SmiToFloat64(right); + Goto(&do_float_comparison); + }, + [&] { + CSA_ASSERT(this, IsHeapNumber(right)); + var_right_float = LoadHeapNumberValue(right); + Goto(&do_float_comparison); + }); + }); BIND(&do_float_comparison); { diff --git a/src/code-stub-assembler.h b/src/code-stub-assembler.h index 27eba3227b..5f8cc69559 100644 --- a/src/code-stub-assembler.h +++ b/src/code-stub-assembler.h @@ -2892,16 +2892,26 @@ class V8_EXPORT_PRIVATE CodeStubAssembler TNode LoadReceiverMap(SloppyTNode receiver); + enum class ArgumentsAccessMode { kLoad, kStore, kHas }; + // Emits keyed sloppy arguments has. Returns whether the key is in the + // arguments. + Node* HasKeyedSloppyArguments(Node* receiver, Node* key, Label* bailout) { + return EmitKeyedSloppyArguments(receiver, key, nullptr, bailout, + ArgumentsAccessMode::kHas); + } + // Emits keyed sloppy arguments load. Returns either the loaded value. Node* LoadKeyedSloppyArguments(Node* receiver, Node* key, Label* bailout) { - return EmitKeyedSloppyArguments(receiver, key, nullptr, bailout); + return EmitKeyedSloppyArguments(receiver, key, nullptr, bailout, + ArgumentsAccessMode::kLoad); } // Emits keyed sloppy arguments store. void StoreKeyedSloppyArguments(Node* receiver, Node* key, Node* value, Label* bailout) { DCHECK_NOT_NULL(value); - EmitKeyedSloppyArguments(receiver, key, value, bailout); + EmitKeyedSloppyArguments(receiver, key, value, bailout, + ArgumentsAccessMode::kStore); } // Loads script context from the script context table. @@ -3409,7 +3419,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler // Emits keyed sloppy arguments load if the |value| is nullptr or store // otherwise. Returns either the loaded value or |value|. Node* EmitKeyedSloppyArguments(Node* receiver, Node* key, Node* value, - Label* bailout); + Label* bailout, + ArgumentsAccessMode access_mode); TNode AllocateSlicedString(RootIndex map_root_index, TNode length, diff --git a/src/compiler/access-info.cc b/src/compiler/access-info.cc index aff83fe7aa..305a726e44 100644 --- a/src/compiler/access-info.cc +++ b/src/compiler/access-info.cc @@ -63,6 +63,8 @@ std::ostream& operator<<(std::ostream& os, AccessMode access_mode) { return os << "Store"; case AccessMode::kStoreInLiteral: return os << "StoreInLiteral"; + case AccessMode::kHas: + return os << "Has"; } UNREACHABLE(); } @@ -177,6 +179,7 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that, if (this->field_index_.GetFieldAccessStubKey() == that->field_index_.GetFieldAccessStubKey()) { switch (access_mode) { + case AccessMode::kHas: case AccessMode::kLoad: { if (this->field_representation_ != that->field_representation_) { if (!IsAnyTagged(this->field_representation_) || @@ -306,7 +309,7 @@ void ProcessFeedbackMaps(Isolate* isolate, MapHandles const& maps, bool AccessInfoFactory::ComputeElementAccessInfos( MapHandles const& maps, AccessMode access_mode, ZoneVector* access_infos) const { - if (access_mode == AccessMode::kLoad) { + if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) { // For polymorphic loads of similar elements kinds (i.e. all tagged or all // double), always use the "worst case" code without a transition. This is // much faster than transitioning the elements to the worst case, trading a @@ -414,6 +417,12 @@ bool AccessInfoFactory::ComputeAccessorDescriptorAccessInfo( PropertyAccessInfo::ModuleExport(MapHandles{receiver_map}, cell); return true; } + if (access_mode == AccessMode::kHas) { + // HasProperty checks don't call getter/setters, existence is sufficient. + *access_info = PropertyAccessInfo::AccessorConstant( + MapHandles{receiver_map}, Handle(), holder); + return true; + } Handle accessors(descriptors->GetStrongValue(number), isolate()); if (!accessors->IsAccessorPair()) return false; Handle accessor(access_mode == AccessMode::kLoad @@ -456,11 +465,13 @@ bool AccessInfoFactory::ComputePropertyAccessInfo( PropertyAccessInfo* access_info) const { CHECK(name->IsUniqueName()); + if (access_mode == AccessMode::kHas && !map->IsJSReceiverMap()) return false; + // Check if it is safe to inline property access for the {map}. if (!CanInlinePropertyAccess(map)) return false; // We support fast inline cases for certain JSObject getters. - if (access_mode == AccessMode::kLoad && + if ((access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) && LookupSpecialFieldAccessor(map, name, access_info)) { return true; } diff --git a/src/compiler/access-info.h b/src/compiler/access-info.h index f40c8a31d4..13266eb22c 100644 --- a/src/compiler/access-info.h +++ b/src/compiler/access-info.h @@ -29,7 +29,7 @@ class TypeCache; // Whether we are loading a property or storing to a property. // For a store during literal creation, do not walk up the prototype chain. -enum class AccessMode { kLoad, kStore, kStoreInLiteral }; +enum class AccessMode { kLoad, kStore, kStoreInLiteral, kHas }; std::ostream& operator<<(std::ostream&, AccessMode); diff --git a/src/compiler/bytecode-graph-builder.cc b/src/compiler/bytecode-graph-builder.cc index 763242d117..8789f683f8 100644 --- a/src/compiler/bytecode-graph-builder.cc +++ b/src/compiler/bytecode-graph-builder.cc @@ -2542,7 +2542,9 @@ void BytecodeGraphBuilder::VisitTestIn() { Node* object = environment()->LookupAccumulator(); Node* key = environment()->LookupRegister(bytecode_iterator().GetRegisterOperand(0)); - Node* node = NewNode(javascript()->HasProperty(), object, key); + VectorSlotPair feedback = + CreateVectorSlotPair(bytecode_iterator().GetIndexOperand(1)); + Node* node = NewNode(javascript()->HasProperty(feedback), object, key); environment()->BindAccumulator(node, Environment::kAttachFrameState); } diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index aa9ecf2dd5..e3567199fe 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -944,9 +944,10 @@ Reduction JSCallReducer::ReduceReflectHas(Node* node) { Node* etrue = effect; Node* vtrue; { + // TODO(magardn): collect feedback so this can be optimized vtrue = etrue = if_true = - graph()->NewNode(javascript()->HasProperty(), target, key, context, - frame_state, etrue, if_true); + graph()->NewNode(javascript()->HasProperty(VectorSlotPair()), target, + key, context, frame_state, etrue, if_true); } // Rewire potential exception edges. diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc index de2f2b2e02..ce647204ba 100644 --- a/src/compiler/js-native-context-specialization.cc +++ b/src/compiler/js-native-context-specialization.cc @@ -106,6 +106,8 @@ Reduction JSNativeContextSpecialization::Reduce(Node* node) { return ReduceJSLoadNamed(node); case IrOpcode::kJSStoreNamed: return ReduceJSStoreNamed(node); + case IrOpcode::kJSHasProperty: + return ReduceJSHasProperty(node); case IrOpcode::kJSLoadProperty: return ReduceJSLoadProperty(node); case IrOpcode::kJSStoreProperty: @@ -208,7 +210,7 @@ bool IsStringConstant(JSHeapBroker* broker, Node* node) { HeapObjectMatcher matcher(node); return matcher.HasValue() && matcher.Ref(broker).IsString(); } -} +} // namespace Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionEnter( Node* node) { @@ -1108,7 +1110,8 @@ Reduction JSNativeContextSpecialization::ReduceNamedAccess( node->opcode() == IrOpcode::kJSStoreNamed || node->opcode() == IrOpcode::kJSLoadProperty || node->opcode() == IrOpcode::kJSStoreProperty || - node->opcode() == IrOpcode::kJSStoreNamedOwn); + node->opcode() == IrOpcode::kJSStoreNamedOwn || + node->opcode() == IrOpcode::kJSHasProperty); Node* receiver = NodeProperties::GetValueInput(node, 0); Node* context = NodeProperties::GetContextInput(node); Node* frame_state = NodeProperties::GetFrameStateInput(node); @@ -1444,7 +1447,6 @@ Reduction JSNativeContextSpecialization::ReduceJSLoadNamed(Node* node) { AccessMode::kLoad); } - Reduction JSNativeContextSpecialization::ReduceJSStoreNamed(Node* node) { DCHECK_EQ(IrOpcode::kJSStoreNamed, node->opcode()); NamedAccess const& p = NamedAccessOf(node->op()); @@ -1505,7 +1507,8 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess( KeyedAccessStoreMode store_mode) { DCHECK(node->opcode() == IrOpcode::kJSLoadProperty || node->opcode() == IrOpcode::kJSStoreProperty || - node->opcode() == IrOpcode::kJSStoreInArrayLiteral); + node->opcode() == IrOpcode::kJSStoreInArrayLiteral || + node->opcode() == IrOpcode::kJSHasProperty); Node* receiver = NodeProperties::GetValueInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); Node* control = NodeProperties::GetControlInput(node); @@ -1566,6 +1569,17 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess( for (Handle prototype_map : prototype_maps) { dependencies()->DependOnStableMap(MapRef(broker(), prototype_map)); } + } else if (access_mode == AccessMode::kHas) { + // If we have any fast arrays, we need to check and depend on + // NoElementsProtector. + for (ElementAccessInfo const& access_info : access_infos) { + if (IsFastElementsKind(access_info.elements_kind())) { + if (!isolate()->IsNoElementsProtectorIntact()) return NoChange(); + dependencies()->DependOnProtector( + PropertyCellRef(broker(), factory()->no_elements_protector())); + break; + } + } } // Ensure that {receiver} is a heap object. @@ -1704,7 +1718,7 @@ Reduction JSNativeContextSpecialization::ReduceElementAccess( } Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant( - Node* node, Node* index, FeedbackNexus const& nexus, + Node* node, Node* index, FeedbackNexus const& nexus, AccessMode access_mode, KeyedAccessLoadMode load_mode) { DCHECK_EQ(node->opcode(), IrOpcode::kJSLoadProperty); Node* receiver = NodeProperties::GetValueInput(node, 0); @@ -1715,7 +1729,8 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant( HeapObjectRef receiver_ref = mreceiver.Ref(broker()).AsHeapObject(); if (receiver_ref.map().oddball_type() == OddballType::kHole || receiver_ref.map().oddball_type() == OddballType::kNull || - receiver_ref.map().oddball_type() == OddballType::kUndefined) { + receiver_ref.map().oddball_type() == OddballType::kUndefined || + (receiver_ref.map().IsString() && access_mode == AccessMode::kHas)) { return NoChange(); } @@ -1732,7 +1747,9 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant( // We can safely constant-fold the {index} access to {receiver}, // since the element is non-configurable, non-writable and thus // cannot change anymore. - Node* value = jsgraph()->Constant(it.GetDataValue()); + Node* value = access_mode == AccessMode::kHas + ? jsgraph()->TrueConstant() + : jsgraph()->Constant(it.GetDataValue()); ReplaceWithValue(node, value, effect, control); return Replace(value); } @@ -1761,7 +1778,9 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant( effect = graph()->NewNode( simplified()->CheckIf(DeoptimizeReason::kCowArrayElementsChanged), check, effect, control); - Node* value = jsgraph()->Constant(it.GetDataValue()); + Node* value = access_mode == AccessMode::kHas + ? jsgraph()->TrueConstant() + : jsgraph()->Constant(it.GetDataValue()); ReplaceWithValue(node, value, effect, control); return Replace(value); } @@ -1771,7 +1790,7 @@ Reduction JSNativeContextSpecialization::ReduceKeyedLoadFromHeapConstant( // For constant Strings we can eagerly strength-reduce the keyed // accesses using the known length, which doesn't change. - if (receiver_ref.IsString()) { + if (receiver_ref.IsString() && access_mode != AccessMode::kHas) { // We can only assume that the {index} is a valid array index if the // IC is in element access mode and not MEGAMORPHIC, otherwise there's // no guard for the bounds check below. @@ -1797,14 +1816,16 @@ Reduction JSNativeContextSpecialization::ReduceKeyedAccess( AccessMode access_mode, KeyedAccessLoadMode load_mode, KeyedAccessStoreMode store_mode) { DCHECK(node->opcode() == IrOpcode::kJSLoadProperty || - node->opcode() == IrOpcode::kJSStoreProperty); + node->opcode() == IrOpcode::kJSStoreProperty || + node->opcode() == IrOpcode::kJSHasProperty); + Node* receiver = NodeProperties::GetValueInput(node, 0); Node* effect = NodeProperties::GetEffectInput(node); if (access_mode == AccessMode::kLoad && receiver->opcode() == IrOpcode::kHeapConstant) { - Reduction reduction = - ReduceKeyedLoadFromHeapConstant(node, index, nexus, load_mode); + Reduction reduction = ReduceKeyedLoadFromHeapConstant( + node, index, nexus, access_mode, load_mode); if (reduction.Changed()) return reduction; } @@ -1879,6 +1900,21 @@ Reduction JSNativeContextSpecialization::ReduceSoftDeoptimize( return NoChange(); } +Reduction JSNativeContextSpecialization::ReduceJSHasProperty(Node* node) { + DCHECK_EQ(IrOpcode::kJSHasProperty, node->opcode()); + PropertyAccess const& p = PropertyAccessOf(node->op()); + Node* index = NodeProperties::GetValueInput(node, 1); + Node* value = jsgraph()->Dead(); + + // Extract receiver maps from the has property IC using the FeedbackNexus. + if (!p.feedback().IsValid()) return NoChange(); + FeedbackNexus nexus(p.feedback().vector(), p.feedback().slot()); + + // Try to lower the keyed access based on the {nexus}. + return ReduceKeyedAccess(node, index, value, nexus, AccessMode::kHas, + STANDARD_LOAD, STANDARD_STORE); +} + Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey( Node* node) { // We can optimize a property load if it's being used inside a for..in: @@ -2188,6 +2224,22 @@ JSNativeContextSpecialization::BuildPropertyLoad( return ValueEffectControl(value, effect, control); } +JSNativeContextSpecialization::ValueEffectControl +JSNativeContextSpecialization::BuildPropertyTest( + Node* effect, Node* control, PropertyAccessInfo const& access_info) { + // Determine actual holder and perform prototype chain checks. + Handle holder; + if (access_info.holder().ToHandle(&holder)) { + dependencies()->DependOnStablePrototypeChains( + broker(), access_info.receiver_maps(), JSObjectRef(broker(), holder)); + } + + Node* value = access_info.IsNotFound() ? jsgraph()->FalseConstant() + : jsgraph()->TrueConstant(); + + return ValueEffectControl(value, effect, control); +} + JSNativeContextSpecialization::ValueEffectControl JSNativeContextSpecialization::BuildPropertyAccess( Node* receiver, Node* value, Node* context, Node* frame_state, Node* effect, @@ -2202,6 +2254,8 @@ JSNativeContextSpecialization::BuildPropertyAccess( return BuildPropertyStore(receiver, value, context, frame_state, effect, control, name, if_exceptions, access_info, access_mode); + case AccessMode::kHas: + return BuildPropertyTest(effect, control, access_info); } UNREACHABLE(); return ValueEffectControl(); @@ -2570,7 +2624,6 @@ JSNativeContextSpecialization::BuildElementAccess( Node* receiver, Node* index, Node* value, Node* effect, Node* control, ElementAccessInfo const& access_info, AccessMode access_mode, KeyedAccessLoadMode load_mode, KeyedAccessStoreMode store_mode) { - // TODO(bmeurer): We currently specialize based on elements kind. We should // also be able to properly support strings and other JSObjects here. ElementsKind elements_kind = access_info.elements_kind(); @@ -2670,7 +2723,7 @@ JSNativeContextSpecialization::BuildElementAccess( // below are performed on unsigned values, which means that all the // Negative32 values are treated as out-of-bounds. index = graph()->NewNode(simplified()->NumberToUint32(), index); - } else { + } else if (access_mode != AccessMode::kHas) { // Check that the {index} is in the valid range for the {receiver}. index = effect = graph()->NewNode(simplified()->CheckBounds(VectorSlotPair()), index, @@ -2775,6 +2828,13 @@ JSNativeContextSpecialization::BuildElementAccess( } break; } + case AccessMode::kHas: + // For has property on a typed array, all we need is a bounds check. + value = effect = + graph()->NewNode(simplified()->SpeculativeNumberLessThan( + NumberOperationHint::kSignedSmall), + index, length, effect, control); + break; } } else { // Load the elements for the {receiver}. @@ -2821,7 +2881,7 @@ JSNativeContextSpecialization::BuildElementAccess( index = effect = graph()->NewNode( simplified()->CheckBounds(VectorSlotPair()), index, jsgraph()->Constant(Smi::kMaxValue), effect, control); - } else { + } else if (access_mode != AccessMode::kHas) { // Check that the {index} is in the valid range for the {receiver}. index = effect = graph()->NewNode(simplified()->CheckBounds(VectorSlotPair()), index, @@ -2941,6 +3001,58 @@ JSNativeContextSpecialization::BuildElementAccess( effect, control); } } + } else if (access_mode == AccessMode::kHas) { + // For packed arrays with NoElementsProctector valid, a bound check + // is equivalent to HasProperty. + value = effect = graph()->NewNode(simplified()->SpeculativeNumberLessThan( + NumberOperationHint::kSignedSmall), + index, length, effect, control); + if (IsHoleyElementsKind(elements_kind)) { + // If the index is in bounds, do a load and hole check. + + Node* branch = graph()->NewNode(common()->Branch(), value, control); + + Node* if_false = graph()->NewNode(common()->IfFalse(), branch); + Node* efalse = effect; + Node* vfalse = jsgraph()->FalseConstant(); + + element_access.type = + Type::Union(element_type, Type::Hole(), graph()->zone()); + + if (elements_kind == HOLEY_ELEMENTS || + elements_kind == HOLEY_SMI_ELEMENTS) { + element_access.machine_type = MachineType::AnyTagged(); + } + + Node* if_true = graph()->NewNode(common()->IfTrue(), branch); + Node* etrue = effect; + + Node* checked = etrue = + graph()->NewNode(simplified()->CheckBounds(VectorSlotPair()), index, + length, etrue, if_true); + + Node* element = etrue = + graph()->NewNode(simplified()->LoadElement(element_access), + elements, checked, etrue, if_true); + + Node* vtrue; + if (IsDoubleElementsKind(elements_kind)) { + vtrue = + graph()->NewNode(simplified()->NumberIsFloat64Hole(), element); + } else { + vtrue = graph()->NewNode(simplified()->ReferenceEqual(), element, + jsgraph()->TheHoleConstant()); + } + + vtrue = graph()->NewNode(simplified()->BooleanNot(), vtrue); + + control = graph()->NewNode(common()->Merge(2), if_true, if_false); + effect = + graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control); + value = + graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2), + vtrue, vfalse, control); + } } else { DCHECK(access_mode == AccessMode::kStore || access_mode == AccessMode::kStoreInLiteral); diff --git a/src/compiler/js-native-context-specialization.h b/src/compiler/js-native-context-specialization.h index 9e9d5f4b7e..745a10403f 100644 --- a/src/compiler/js-native-context-specialization.h +++ b/src/compiler/js-native-context-specialization.h @@ -84,6 +84,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final Reduction ReduceJSStoreGlobal(Node* node); Reduction ReduceJSLoadNamed(Node* node); Reduction ReduceJSStoreNamed(Node* node); + Reduction ReduceJSHasProperty(Node* node); Reduction ReduceJSLoadProperty(Node* node); Reduction ReduceJSStoreProperty(Node* node); Reduction ReduceJSStoreNamedOwn(Node* node); @@ -117,6 +118,7 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final Node* index, Handle property_cell); Reduction ReduceKeyedLoadFromHeapConstant(Node* node, Node* index, FeedbackNexus const& nexus, + AccessMode access_mode, KeyedAccessLoadMode load_mode); Reduction ReduceElementAccessOnString(Node* node, Node* index, Node* value, AccessMode access_mode, @@ -169,6 +171,9 @@ class V8_EXPORT_PRIVATE JSNativeContextSpecialization final PropertyAccessInfo const& access_info, AccessMode access_mode); + ValueEffectControl BuildPropertyTest(Node* effect, Node* control, + PropertyAccessInfo const& access_info); + // Helpers for accessor inlining. Node* InlinePropertyGetterCall(Node* receiver, Node* context, Node* frame_state, Node** effect, diff --git a/src/compiler/js-operator.cc b/src/compiler/js-operator.cc index 15f6152787..e207a034eb 100644 --- a/src/compiler/js-operator.cc +++ b/src/compiler/js-operator.cc @@ -282,7 +282,8 @@ bool operator!=(PropertyAccess const& lhs, PropertyAccess const& rhs) { PropertyAccess const& PropertyAccessOf(const Operator* op) { - DCHECK(op->opcode() == IrOpcode::kJSLoadProperty || + DCHECK(op->opcode() == IrOpcode::kJSHasProperty || + op->opcode() == IrOpcode::kJSLoadProperty || op->opcode() == IrOpcode::kJSStoreProperty); return OpParameter(op); } @@ -624,7 +625,6 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) { V(CreateTypedArray, Operator::kNoProperties, 5, 1) \ V(CreateObject, Operator::kNoProperties, 1, 1) \ V(ObjectIsArray, Operator::kNoProperties, 1, 1) \ - V(HasProperty, Operator::kNoProperties, 2, 1) \ V(HasInPrototypeChain, Operator::kNoProperties, 2, 1) \ V(OrdinaryHasInstance, Operator::kNoProperties, 2, 1) \ V(ForInEnumerate, Operator::kNoProperties, 1, 1) \ @@ -950,6 +950,15 @@ const Operator* JSOperatorBuilder::LoadProperty( access); // parameter } +const Operator* JSOperatorBuilder::HasProperty(VectorSlotPair const& feedback) { + PropertyAccess access(LanguageMode::kSloppy, feedback); + return new (zone()) Operator1( // -- + IrOpcode::kJSHasProperty, Operator::kNoProperties, // opcode + "JSHasProperty", // name + 2, 1, 1, 1, 1, 2, // counts + access); // parameter +} + const Operator* JSOperatorBuilder::InstanceOf(VectorSlotPair const& feedback) { FeedbackParameter parameter(feedback); return new (zone()) Operator1( // -- diff --git a/src/compiler/js-operator.h b/src/compiler/js-operator.h index 16eaf615d9..773f5ddcbb 100644 --- a/src/compiler/js-operator.h +++ b/src/compiler/js-operator.h @@ -789,7 +789,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final const Operator* DeleteProperty(); - const Operator* HasProperty(); + const Operator* HasProperty(VectorSlotPair const& feedback); const Operator* GetSuperConstructor(); diff --git a/src/feedback-vector-inl.h b/src/feedback-vector-inl.h index 8f65d109d6..afdebc07a2 100644 --- a/src/feedback-vector-inl.h +++ b/src/feedback-vector-inl.h @@ -71,6 +71,7 @@ int FeedbackMetadata::GetSlotSize(FeedbackSlotKind kind) { case FeedbackSlotKind::kLoadGlobalInsideTypeof: case FeedbackSlotKind::kLoadGlobalNotInsideTypeof: case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kHasKeyed: case FeedbackSlotKind::kStoreNamedSloppy: case FeedbackSlotKind::kStoreNamedStrict: case FeedbackSlotKind::kStoreOwnNamed: @@ -267,6 +268,7 @@ void FeedbackVector::ComputeCounts(int* with_type_info, int* generic, case FeedbackSlotKind::kLoadGlobalInsideTypeof: case FeedbackSlotKind::kLoadGlobalNotInsideTypeof: case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kHasKeyed: case FeedbackSlotKind::kStoreNamedSloppy: case FeedbackSlotKind::kStoreNamedStrict: case FeedbackSlotKind::kStoreOwnNamed: diff --git a/src/feedback-vector.cc b/src/feedback-vector.cc index 5521eeff6e..c26bedc600 100644 --- a/src/feedback-vector.cc +++ b/src/feedback-vector.cc @@ -143,6 +143,8 @@ const char* FeedbackMetadata::Kind2String(FeedbackSlotKind kind) { return "LoadGlobalNotInsideTypeof"; case FeedbackSlotKind::kLoadKeyed: return "LoadKeyed"; + case FeedbackSlotKind::kHasKeyed: + return "HasKeyed"; case FeedbackSlotKind::kStoreNamedSloppy: return "StoreNamedSloppy"; case FeedbackSlotKind::kStoreNamedStrict: @@ -264,6 +266,7 @@ Handle FeedbackVector::New(Isolate* isolate, case FeedbackSlotKind::kCloneObject: case FeedbackSlotKind::kLoadProperty: case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kHasKeyed: case FeedbackSlotKind::kStoreNamedSloppy: case FeedbackSlotKind::kStoreNamedStrict: case FeedbackSlotKind::kStoreOwnNamed: @@ -444,6 +447,7 @@ void FeedbackNexus::ConfigureUninitialized() { case FeedbackSlotKind::kStoreOwnNamed: case FeedbackSlotKind::kLoadProperty: case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kHasKeyed: case FeedbackSlotKind::kStoreDataPropertyInLiteral: { SetFeedback(*FeedbackVector::UninitializedSentinel(isolate), SKIP_WRITE_BARRIER); @@ -484,6 +488,7 @@ bool FeedbackNexus::Clear() { case FeedbackSlotKind::kStoreOwnNamed: case FeedbackSlotKind::kLoadProperty: case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kHasKeyed: case FeedbackSlotKind::kStoreGlobalSloppy: case FeedbackSlotKind::kStoreGlobalStrict: case FeedbackSlotKind::kLoadGlobalNotInsideTypeof: @@ -594,7 +599,8 @@ InlineCacheState FeedbackNexus::ic_state() const { case FeedbackSlotKind::kStoreInArrayLiteral: case FeedbackSlotKind::kStoreOwnNamed: case FeedbackSlotKind::kLoadProperty: - case FeedbackSlotKind::kLoadKeyed: { + case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kHasKeyed: { if (feedback == MaybeObject::FromObject( *FeedbackVector::UninitializedSentinel(isolate))) { return UNINITIALIZED; @@ -619,7 +625,8 @@ InlineCacheState FeedbackNexus::ic_state() const { return POLYMORPHIC; } if (heap_object->IsName()) { - DCHECK(IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind())); + DCHECK(IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) || + IsKeyedHasICKind(kind())); Object extra = GetFeedbackExtra()->GetHeapObjectAssumeStrong(); WeakFixedArray extra_array = WeakFixedArray::cast(extra); return extra_array->length() > 2 ? POLYMORPHIC : MONOMORPHIC; @@ -926,7 +933,7 @@ int FeedbackNexus::ExtractMaps(MapHandles* maps) const { DCHECK(IsLoadICKind(kind()) || IsStoreICKind(kind()) || IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) || IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind()) || - IsStoreInArrayLiteralICKind(kind())); + IsStoreInArrayLiteralICKind(kind()) || IsKeyedHasICKind(kind())); Isolate* isolate = GetIsolate(); MaybeObject feedback = GetFeedback(); @@ -974,7 +981,8 @@ int FeedbackNexus::ExtractMaps(MapHandles* maps) const { MaybeObjectHandle FeedbackNexus::FindHandlerForMap(Handle map) const { DCHECK(IsLoadICKind(kind()) || IsStoreICKind(kind()) || IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) || - IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind())); + IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind()) || + IsKeyedHasICKind(kind())); MaybeObject feedback = GetFeedback(); Isolate* isolate = GetIsolate(); @@ -1020,7 +1028,7 @@ bool FeedbackNexus::FindHandlers(MaybeObjectHandles* code_list, DCHECK(IsLoadICKind(kind()) || IsStoreICKind(kind()) || IsKeyedLoadICKind(kind()) || IsKeyedStoreICKind(kind()) || IsStoreOwnICKind(kind()) || IsStoreDataPropertyInLiteralKind(kind()) || - IsStoreInArrayLiteralICKind(kind())); + IsStoreInArrayLiteralICKind(kind()) || IsKeyedHasICKind(kind())); MaybeObject feedback = GetFeedback(); Isolate* isolate = GetIsolate(); @@ -1062,7 +1070,8 @@ bool FeedbackNexus::FindHandlers(MaybeObjectHandles* code_list, } Name FeedbackNexus::GetName() const { - if (IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind())) { + if (IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind()) || + IsKeyedHasICKind(kind())) { MaybeObject feedback = GetFeedback(); if (IsPropertyNameFeedback(feedback)) { return Name::cast(feedback->GetHeapObjectAssumeStrong()); @@ -1196,7 +1205,7 @@ KeyedAccessStoreMode FeedbackNexus::GetKeyedAccessStoreMode() const { IcCheckType FeedbackNexus::GetKeyType() const { DCHECK(IsKeyedStoreICKind(kind()) || IsKeyedLoadICKind(kind()) || - IsStoreInArrayLiteralICKind(kind())); + IsStoreInArrayLiteralICKind(kind()) || IsKeyedHasICKind(kind())); MaybeObject feedback = GetFeedback(); if (feedback == MaybeObject::FromObject( *FeedbackVector::MegamorphicSentinel(GetIsolate()))) { diff --git a/src/feedback-vector.h b/src/feedback-vector.h index 0b84b497af..f19aea9906 100644 --- a/src/feedback-vector.h +++ b/src/feedback-vector.h @@ -40,6 +40,7 @@ enum class FeedbackSlotKind { kLoadGlobalNotInsideTypeof, kLoadGlobalInsideTypeof, kLoadKeyed, + kHasKeyed, kStoreGlobalStrict, kStoreNamedStrict, kStoreOwnNamed, @@ -75,6 +76,10 @@ inline bool IsKeyedLoadICKind(FeedbackSlotKind kind) { return kind == FeedbackSlotKind::kLoadKeyed; } +inline bool IsKeyedHasICKind(FeedbackSlotKind kind) { + return kind == FeedbackSlotKind::kHasKeyed; +} + inline bool IsStoreGlobalICKind(FeedbackSlotKind kind) { return kind == FeedbackSlotKind::kStoreGlobalSloppy || kind == FeedbackSlotKind::kStoreGlobalStrict; @@ -348,6 +353,10 @@ class V8_EXPORT_PRIVATE FeedbackVectorSpec { return AddSlot(FeedbackSlotKind::kLoadKeyed); } + FeedbackSlot AddKeyedHasICSlot() { + return AddSlot(FeedbackSlotKind::kHasKeyed); + } + FeedbackSlotKind GetStoreICSlot(LanguageMode language_mode) { STATIC_ASSERT(LanguageModeSize == 2); return is_strict(language_mode) ? FeedbackSlotKind::kStoreNamedStrict diff --git a/src/heap/object-stats.cc b/src/heap/object-stats.cc index 10fd67e907..affd574ba2 100644 --- a/src/heap/object-stats.cc +++ b/src/heap/object-stats.cc @@ -578,6 +578,7 @@ static ObjectStats::VirtualInstanceType GetFeedbackSlotType( case FeedbackSlotKind::kLoadGlobalInsideTypeof: case FeedbackSlotKind::kLoadGlobalNotInsideTypeof: case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kHasKeyed: if (obj == *isolate->factory()->uninitialized_symbol() || obj == *isolate->factory()->premonomorphic_symbol()) { return ObjectStats::FEEDBACK_VECTOR_SLOT_LOAD_UNUSED_TYPE; diff --git a/src/ic/accessor-assembler.cc b/src/ic/accessor-assembler.cc index 2f45ad5f19..c1d7b6e40c 100644 --- a/src/ic/accessor-assembler.cc +++ b/src/ic/accessor-assembler.cc @@ -140,7 +140,7 @@ void AccessorAssembler::HandlePolymorphicCase( void AccessorAssembler::HandleLoadICHandlerCase( const LoadICParameters* p, TNode handler, Label* miss, ExitPoint* exit_point, ICMode ic_mode, OnNonExistent on_nonexistent, - ElementSupport support_elements) { + ElementSupport support_elements, LoadAccessMode access_mode) { Comment("have_handler"); VARIABLE(var_holder, MachineRepresentation::kTagged, p->holder); @@ -159,14 +159,15 @@ void AccessorAssembler::HandleLoadICHandlerCase( { HandleLoadICSmiHandlerCase(p, var_holder.value(), var_smi_handler.value(), handler, miss, exit_point, on_nonexistent, - support_elements); + support_elements, access_mode); } BIND(&try_proto_handler); { GotoIf(IsCodeMap(LoadMap(CAST(handler))), &call_handler); HandleLoadICProtoHandler(p, handler, &var_holder, &var_smi_handler, - &if_smi_handler, miss, exit_point, ic_mode); + &if_smi_handler, miss, exit_point, ic_mode, + access_mode); } BIND(&call_handler); @@ -307,7 +308,8 @@ TNode AccessorAssembler::LoadDescriptorValueOrFieldType( void AccessorAssembler::HandleLoadICSmiHandlerCase( const LoadICParameters* p, Node* holder, SloppyTNode smi_handler, SloppyTNode handler, Label* miss, ExitPoint* exit_point, - OnNonExistent on_nonexistent, ElementSupport support_elements) { + OnNonExistent on_nonexistent, ElementSupport support_elements, + LoadAccessMode access_mode) { VARIABLE(var_double_value, MachineRepresentation::kFloat64); Label rebox_double(this, &var_double_value); @@ -318,8 +320,17 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase( Label if_element(this), if_indexed_string(this), if_property(this); GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kElement)), &if_element); - Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)), - &if_indexed_string, &if_property); + + if (access_mode == LoadAccessMode::kHas) { + CSA_ASSERT(this, + WordNotEqual(handler_kind, + IntPtrConstant(LoadHandler::kIndexedString))); + Goto(&if_property); + } else { + Branch( + WordEqual(handler_kind, IntPtrConstant(LoadHandler::kIndexedString)), + &if_indexed_string, &if_property); + } BIND(&if_element); Comment("element_load"); @@ -334,7 +345,7 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase( EmitElementLoad(holder, elements, elements_kind, intptr_index, is_jsarray_condition, &if_hole, &rebox_double, &var_double_value, &unimplemented_elements_kind, &if_oob, - miss, exit_point); + miss, exit_point, access_mode); BIND(&unimplemented_elements_kind); { @@ -368,41 +379,65 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase( miss); BIND(&return_undefined); - exit_point->Return(UndefinedConstant()); + exit_point->Return(access_mode == LoadAccessMode::kHas + ? FalseConstant() + : UndefinedConstant()); } BIND(&if_hole); { Comment("convert hole"); - GotoIfNot(IsSetWord(handler_word), miss); + + if (access_mode != LoadAccessMode::kHas) { + GotoIfNot(IsSetWord(handler_word), miss); + } GotoIf(IsNoElementsProtectorCellInvalid(), miss); - exit_point->Return(UndefinedConstant()); + exit_point->Return(access_mode == LoadAccessMode::kHas + ? FalseConstant() + : UndefinedConstant()); } - BIND(&if_indexed_string); - { - Label if_oob(this, Label::kDeferred); + if (access_mode != LoadAccessMode::kHas) { + BIND(&if_indexed_string); + { + Label if_oob(this, Label::kDeferred); - Comment("indexed string"); - Node* intptr_index = TryToIntptr(p->name, miss); - Node* length = LoadStringLengthAsWord(holder); - GotoIf(UintPtrGreaterThanOrEqual(intptr_index, length), &if_oob); - TNode code = StringCharCodeAt(holder, intptr_index); - TNode result = StringFromSingleCharCode(code); - Return(result); + Comment("indexed string"); + Node* intptr_index = TryToIntptr(p->name, miss); + Node* length = LoadStringLengthAsWord(holder); + GotoIf(UintPtrGreaterThanOrEqual(intptr_index, length), &if_oob); + TNode code = StringCharCodeAt(holder, intptr_index); + TNode result = StringFromSingleCharCode(code); + Return(result); - BIND(&if_oob); - Node* allow_out_of_bounds = - IsSetWord(handler_word); - GotoIfNot(allow_out_of_bounds, miss); - GotoIf(IsNoElementsProtectorCellInvalid(), miss); - Return(UndefinedConstant()); + BIND(&if_oob); + Node* allow_out_of_bounds = + IsSetWord(handler_word); + GotoIfNot(allow_out_of_bounds, miss); + GotoIf(IsNoElementsProtectorCellInvalid(), miss); + Return(UndefinedConstant()); + } } BIND(&if_property); Comment("property_load"); } + if (access_mode == LoadAccessMode::kHas) { + HandleLoadICSmiHandlerHasNamedCase(p, holder, handler_kind, miss, + exit_point); + } else { + HandleLoadICSmiHandlerLoadNamedCase( + p, holder, handler_kind, handler_word, &rebox_double, &var_double_value, + handler, miss, exit_point, on_nonexistent, support_elements); + } +} + +void AccessorAssembler::HandleLoadICSmiHandlerLoadNamedCase( + const LoadICParameters* p, Node* holder, TNode handler_kind, + TNode handler_word, Label* rebox_double, Variable* var_double_value, + SloppyTNode handler, Label* miss, ExitPoint* exit_point, + OnNonExistent on_nonexistent, ElementSupport support_elements) { Label constant(this), field(this), normal(this, Label::kDeferred), interceptor(this, Label::kDeferred), nonexistent(this), accessor(this, Label::kDeferred), global(this, Label::kDeferred), @@ -442,7 +477,7 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase( &module_export, &interceptor); BIND(&field); - HandleLoadField(holder, handler_word, &var_double_value, &rebox_double, + HandleLoadField(holder, handler_word, var_double_value, rebox_double, exit_point); BIND(&nonexistent); @@ -589,8 +624,88 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase( } } - BIND(&rebox_double); - exit_point->Return(AllocateHeapNumberWithValue(var_double_value.value())); + BIND(rebox_double); + exit_point->Return(AllocateHeapNumberWithValue(var_double_value->value())); +} + +void AccessorAssembler::HandleLoadICSmiHandlerHasNamedCase( + const LoadICParameters* p, Node* holder, TNode handler_kind, + Label* miss, ExitPoint* exit_point) { + Label return_true(this), return_false(this), return_lookup(this), + normal(this), global(this); + + GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), + &return_true); + + GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstant)), + &return_true); + + GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNonExistent)), + &return_false); + + GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNormal)), + &normal); + + GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kAccessor)), + &return_true); + + GotoIf( + WordEqual(handler_kind, IntPtrConstant(LoadHandler::kNativeDataProperty)), + &return_true); + + GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kApiGetter)), + &return_true); + + GotoIf(WordEqual(handler_kind, + IntPtrConstant(LoadHandler::kApiGetterHolderIsPrototype)), + &return_true); + + Branch(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kGlobal)), &global, + &return_lookup); + + BIND(&return_true); + exit_point->Return(TrueConstant()); + + BIND(&return_false); + exit_point->Return(FalseConstant()); + + BIND(&return_lookup); + { + CSA_ASSERT( + this, + Word32Or( + WordEqual(handler_kind, IntPtrConstant(LoadHandler::kInterceptor)), + Word32Or( + WordEqual(handler_kind, IntPtrConstant(LoadHandler::kProxy)), + WordEqual(handler_kind, + IntPtrConstant(LoadHandler::kModuleExport))))); + exit_point->ReturnCallStub( + Builtins::CallableFor(isolate(), Builtins::kHasProperty), p->context, + p->receiver, p->name); + } + + BIND(&normal); + { + Comment("has_normal"); + TNode properties = CAST(LoadSlowProperties(holder)); + TVARIABLE(IntPtrT, var_name_index); + Label found(this); + NameDictionaryLookup(properties, CAST(p->name), &found, + &var_name_index, miss); + + BIND(&found); + exit_point->Return(TrueConstant()); + } + + BIND(&global); + { + CSA_ASSERT(this, IsPropertyCell(holder)); + // Ensure the property cell doesn't contain the hole. + Node* value = LoadObjectField(holder, PropertyCell::kValueOffset); + GotoIf(IsTheHole(value), miss); + + exit_point->Return(TrueConstant()); + } } // Performs actions common to both load and store handlers: @@ -707,7 +822,7 @@ Node* AccessorAssembler::HandleProtoHandler( void AccessorAssembler::HandleLoadICProtoHandler( const LoadICParameters* p, Node* handler, Variable* var_holder, Variable* var_smi_handler, Label* if_smi_handler, Label* miss, - ExitPoint* exit_point, ICMode ic_mode) { + ExitPoint* exit_point, ICMode ic_mode, LoadAccessMode access_mode) { DCHECK_EQ(MachineRepresentation::kTagged, var_holder->rep()); DCHECK_EQ(MachineRepresentation::kTagged, var_smi_handler->rep()); @@ -717,14 +832,18 @@ void AccessorAssembler::HandleLoadICProtoHandler( nullptr, // on_found_on_receiver [=](Node* properties, Node* name_index) { - VARIABLE(var_details, MachineRepresentation::kWord32); - VARIABLE(var_value, MachineRepresentation::kTagged); - LoadPropertyFromNameDictionary(properties, name_index, &var_details, - &var_value); - Node* value = - CallGetterIfAccessor(var_value.value(), var_details.value(), - p->context, p->receiver, miss); - exit_point->Return(value); + if (access_mode == LoadAccessMode::kHas) { + exit_point->Return(TrueConstant()); + } else { + VARIABLE(var_details, MachineRepresentation::kWord32); + VARIABLE(var_value, MachineRepresentation::kTagged); + LoadPropertyFromNameDictionary(properties, name_index, &var_details, + &var_value); + Node* value = + CallGetterIfAccessor(var_value.value(), var_details.value(), + p->context, p->receiver, miss); + exit_point->Return(value); + } }, miss, ic_mode); @@ -1764,7 +1883,7 @@ void AccessorAssembler::EmitElementLoad( SloppyTNode intptr_index, Node* is_jsarray_condition, Label* if_hole, Label* rebox_double, Variable* var_double_value, Label* unimplemented_elements_kind, Label* out_of_bounds, Label* miss, - ExitPoint* exit_point) { + ExitPoint* exit_point, LoadAccessMode access_mode) { Label if_typed_array(this), if_fast_packed(this), if_fast_holey(this), if_fast_double(this), if_fast_holey_double(this), if_nonfast(this), if_dictionary(this); @@ -1797,7 +1916,9 @@ void AccessorAssembler::EmitElementLoad( { Comment("fast packed elements"); exit_point->Return( - UnsafeLoadFixedArrayElement(CAST(elements), intptr_index)); + access_mode == LoadAccessMode::kHas + ? TrueConstant() + : UnsafeLoadFixedArrayElement(CAST(elements), intptr_index)); } BIND(&if_fast_holey); @@ -1805,15 +1926,20 @@ void AccessorAssembler::EmitElementLoad( Comment("fast holey elements"); Node* element = UnsafeLoadFixedArrayElement(CAST(elements), intptr_index); GotoIf(WordEqual(element, TheHoleConstant()), if_hole); - exit_point->Return(element); + exit_point->Return(access_mode == LoadAccessMode::kHas ? TrueConstant() + : element); } BIND(&if_fast_double); { Comment("packed double elements"); - var_double_value->Bind(LoadFixedDoubleArrayElement(elements, intptr_index, - MachineType::Float64())); - Goto(rebox_double); + if (access_mode == LoadAccessMode::kHas) { + exit_point->Return(TrueConstant()); + } else { + var_double_value->Bind(LoadFixedDoubleArrayElement( + elements, intptr_index, MachineType::Float64())); + Goto(rebox_double); + } } BIND(&if_fast_holey_double); @@ -1822,8 +1948,12 @@ void AccessorAssembler::EmitElementLoad( Node* value = LoadFixedDoubleArrayElement(elements, intptr_index, MachineType::Float64(), 0, INTPTR_PARAMETERS, if_hole); - var_double_value->Bind(value); - Goto(rebox_double); + if (access_mode == LoadAccessMode::kHas) { + exit_point->Return(TrueConstant()); + } else { + var_double_value->Bind(value); + Goto(rebox_double); + } } BIND(&if_nonfast); @@ -1845,7 +1975,8 @@ void AccessorAssembler::EmitElementLoad( TNode value = BasicLoadNumberDictionaryElement( CAST(elements), intptr_index, miss, if_hole); - exit_point->Return(value); + exit_point->Return(access_mode == LoadAccessMode::kHas ? TrueConstant() + : value); } BIND(&if_typed_array); @@ -1858,97 +1989,101 @@ void AccessorAssembler::EmitElementLoad( // Bounds check. Node* length = SmiUntag(LoadJSTypedArrayLength(CAST(object))); GotoIfNot(UintPtrLessThan(intptr_index, length), out_of_bounds); + if (access_mode == LoadAccessMode::kHas) { + exit_point->Return(TrueConstant()); + } else { + Node* backing_store = LoadFixedTypedArrayBackingStore(CAST(elements)); - Node* backing_store = LoadFixedTypedArrayBackingStore(CAST(elements)); - - Label uint8_elements(this), int8_elements(this), uint16_elements(this), - int16_elements(this), uint32_elements(this), int32_elements(this), - float32_elements(this), float64_elements(this), bigint64_elements(this), - biguint64_elements(this); - Label* elements_kind_labels[] = { - &uint8_elements, &uint8_elements, &int8_elements, - &uint16_elements, &int16_elements, &uint32_elements, - &int32_elements, &float32_elements, &float64_elements, - &bigint64_elements, &biguint64_elements}; - int32_t elements_kinds[] = { - UINT8_ELEMENTS, UINT8_CLAMPED_ELEMENTS, INT8_ELEMENTS, - UINT16_ELEMENTS, INT16_ELEMENTS, UINT32_ELEMENTS, - INT32_ELEMENTS, FLOAT32_ELEMENTS, FLOAT64_ELEMENTS, - BIGINT64_ELEMENTS, BIGUINT64_ELEMENTS}; - const size_t kTypedElementsKindCount = - LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND - - FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + 1; - DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kinds)); - DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kind_labels)); - Switch(elements_kind, miss, elements_kinds, elements_kind_labels, - kTypedElementsKindCount); - BIND(&uint8_elements); - { - Comment("UINT8_ELEMENTS"); // Handles UINT8_CLAMPED_ELEMENTS too. - Node* element = Load(MachineType::Uint8(), backing_store, intptr_index); - exit_point->Return(SmiFromInt32(element)); - } - BIND(&int8_elements); - { - Comment("INT8_ELEMENTS"); - Node* element = Load(MachineType::Int8(), backing_store, intptr_index); - exit_point->Return(SmiFromInt32(element)); - } - BIND(&uint16_elements); - { - Comment("UINT16_ELEMENTS"); - Node* index = WordShl(intptr_index, IntPtrConstant(1)); - Node* element = Load(MachineType::Uint16(), backing_store, index); - exit_point->Return(SmiFromInt32(element)); - } - BIND(&int16_elements); - { - Comment("INT16_ELEMENTS"); - Node* index = WordShl(intptr_index, IntPtrConstant(1)); - Node* element = Load(MachineType::Int16(), backing_store, index); - exit_point->Return(SmiFromInt32(element)); - } - BIND(&uint32_elements); - { - Comment("UINT32_ELEMENTS"); - Node* index = WordShl(intptr_index, IntPtrConstant(2)); - Node* element = Load(MachineType::Uint32(), backing_store, index); - exit_point->Return(ChangeUint32ToTagged(element)); - } - BIND(&int32_elements); - { - Comment("INT32_ELEMENTS"); - Node* index = WordShl(intptr_index, IntPtrConstant(2)); - Node* element = Load(MachineType::Int32(), backing_store, index); - exit_point->Return(ChangeInt32ToTagged(element)); - } - BIND(&float32_elements); - { - Comment("FLOAT32_ELEMENTS"); - Node* index = WordShl(intptr_index, IntPtrConstant(2)); - Node* element = Load(MachineType::Float32(), backing_store, index); - var_double_value->Bind(ChangeFloat32ToFloat64(element)); - Goto(rebox_double); - } - BIND(&float64_elements); - { - Comment("FLOAT64_ELEMENTS"); - Node* index = WordShl(intptr_index, IntPtrConstant(3)); - Node* element = Load(MachineType::Float64(), backing_store, index); - var_double_value->Bind(element); - Goto(rebox_double); - } - BIND(&bigint64_elements); - { - Comment("BIGINT64_ELEMENTS"); - exit_point->Return(LoadFixedTypedArrayElementAsTagged( - backing_store, intptr_index, BIGINT64_ELEMENTS, INTPTR_PARAMETERS)); - } - BIND(&biguint64_elements); - { - Comment("BIGUINT64_ELEMENTS"); - exit_point->Return(LoadFixedTypedArrayElementAsTagged( - backing_store, intptr_index, BIGUINT64_ELEMENTS, INTPTR_PARAMETERS)); + Label uint8_elements(this), int8_elements(this), uint16_elements(this), + int16_elements(this), uint32_elements(this), int32_elements(this), + float32_elements(this), float64_elements(this), + bigint64_elements(this), biguint64_elements(this); + Label* elements_kind_labels[] = { + &uint8_elements, &uint8_elements, &int8_elements, + &uint16_elements, &int16_elements, &uint32_elements, + &int32_elements, &float32_elements, &float64_elements, + &bigint64_elements, &biguint64_elements}; + int32_t elements_kinds[] = { + UINT8_ELEMENTS, UINT8_CLAMPED_ELEMENTS, INT8_ELEMENTS, + UINT16_ELEMENTS, INT16_ELEMENTS, UINT32_ELEMENTS, + INT32_ELEMENTS, FLOAT32_ELEMENTS, FLOAT64_ELEMENTS, + BIGINT64_ELEMENTS, BIGUINT64_ELEMENTS}; + const size_t kTypedElementsKindCount = + LAST_FIXED_TYPED_ARRAY_ELEMENTS_KIND - + FIRST_FIXED_TYPED_ARRAY_ELEMENTS_KIND + 1; + DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kinds)); + DCHECK_EQ(kTypedElementsKindCount, arraysize(elements_kind_labels)); + Switch(elements_kind, miss, elements_kinds, elements_kind_labels, + kTypedElementsKindCount); + BIND(&uint8_elements); + { + Comment("UINT8_ELEMENTS"); // Handles UINT8_CLAMPED_ELEMENTS too. + Node* element = Load(MachineType::Uint8(), backing_store, intptr_index); + exit_point->Return(SmiFromInt32(element)); + } + BIND(&int8_elements); + { + Comment("INT8_ELEMENTS"); + Node* element = Load(MachineType::Int8(), backing_store, intptr_index); + exit_point->Return(SmiFromInt32(element)); + } + BIND(&uint16_elements); + { + Comment("UINT16_ELEMENTS"); + Node* index = WordShl(intptr_index, IntPtrConstant(1)); + Node* element = Load(MachineType::Uint16(), backing_store, index); + exit_point->Return(SmiFromInt32(element)); + } + BIND(&int16_elements); + { + Comment("INT16_ELEMENTS"); + Node* index = WordShl(intptr_index, IntPtrConstant(1)); + Node* element = Load(MachineType::Int16(), backing_store, index); + exit_point->Return(SmiFromInt32(element)); + } + BIND(&uint32_elements); + { + Comment("UINT32_ELEMENTS"); + Node* index = WordShl(intptr_index, IntPtrConstant(2)); + Node* element = Load(MachineType::Uint32(), backing_store, index); + exit_point->Return(ChangeUint32ToTagged(element)); + } + BIND(&int32_elements); + { + Comment("INT32_ELEMENTS"); + Node* index = WordShl(intptr_index, IntPtrConstant(2)); + Node* element = Load(MachineType::Int32(), backing_store, index); + exit_point->Return(ChangeInt32ToTagged(element)); + } + BIND(&float32_elements); + { + Comment("FLOAT32_ELEMENTS"); + Node* index = WordShl(intptr_index, IntPtrConstant(2)); + Node* element = Load(MachineType::Float32(), backing_store, index); + var_double_value->Bind(ChangeFloat32ToFloat64(element)); + Goto(rebox_double); + } + BIND(&float64_elements); + { + Comment("FLOAT64_ELEMENTS"); + Node* index = WordShl(intptr_index, IntPtrConstant(3)); + Node* element = Load(MachineType::Float64(), backing_store, index); + var_double_value->Bind(element); + Goto(rebox_double); + } + BIND(&bigint64_elements); + { + Comment("BIGINT64_ELEMENTS"); + exit_point->Return(LoadFixedTypedArrayElementAsTagged( + backing_store, intptr_index, BIGINT64_ELEMENTS, INTPTR_PARAMETERS)); + } + BIND(&biguint64_elements); + { + Comment("BIGUINT64_ELEMENTS"); + exit_point->Return(LoadFixedTypedArrayElementAsTagged( + backing_store, intptr_index, BIGUINT64_ELEMENTS, + INTPTR_PARAMETERS)); + } } } } @@ -2661,7 +2796,8 @@ void AccessorAssembler::LoadGlobalIC_TryHandlerCase( on_nonexistent); } -void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) { +void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p, + LoadAccessMode access_mode) { ExitPoint direct_exit(this); TVARIABLE(MaybeObject, var_handler); @@ -2681,9 +2817,9 @@ void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) { &var_handler, &try_polymorphic); BIND(&if_handler); { - HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit, - ICMode::kNonGlobalIC, - OnNonExistent::kReturnUndefined, kSupportElements); + HandleLoadICHandlerCase( + p, CAST(var_handler.value()), &miss, &direct_exit, ICMode::kNonGlobalIC, + OnNonExistent::kReturnUndefined, kSupportElements, access_mode); } BIND(&try_polymorphic); @@ -2707,8 +2843,10 @@ void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) { BIND(&generic); { // TODO(jkummerow): Inline this? Or some of it? - TailCallBuiltin(Builtins::kKeyedLoadIC_Megamorphic, p->context, p->receiver, - p->name, p->slot, p->vector); + TailCallBuiltin(access_mode == LoadAccessMode::kLoad + ? Builtins::kKeyedLoadIC_Megamorphic + : Builtins::kKeyedHasIC_Megamorphic, + p->context, p->receiver, p->name, p->slot, p->vector); } BIND(&try_polymorphic_name); @@ -2754,16 +2892,20 @@ void AccessorAssembler::KeyedLoadIC(const LoadICParameters* p) { // If the name comparison succeeded, we know we have a weak fixed array // with at least one map/handler pair. Node* name = var_name.value(); - TailCallBuiltin(Builtins::kKeyedLoadIC_PolymorphicName, p->context, - p->receiver, name, p->slot, p->vector); + TailCallBuiltin(access_mode == LoadAccessMode::kLoad + ? Builtins::kKeyedLoadIC_PolymorphicName + : Builtins::kKeyedHasIC_PolymorphicName, + p->context, p->receiver, name, p->slot, p->vector); } } BIND(&miss); { Comment("KeyedLoadIC_miss"); - TailCallRuntime(Runtime::kKeyedLoadIC_Miss, p->context, p->receiver, - p->name, p->slot, p->vector); + TailCallRuntime(access_mode == LoadAccessMode::kLoad + ? Runtime::kKeyedLoadIC_Miss + : Runtime::kKeyedHasIC_Miss, + p->context, p->receiver, p->name, p->slot, p->vector); } } @@ -2846,7 +2988,8 @@ void AccessorAssembler::KeyedLoadICGeneric(const LoadICParameters* p) { } } -void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p) { +void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p, + LoadAccessMode access_mode) { TVARIABLE(MaybeObject, var_handler); Label if_handler(this, &var_handler), miss(this, Label::kDeferred); @@ -2874,16 +3017,18 @@ void AccessorAssembler::KeyedLoadICPolymorphicName(const LoadICParameters* p) { BIND(&if_handler); { ExitPoint direct_exit(this); - HandleLoadICHandlerCase(p, CAST(var_handler.value()), &miss, &direct_exit, - ICMode::kNonGlobalIC, - OnNonExistent::kReturnUndefined, kOnlyProperties); + HandleLoadICHandlerCase( + p, CAST(var_handler.value()), &miss, &direct_exit, ICMode::kNonGlobalIC, + OnNonExistent::kReturnUndefined, kOnlyProperties, access_mode); } BIND(&miss); { Comment("KeyedLoadIC_miss"); - TailCallRuntime(Runtime::kKeyedLoadIC_Miss, context, receiver, name, slot, - vector); + TailCallRuntime(access_mode == LoadAccessMode::kLoad + ? Runtime::kKeyedLoadIC_Miss + : Runtime::kKeyedHasIC_Miss, + context, receiver, name, slot, vector); } } @@ -3380,7 +3525,7 @@ void AccessorAssembler::GenerateKeyedLoadIC() { Node* context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); - KeyedLoadIC(&p); + KeyedLoadIC(&p, LoadAccessMode::kLoad); } void AccessorAssembler::GenerateKeyedLoadIC_Megamorphic() { @@ -3432,7 +3577,7 @@ void AccessorAssembler::GenerateKeyedLoadIC_PolymorphicName() { Node* context = Parameter(Descriptor::kContext); LoadICParameters p(context, receiver, name, slot, vector); - KeyedLoadICPolymorphicName(&p); + KeyedLoadICPolymorphicName(&p, LoadAccessMode::kLoad); } void AccessorAssembler::GenerateStoreGlobalIC() { @@ -3754,5 +3899,42 @@ void AccessorAssembler::GenerateCloneObjectIC() { } } +void AccessorAssembler::GenerateKeyedHasIC() { + typedef LoadWithVectorDescriptor Descriptor; + + Node* receiver = Parameter(Descriptor::kReceiver); + Node* name = Parameter(Descriptor::kName); + Node* slot = Parameter(Descriptor::kSlot); + Node* vector = Parameter(Descriptor::kVector); + Node* context = Parameter(Descriptor::kContext); + + LoadICParameters p(context, receiver, name, slot, vector); + KeyedLoadIC(&p, LoadAccessMode::kHas); +} + +void AccessorAssembler::GenerateKeyedHasIC_Megamorphic() { + typedef LoadWithVectorDescriptor Descriptor; + + Node* receiver = Parameter(Descriptor::kReceiver); + Node* name = Parameter(Descriptor::kName); + Node* context = Parameter(Descriptor::kContext); + // TODO(magardn): implement HasProperty handling in KeyedLoadICGeneric + Return(HasProperty(context, receiver, name, + HasPropertyLookupMode::kHasProperty)); +} + +void AccessorAssembler::GenerateKeyedHasIC_PolymorphicName() { + typedef LoadWithVectorDescriptor Descriptor; + + Node* receiver = Parameter(Descriptor::kReceiver); + Node* name = Parameter(Descriptor::kName); + Node* slot = Parameter(Descriptor::kSlot); + Node* vector = Parameter(Descriptor::kVector); + Node* context = Parameter(Descriptor::kContext); + + LoadICParameters p(context, receiver, name, slot, vector); + KeyedLoadICPolymorphicName(&p, LoadAccessMode::kHas); +} + } // namespace internal } // namespace v8 diff --git a/src/ic/accessor-assembler.h b/src/ic/accessor-assembler.h index 039c4ac625..beed6f655f 100644 --- a/src/ic/accessor-assembler.h +++ b/src/ic/accessor-assembler.h @@ -44,6 +44,9 @@ class AccessorAssembler : public CodeStubAssembler { void GenerateStoreGlobalICTrampoline(); void GenerateCloneObjectIC(); void GenerateCloneObjectIC_Slow(); + void GenerateKeyedHasIC(); + void GenerateKeyedHasIC_Megamorphic(); + void GenerateKeyedHasIC_PolymorphicName(); void GenerateLoadGlobalIC(TypeofMode typeof_mode); void GenerateLoadGlobalICTrampoline(TypeofMode typeof_mode); @@ -105,6 +108,7 @@ class AccessorAssembler : public CodeStubAssembler { SloppyTNode value; }; + enum class LoadAccessMode { kLoad, kHas }; enum class ICMode { kNonGlobalIC, kGlobalIC }; enum ElementSupport { kOnlyProperties, kSupportElements }; void HandleStoreICHandlerCase( @@ -153,9 +157,10 @@ class AccessorAssembler : public CodeStubAssembler { void LoadIC_Uninitialized(const LoadICParameters* p); - void KeyedLoadIC(const LoadICParameters* p); + void KeyedLoadIC(const LoadICParameters* p, LoadAccessMode access_mode); void KeyedLoadICGeneric(const LoadICParameters* p); - void KeyedLoadICPolymorphicName(const LoadICParameters* p); + void KeyedLoadICPolymorphicName(const LoadICParameters* p, + LoadAccessMode access_mode); void StoreIC(const StoreICParameters* p); void StoreGlobalIC(const StoreICParameters* p); void StoreGlobalIC_PropertyCellCase(Node* property_cell, Node* value, @@ -180,19 +185,22 @@ class AccessorAssembler : public CodeStubAssembler { const LoadICParameters* p, TNode handler, Label* miss, ExitPoint* exit_point, ICMode ic_mode = ICMode::kNonGlobalIC, OnNonExistent on_nonexistent = OnNonExistent::kReturnUndefined, - ElementSupport support_elements = kOnlyProperties); + ElementSupport support_elements = kOnlyProperties, + LoadAccessMode access_mode = LoadAccessMode::kLoad); void HandleLoadICSmiHandlerCase(const LoadICParameters* p, Node* holder, SloppyTNode smi_handler, SloppyTNode handler, Label* miss, ExitPoint* exit_point, OnNonExistent on_nonexistent, - ElementSupport support_elements); + ElementSupport support_elements, + LoadAccessMode access_mode); void HandleLoadICProtoHandler(const LoadICParameters* p, Node* handler, Variable* var_holder, Variable* var_smi_handler, Label* if_smi_handler, Label* miss, - ExitPoint* exit_point, ICMode ic_mode); + ExitPoint* exit_point, ICMode ic_mode, + LoadAccessMode access_mode); void HandleLoadCallbackProperty(const LoadICParameters* p, TNode holder, @@ -211,6 +219,18 @@ class AccessorAssembler : public CodeStubAssembler { void EmitAccessCheck(Node* expected_native_context, Node* context, Node* receiver, Label* can_access, Label* miss); + void HandleLoadICSmiHandlerLoadNamedCase( + const LoadICParameters* p, Node* holder, TNode handler_kind, + TNode handler_word, Label* rebox_double, + Variable* var_double_value, SloppyTNode handler, Label* miss, + ExitPoint* exit_point, OnNonExistent on_nonexistent, + ElementSupport support_elements); + + void HandleLoadICSmiHandlerHasNamedCase(const LoadICParameters* p, + Node* holder, + TNode handler_kind, + Label* miss, ExitPoint* exit_point); + // LoadGlobalIC implementation. void LoadGlobalIC_TryPropertyCellCase( @@ -296,7 +316,8 @@ class AccessorAssembler : public CodeStubAssembler { Label* if_hole, Label* rebox_double, Variable* var_double_value, Label* unimplemented_elements_kind, Label* out_of_bounds, - Label* miss, ExitPoint* exit_point); + Label* miss, ExitPoint* exit_point, + LoadAccessMode access_mode = LoadAccessMode::kLoad); void NameDictionaryNegativeLookup(Node* object, SloppyTNode name, Label* miss); TNode IsPropertyDetailsConst(Node* details); diff --git a/src/ic/ic.cc b/src/ic/ic.cc index d8cd858b91..b050513adb 100644 --- a/src/ic/ic.cc +++ b/src/ic/ic.cc @@ -216,7 +216,7 @@ JSFunction IC::GetHostFunction() const { return frame->function(); } -static void LookupForRead(Isolate* isolate, LookupIterator* it) { +static void LookupForRead(LookupIterator* it, bool is_has_property) { for (; it->IsFound(); it->Next()) { switch (it->state()) { case LookupIterator::NOT_FOUND: @@ -227,7 +227,13 @@ static void LookupForRead(Isolate* isolate, LookupIterator* it) { case LookupIterator::INTERCEPTOR: { // If there is a getter, return; otherwise loop to perform the lookup. Handle holder = it->GetHolder(); - if (!holder->GetNamedInterceptor()->getter()->IsUndefined(isolate)) { + if (!holder->GetNamedInterceptor()->getter()->IsUndefined( + it->isolate())) { + return; + } + if (is_has_property && + !holder->GetNamedInterceptor()->query()->IsUndefined( + it->isolate())) { return; } break; @@ -284,7 +290,6 @@ bool IC::RecomputeHandlerForName(Handle name) { return true; } - void IC::UpdateState(Handle receiver, Handle name) { if (state() == NO_FEEDBACK) return; update_receiver_map(receiver); @@ -306,7 +311,6 @@ MaybeHandle IC::TypeError(MessageTemplate index, Handle object, THROW_NEW_ERROR(isolate(), NewTypeError(index, key, object), Object); } - MaybeHandle IC::ReferenceError(Handle name) { HandleScope scope(isolate()); THROW_NEW_ERROR( @@ -429,7 +433,8 @@ MaybeHandle LoadIC::Load(Handle object, Handle name) { // If the object is undefined or null it's illegal to try to get any // of its properties; throw a TypeError in that case. - if (object->IsNullOrUndefined(isolate())) { + if (IsAnyHas() ? !object->IsJSReceiver() + : object->IsNullOrUndefined(isolate())) { if (use_ic && state() != PREMONOMORPHIC) { // Ensure the IC state progresses. TRACE_HANDLER_STATS(isolate(), LoadIC_NonReceiver); @@ -441,7 +446,9 @@ MaybeHandle LoadIC::Load(Handle object, Handle name) { if (*name == ReadOnlyRoots(isolate()).iterator_symbol()) { return Runtime::ThrowIteratorError(isolate(), object); } - return TypeError(MessageTemplate::kNonObjectPropertyLoad, object, name); + return TypeError(IsAnyHas() ? MessageTemplate::kInvalidInOperatorUse + : MessageTemplate::kNonObjectPropertyLoad, + object, name); } if (MigrateDeprecated(object)) use_ic = false; @@ -450,9 +457,11 @@ MaybeHandle LoadIC::Load(Handle object, Handle name) { JSObject::MakePrototypesFast(object, kStartAtReceiver, isolate()); update_receiver_map(object); } - // Named lookup in the object. + LookupIterator it(isolate(), object, name); - LookupForRead(isolate(), &it); + + // Named lookup in the object. + LookupForRead(&it, IsAnyHas()); if (name->IsPrivate()) { if (name->IsPrivateName() && !it.IsFound()) { @@ -473,6 +482,14 @@ MaybeHandle LoadIC::Load(Handle object, Handle name) { // Update inline cache and stub cache. if (use_ic) UpdateCaches(&it); + if (IsAnyHas()) { + // Named lookup in the object. + Maybe maybe = JSReceiver::HasProperty(&it); + if (maybe.IsNothing()) return MaybeHandle(); + return maybe.FromJust() ? ReadOnlyRoots(isolate()).true_value_handle() + : ReadOnlyRoots(isolate()).false_value_handle(); + } + // Get the property. Handle result; @@ -619,7 +636,6 @@ void IC::UpdateMonomorphicIC(const MaybeObjectHandle& handler, ConfigureVectorState(name, receiver_map(), handler); } - void IC::CopyICToMegamorphicCache(Handle name) { MapHandles maps; MaybeObjectHandles handlers; @@ -654,7 +670,7 @@ void IC::PatchCache(Handle name, Handle handler) { void IC::PatchCache(Handle name, const MaybeObjectHandle& handler) { DCHECK(IsHandler(*handler)); // Currently only load and store ICs support non-code handlers. - DCHECK(IsAnyLoad() || IsAnyStore()); + DCHECK(IsAnyLoad() || IsAnyStore() || IsAnyHas()); switch (state()) { case NO_FEEDBACK: break; @@ -728,6 +744,7 @@ void LoadIC::UpdateCaches(LookupIterator* lookup) { } StubCache* IC::stub_cache() { + DCHECK(!IsAnyHas()); if (IsAnyLoad()) { return isolate()->load_stub_cache(); } else { @@ -738,13 +755,15 @@ StubCache* IC::stub_cache() { void IC::UpdateMegamorphicCache(Handle map, Handle name, const MaybeObjectHandle& handler) { - stub_cache()->Set(*name, *map, *handler); + if (!IsAnyHas()) { + stub_cache()->Set(*name, *map, *handler); + } } void IC::TraceHandlerCacheHitStats(LookupIterator* lookup) { DCHECK_EQ(LookupIterator::ACCESSOR, lookup->state()); if (V8_LIKELY(!FLAG_runtime_stats)) return; - if (IsAnyLoad()) { + if (IsAnyLoad() || IsAnyHas()) { TRACE_HANDLER_STATS(isolate(), LoadIC_HandlerCacheHit_Accessor); } else { DCHECK(IsAnyStore()); @@ -755,22 +774,29 @@ void IC::TraceHandlerCacheHitStats(LookupIterator* lookup) { Handle LoadIC::ComputeHandler(LookupIterator* lookup) { Handle receiver = lookup->GetReceiver(); ReadOnlyRoots roots(isolate()); - if (receiver->IsString() && *lookup->name() == roots.length_string()) { - TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength); - return BUILTIN_CODE(isolate(), LoadIC_StringLength); - } - if (receiver->IsStringWrapper() && *lookup->name() == roots.length_string()) { - TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength); - return BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength); - } + // `in` cannot be called on strings, and will always return true for string + // wrapper length and function prototypes. The latter two cases are given + // LoadHandler::LoadNativeDataProperty below. + if (!IsAnyHas()) { + if (receiver->IsString() && *lookup->name() == roots.length_string()) { + TRACE_HANDLER_STATS(isolate(), LoadIC_StringLength); + return BUILTIN_CODE(isolate(), LoadIC_StringLength); + } - // Use specialized code for getting prototype of functions. - if (receiver->IsJSFunction() && *lookup->name() == roots.prototype_string() && - !JSFunction::cast(*receiver)->PrototypeRequiresRuntimeLookup()) { - Handle stub; - TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub); - return BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype); + if (receiver->IsStringWrapper() && + *lookup->name() == roots.length_string()) { + TRACE_HANDLER_STATS(isolate(), LoadIC_StringWrapperLength); + return BUILTIN_CODE(isolate(), LoadIC_StringWrapperLength); + } + + // Use specialized code for getting prototype of functions. + if (receiver->IsJSFunction() && + *lookup->name() == roots.prototype_string() && + !JSFunction::cast(*receiver)->PrototypeRequiresRuntimeLookup()) { + TRACE_HANDLER_STATS(isolate(), LoadIC_FunctionPrototypeStub); + return BUILTIN_CODE(isolate(), LoadIC_FunctionPrototype); + } } Handle map = receiver_map(); @@ -1118,22 +1144,30 @@ bool AllowConvertHoleElementToUndefined(Isolate* isolate, Handle KeyedLoadIC::LoadElementHandler(Handle receiver_map, KeyedAccessLoadMode load_mode) { + // Has a getter interceptor, or is any has and has a query interceptor. if (receiver_map->has_indexed_interceptor() && - !receiver_map->GetIndexedInterceptor()->getter()->IsUndefined( - isolate()) && + (!receiver_map->GetIndexedInterceptor()->getter()->IsUndefined( + isolate()) || + (IsAnyHas() && + !receiver_map->GetIndexedInterceptor()->query()->IsUndefined( + isolate()))) && !receiver_map->GetIndexedInterceptor()->non_masking()) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedInterceptorStub); - return BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC); + return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIndexedInterceptorIC) + : BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC); } + InstanceType instance_type = receiver_map->instance_type(); if (instance_type < FIRST_NONSTRING_TYPE) { TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedStringDH); + if (IsAnyHas()) return BUILTIN_CODE(isolate(), HasIC_Slow); return LoadHandler::LoadIndexedString(isolate(), load_mode); } if (instance_type < FIRST_JS_RECEIVER_TYPE) { TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_SlowStub); - return BUILTIN_CODE(isolate(), KeyedLoadIC_Slow); + return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIC_Slow) + : BUILTIN_CODE(isolate(), KeyedLoadIC_Slow); } if (instance_type == JS_PROXY_TYPE) { return LoadHandler::LoadProxy(isolate()); @@ -1143,7 +1177,8 @@ Handle KeyedLoadIC::LoadElementHandler(Handle receiver_map, if (IsSloppyArgumentsElementsKind(elements_kind)) { // TODO(jgruber): Update counter name. TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_KeyedLoadSloppyArgumentsStub); - return BUILTIN_CODE(isolate(), KeyedLoadIC_SloppyArguments); + return IsAnyHas() ? BUILTIN_CODE(isolate(), KeyedHasIC_SloppyArguments) + : BUILTIN_CODE(isolate(), KeyedLoadIC_SloppyArguments); } bool is_js_array = instance_type == JS_ARRAY_TYPE; if (elements_kind == DICTIONARY_ELEMENTS) { @@ -1244,14 +1279,27 @@ KeyedAccessLoadMode GetLoadMode(Isolate* isolate, Handle receiver, } // namespace -MaybeHandle KeyedLoadIC::Load(Handle object, - Handle key) { - if (MigrateDeprecated(object)) { - Handle result; +MaybeHandle KeyedLoadIC::RuntimeLoad(Handle object, + Handle key) { + Handle result; + + if (IsKeyedLoadIC()) { ASSIGN_RETURN_ON_EXCEPTION( isolate(), result, Runtime::GetObjectProperty(isolate(), object, key), Object); - return result; + } else { + DCHECK(IsKeyedHasIC()); + ASSIGN_RETURN_ON_EXCEPTION(isolate(), result, + Runtime::HasProperty(isolate(), object, key), + Object); + } + return result; +} + +MaybeHandle KeyedLoadIC::Load(Handle object, + Handle key) { + if (MigrateDeprecated(object)) { + return RuntimeLoad(object, key); } Handle load_handle; @@ -1282,11 +1330,7 @@ MaybeHandle KeyedLoadIC::Load(Handle object, if (!load_handle.is_null()) return load_handle; - Handle result; - ASSIGN_RETURN_ON_EXCEPTION(isolate(), result, - Runtime::GetObjectProperty(isolate(), object, key), - Object); - return result; + return RuntimeLoad(object, key); } bool StoreIC::LookupForWrite(LookupIterator* it, Handle value, @@ -1845,7 +1889,6 @@ void KeyedStoreIC::UpdateStoreElement(Handle receiver_map, } } - Handle KeyedStoreIC::ComputeTransitionedMap( Handle map, KeyedAccessStoreMode store_mode) { switch (store_mode) { @@ -1983,7 +2026,6 @@ void KeyedStoreIC::StoreElementPolymorphicHandlers( } } - static KeyedAccessStoreMode GetStoreMode(Handle receiver, uint32_t index, Handle value) { bool oob_access = IsOutOfBoundsAccess(receiver, index); @@ -2028,7 +2070,6 @@ static KeyedAccessStoreMode GetStoreMode(Handle receiver, } } - MaybeHandle KeyedStoreIC::Store(Handle object, Handle key, Handle value) { @@ -2822,7 +2863,6 @@ RUNTIME_FUNCTION(Runtime_LoadPropertyWithInterceptor) { isolate, NewReferenceError(MessageTemplate::kNotDefined, it.name())); } - RUNTIME_FUNCTION(Runtime_StorePropertyWithInterceptor) { HandleScope scope(isolate); DCHECK_EQ(5, args.length()); @@ -2870,7 +2910,6 @@ RUNTIME_FUNCTION(Runtime_StorePropertyWithInterceptor) { return *value; } - RUNTIME_FUNCTION(Runtime_LoadElementWithInterceptor) { // TODO(verwaest): This should probably get the holder and receiver as input. HandleScope scope(isolate); @@ -2896,5 +2935,61 @@ RUNTIME_FUNCTION(Runtime_LoadElementWithInterceptor) { return *result; } + +RUNTIME_FUNCTION(Runtime_KeyedHasIC_Miss) { + HandleScope scope(isolate); + DCHECK_EQ(4, args.length()); + // Runtime functions don't follow the IC's calling convention. + Handle receiver = args.at(0); + Handle key = args.at(1); + Handle slot = args.at(2); + Handle maybe_vector = args.at(3); + + Handle vector = Handle(); + if (!maybe_vector->IsUndefined()) { + DCHECK(maybe_vector->IsFeedbackVector()); + vector = Handle::cast(maybe_vector); + } + FeedbackSlot vector_slot = FeedbackVector::ToSlot(slot->value()); + KeyedLoadIC ic(isolate, vector, vector_slot, FeedbackSlotKind::kHasKeyed); + ic.UpdateState(receiver, key); + RETURN_RESULT_OR_FAILURE(isolate, ic.Load(receiver, key)); +} + +RUNTIME_FUNCTION(Runtime_HasElementWithInterceptor) { + HandleScope scope(isolate); + Handle receiver = args.at(0); + DCHECK_GE(args.smi_at(1), 0); + uint32_t index = args.smi_at(1); + + Handle interceptor(receiver->GetIndexedInterceptor(), + isolate); + PropertyCallbackArguments arguments(isolate, interceptor->data(), *receiver, + *receiver, Just(kDontThrow)); + + if (!interceptor->query()->IsUndefined(isolate)) { + Handle result = arguments.CallIndexedQuery(interceptor, index); + if (!result.is_null()) { + int32_t value; + CHECK(result->ToInt32(&value)); + return value == ABSENT ? ReadOnlyRoots(isolate).false_value() + : ReadOnlyRoots(isolate).true_value(); + } + } else if (!interceptor->getter()->IsUndefined(isolate)) { + Handle result = arguments.CallIndexedGetter(interceptor, index); + if (!result.is_null()) { + return ReadOnlyRoots(isolate).true_value(); + } + } + + LookupIterator it(isolate, receiver, index, receiver); + DCHECK_EQ(LookupIterator::INTERCEPTOR, it.state()); + it.Next(); + Maybe maybe = JSReceiver::HasProperty(&it); + if (maybe.IsNothing()) return ReadOnlyRoots(isolate).exception(); + return maybe.FromJust() ? ReadOnlyRoots(isolate).true_value() + : ReadOnlyRoots(isolate).false_value(); +} + } // namespace internal } // namespace v8 diff --git a/src/ic/ic.h b/src/ic/ic.h index c8960e9e28..aa6ccd9c76 100644 --- a/src/ic/ic.h +++ b/src/ic/ic.h @@ -50,6 +50,7 @@ class IC { state_ = RECOMPUTE_HANDLER; } + bool IsAnyHas() const { return IsKeyedHasIC(); } bool IsAnyLoad() const { return IsLoadIC() || IsLoadGlobalIC() || IsKeyedLoadIC(); } @@ -130,9 +131,10 @@ class IC { bool IsStoreIC() const { return IsStoreICKind(kind_); } bool IsStoreOwnIC() const { return IsStoreOwnICKind(kind_); } bool IsKeyedStoreIC() const { return IsKeyedStoreICKind(kind_); } + bool IsKeyedHasIC() const { return IsKeyedHasICKind(kind_); } bool is_keyed() const { return IsKeyedLoadIC() || IsKeyedStoreIC() || - IsStoreInArrayLiteralICKind(kind_); + IsStoreInArrayLiteralICKind(kind_) || IsKeyedHasIC(); } bool ShouldRecomputeHandler(Handle name); @@ -200,13 +202,12 @@ class IC { DISALLOW_IMPLICIT_CONSTRUCTORS(IC); }; - class LoadIC : public IC { public: LoadIC(Isolate* isolate, Handle vector, FeedbackSlot slot, FeedbackSlotKind kind) : IC(isolate, vector, slot, kind) { - DCHECK(IsAnyLoad()); + DCHECK(IsAnyLoad() || IsAnyHas()); } static bool ShouldThrowReferenceError(FeedbackSlotKind kind) { @@ -222,7 +223,8 @@ class LoadIC : public IC { protected: virtual Handle slow_stub() const { - return BUILTIN_CODE(isolate(), LoadIC_Slow); + return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIC_Slow) + : BUILTIN_CODE(isolate(), LoadIC_Slow); } // Update the inline cache and the global stub cache based on the @@ -260,6 +262,9 @@ class KeyedLoadIC : public LoadIC { Handle key); protected: + V8_WARN_UNUSED_RESULT MaybeHandle RuntimeLoad(Handle object, + Handle key); + // receiver is HeapObject because it could be a String or a JSObject void UpdateLoadElement(Handle receiver, KeyedAccessLoadMode load_mode); @@ -280,7 +285,6 @@ class KeyedLoadIC : public LoadIC { bool CanChangeToAllowOutOfBounds(Handle receiver_map); }; - class StoreIC : public IC { public: StoreIC(Isolate* isolate, Handle vector, FeedbackSlot slot, @@ -331,10 +335,8 @@ class StoreGlobalIC : public StoreIC { enum KeyedStoreCheckMap { kDontCheckMap, kCheckMap }; - enum KeyedStoreIncrementLength { kDontIncrementLength, kIncrementLength }; - class KeyedStoreIC : public StoreIC { public: KeyedAccessStoreMode GetKeyedAccessStoreMode() { diff --git a/src/interpreter/bytecode-array-builder.cc b/src/interpreter/bytecode-array-builder.cc index 2183068576..a89968bcc8 100644 --- a/src/interpreter/bytecode-array-builder.cc +++ b/src/interpreter/bytecode-array-builder.cc @@ -506,17 +506,8 @@ BytecodeArrayBuilder& BytecodeArrayBuilder::CompareOperation( case Token::Value::INSTANCEOF: OutputTestInstanceOf(reg, feedback_slot); break; - default: - UNREACHABLE(); - } - return *this; -} - -BytecodeArrayBuilder& BytecodeArrayBuilder::CompareOperation(Token::Value op, - Register reg) { - switch (op) { case Token::Value::IN: - OutputTestIn(reg); + OutputTestIn(reg, feedback_slot); break; default: UNREACHABLE(); diff --git a/src/interpreter/bytecode-array-builder.h b/src/interpreter/bytecode-array-builder.h index d362ffffa4..2dc9c04d4e 100644 --- a/src/interpreter/bytecode-array-builder.h +++ b/src/interpreter/bytecode-array-builder.h @@ -379,7 +379,6 @@ class V8_EXPORT_PRIVATE BytecodeArrayBuilder final { // Tests. BytecodeArrayBuilder& CompareOperation(Token::Value op, Register reg, int feedback_slot); - BytecodeArrayBuilder& CompareOperation(Token::Value op, Register reg); BytecodeArrayBuilder& CompareReference(Register reg); BytecodeArrayBuilder& CompareUndetectable(); BytecodeArrayBuilder& CompareUndefined(); diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc index 85958706a2..26c9f91573 100644 --- a/src/interpreter/bytecode-generator.cc +++ b/src/interpreter/bytecode-generator.cc @@ -4850,15 +4850,15 @@ void BytecodeGenerator::VisitCompareOperation(CompareOperation* expr) { Register lhs = VisitForRegisterValue(expr->left()); VisitForAccumulatorValue(expr->right()); builder()->SetExpressionPosition(expr); + FeedbackSlot slot; if (expr->op() == Token::IN) { - builder()->CompareOperation(expr->op(), lhs); + slot = feedback_spec()->AddKeyedHasICSlot(); } else if (expr->op() == Token::INSTANCEOF) { - FeedbackSlot slot = feedback_spec()->AddInstanceOfSlot(); - builder()->CompareOperation(expr->op(), lhs, feedback_index(slot)); + slot = feedback_spec()->AddInstanceOfSlot(); } else { - FeedbackSlot slot = feedback_spec()->AddCompareICSlot(); - builder()->CompareOperation(expr->op(), lhs, feedback_index(slot)); + slot = feedback_spec()->AddCompareICSlot(); } + builder()->CompareOperation(expr->op(), lhs, feedback_index(slot)); } // Always returns a boolean value. execution_result()->SetResultIsBoolean(); diff --git a/src/interpreter/bytecodes.h b/src/interpreter/bytecodes.h index 7efcd1ae62..f9713ef79b 100644 --- a/src/interpreter/bytecodes.h +++ b/src/interpreter/bytecodes.h @@ -235,7 +235,7 @@ namespace interpreter { V(TestReferenceEqual, AccumulatorUse::kReadWrite, OperandType::kReg) \ V(TestInstanceOf, AccumulatorUse::kReadWrite, OperandType::kReg, \ OperandType::kIdx) \ - V(TestIn, AccumulatorUse::kReadWrite, OperandType::kReg) \ + V(TestIn, AccumulatorUse::kReadWrite, OperandType::kReg, OperandType::kIdx) \ V(TestUndetectable, AccumulatorUse::kReadWrite) \ V(TestNull, AccumulatorUse::kReadWrite) \ V(TestUndefined, AccumulatorUse::kReadWrite) \ diff --git a/src/interpreter/interpreter-generator.cc b/src/interpreter/interpreter-generator.cc index 32924ec044..e1e3181357 100644 --- a/src/interpreter/interpreter-generator.cc +++ b/src/interpreter/interpreter-generator.cc @@ -1876,16 +1876,22 @@ IGNITION_HANDLER(TestReferenceEqual, InterpreterAssembler) { Dispatch(); } -// TestIn +// TestIn // // Test if the object referenced by the register operand is a property of the // object referenced by the accumulator. IGNITION_HANDLER(TestIn, InterpreterAssembler) { - Node* property = LoadRegisterAtOperandIndex(0); + Node* name = LoadRegisterAtOperandIndex(0); Node* object = GetAccumulator(); + Node* raw_slot = BytecodeOperandIdx(1); + Node* smi_slot = SmiTag(raw_slot); + Node* feedback_vector = LoadFeedbackVectorUnchecked(); Node* context = GetContext(); - SetAccumulator(HasProperty(context, object, property, kHasProperty)); + VARIABLE(var_result, MachineRepresentation::kTagged); + var_result.Bind(CallBuiltin(Builtins::kKeyedHasIC, context, object, name, + smi_slot, feedback_vector)); + SetAccumulator(var_result.value()); Dispatch(); } diff --git a/src/objects-printer.cc b/src/objects-printer.cc index 189c9c18ff..5e1e764573 100644 --- a/src/objects-printer.cc +++ b/src/objects-printer.cc @@ -1128,21 +1128,22 @@ void FeedbackVector::FeedbackSlotPrint(std::ostream& os, void FeedbackNexus::Print(std::ostream& os) { // NOLINT switch (kind()) { case FeedbackSlotKind::kCall: - case FeedbackSlotKind::kLoadProperty: - case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kCloneObject: + case FeedbackSlotKind::kHasKeyed: + case FeedbackSlotKind::kInstanceOf: case FeedbackSlotKind::kLoadGlobalInsideTypeof: case FeedbackSlotKind::kLoadGlobalNotInsideTypeof: - case FeedbackSlotKind::kStoreNamedSloppy: - case FeedbackSlotKind::kStoreNamedStrict: - case FeedbackSlotKind::kStoreOwnNamed: + case FeedbackSlotKind::kLoadKeyed: + case FeedbackSlotKind::kLoadProperty: + case FeedbackSlotKind::kStoreDataPropertyInLiteral: case FeedbackSlotKind::kStoreGlobalSloppy: case FeedbackSlotKind::kStoreGlobalStrict: - case FeedbackSlotKind::kStoreKeyedSloppy: - case FeedbackSlotKind::kInstanceOf: - case FeedbackSlotKind::kStoreDataPropertyInLiteral: - case FeedbackSlotKind::kStoreKeyedStrict: case FeedbackSlotKind::kStoreInArrayLiteral: - case FeedbackSlotKind::kCloneObject: { + case FeedbackSlotKind::kStoreKeyedSloppy: + case FeedbackSlotKind::kStoreKeyedStrict: + case FeedbackSlotKind::kStoreNamedSloppy: + case FeedbackSlotKind::kStoreNamedStrict: + case FeedbackSlotKind::kStoreOwnNamed: { os << InlineCacheState2String(ic_state()); break; } diff --git a/src/runtime/runtime-object.cc b/src/runtime/runtime-object.cc index 680863620c..02d1a8e157 100644 --- a/src/runtime/runtime-object.cc +++ b/src/runtime/runtime-object.cc @@ -53,6 +53,30 @@ MaybeHandle Runtime::GetObjectProperty(Isolate* isolate, return result; } +MaybeHandle Runtime::HasProperty(Isolate* isolate, + Handle object, + Handle key) { + // Check that {object} is actually a receiver. + if (!object->IsJSReceiver()) { + THROW_NEW_ERROR( + isolate, + NewTypeError(MessageTemplate::kInvalidInOperatorUse, key, object), + Object); + } + Handle receiver = Handle::cast(object); + + // Convert the {key} to a name. + Handle name; + ASSIGN_RETURN_ON_EXCEPTION(isolate, name, Object::ToName(isolate, key), + Object); + + // Lookup the {name} on {receiver}. + Maybe maybe = JSReceiver::HasProperty(receiver, name); + if (maybe.IsNothing()) return MaybeHandle(); + return maybe.FromJust() ? ReadOnlyRoots(isolate).true_value_handle() + : ReadOnlyRoots(isolate).false_value_handle(); +} + namespace { bool DeleteObjectPropertyFast(Isolate* isolate, Handle receiver, diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index 8e63cf1740..396084078b 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -573,7 +573,9 @@ namespace internal { F(StoreIC_Miss, 5, 1) \ F(StoreInArrayLiteralIC_Slow, 5, 1) \ F(StorePropertyWithInterceptor, 5, 1) \ - F(CloneObjectIC_Miss, 4, 1) + F(CloneObjectIC_Miss, 4, 1) \ + F(KeyedHasIC_Miss, 4, 1) \ + F(HasElementWithInterceptor, 2, 1) #define FOR_EACH_INTRINSIC_RETURN_OBJECT_IMPL(F, I) \ FOR_EACH_INTRINSIC_ARRAY(F, I) \ @@ -708,6 +710,9 @@ class Runtime : public AllStatic { Isolate* isolate, Handle object, Handle key, bool* is_found_out = nullptr); + V8_WARN_UNUSED_RESULT static MaybeHandle HasProperty( + Isolate* isolate, Handle object, Handle key); + V8_WARN_UNUSED_RESULT static MaybeHandle GetInternalProperties( Isolate* isolate, Handle); diff --git a/test/cctest/interpreter/bytecode_expectations/IfConditions.golden b/test/cctest/interpreter/bytecode_expectations/IfConditions.golden index e68211a189..fa844af5d1 100644 --- a/test/cctest/interpreter/bytecode_expectations/IfConditions.golden +++ b/test/cctest/interpreter/bytecode_expectations/IfConditions.golden @@ -171,11 +171,11 @@ snippet: " " frame size: 0 parameter count: 3 -bytecode array length: 14 +bytecode array length: 15 bytecodes: [ /* 10 E> */ B(StackCheck), /* 19 S> */ B(Ldar), R(arg1), - /* 25 E> */ B(TestIn), R(arg0), + /* 25 E> */ B(TestIn), R(arg0), U8(0), B(JumpIfFalse), U8(7), /* 33 S> */ B(Wide), B(LdaSmi), I16(200), /* 44 S> */ B(Return), @@ -396,7 +396,7 @@ snippet: " " frame size: 0 parameter count: 3 -bytecode array length: 82 +bytecode array length: 83 bytecodes: [ /* 10 E> */ B(StackCheck), /* 21 S> */ B(Ldar), R(arg1), @@ -430,12 +430,12 @@ bytecodes: [ /* 174 S> */ B(LdaSmi), I8(1), /* 183 S> */ B(Return), /* 188 S> */ B(Ldar), R(arg1), - /* 194 E> */ B(TestIn), R(arg0), + /* 194 E> */ B(TestIn), R(arg0), U8(6), B(JumpIfFalse), U8(5), /* 202 S> */ B(LdaSmi), I8(1), /* 211 S> */ B(Return), /* 216 S> */ B(Ldar), R(arg1), - /* 222 E> */ B(TestInstanceOf), R(arg0), U8(6), + /* 222 E> */ B(TestInstanceOf), R(arg0), U8(8), B(JumpIfFalse), U8(5), /* 238 S> */ B(LdaSmi), I8(1), /* 247 S> */ B(Return), diff --git a/test/cctest/interpreter/test-interpreter.cc b/test/cctest/interpreter/test-interpreter.cc index fab903ce4e..d9c32f2210 100644 --- a/test/cctest/interpreter/test-interpreter.cc +++ b/test/cctest/interpreter/test-interpreter.cc @@ -2275,21 +2275,26 @@ TEST(InterpreterTestIn) { const char* properties[] = {"length", "fuzzle", "x", "0"}; for (size_t i = 0; i < arraysize(properties); i++) { bool expected_value = (i == 0); - BytecodeArrayBuilder builder(zone, 1, 1); + FeedbackVectorSpec feedback_spec(zone); + BytecodeArrayBuilder builder(zone, 1, 1, &feedback_spec); Register r0(0); builder.LoadLiteral(ast_factory.GetOneByteString(properties[i])) .StoreAccumulatorInRegister(r0); + FeedbackSlot slot = feedback_spec.AddKeyedHasICSlot(); + Handle metadata = + NewFeedbackMetadata(isolate, &feedback_spec); + size_t array_entry = builder.AllocateDeferredConstantPoolEntry(); builder.SetDeferredConstantPoolEntry(array_entry, array); builder.LoadConstantPoolEntry(array_entry) - .CompareOperation(Token::Value::IN, r0) + .CompareOperation(Token::Value::IN, r0, GetIndex(slot)) .Return(); ast_factory.Internalize(isolate); Handle bytecode_array = builder.ToBytecodeArray(isolate); - InterpreterTester tester(isolate, bytecode_array); + InterpreterTester tester(isolate, bytecode_array, metadata); auto callable = tester.GetCallable<>(); Handle return_value = callable().ToHandleChecked(); CHECK(return_value->IsBoolean()); diff --git a/test/cctest/test-api-interceptors.cc b/test/cctest/test-api-interceptors.cc index 2ca473dea7..41678032af 100644 --- a/test/cctest/test-api-interceptors.cc +++ b/test/cctest/test-api-interceptors.cc @@ -470,6 +470,9 @@ THREADED_TEST(QueryInterceptor) { v8_compile("Object.isFrozen('obj.x');")->Run(env.local()).ToLocalChecked(); CHECK_EQ(8, query_counter_int); + + v8_compile("'x' in obj;")->Run(env.local()).ToLocalChecked(); + CHECK_EQ(9, query_counter_int); } namespace { @@ -874,15 +877,14 @@ THREADED_TEST(InterceptorHasOwnPropertyCausingGC) { CHECK(!value->BooleanValue(isolate)); } - -static void CheckInterceptorLoadIC( - v8::GenericNamedPropertyGetterCallback getter, const char* source, - int expected) { +static void CheckInterceptorIC(v8::GenericNamedPropertyGetterCallback getter, + v8::GenericNamedPropertyQueryCallback query, + const char* source, int expected) { v8::Isolate* isolate = CcTest::isolate(); v8::HandleScope scope(isolate); v8::Local templ = ObjectTemplate::New(isolate); templ->SetHandler(v8::NamedPropertyHandlerConfiguration( - getter, nullptr, nullptr, nullptr, nullptr, v8_str("data"))); + getter, nullptr, query, nullptr, nullptr, v8_str("data"))); LocalContext context; context->Global() ->Set(context.local(), v8_str("o"), @@ -892,6 +894,11 @@ static void CheckInterceptorLoadIC( CHECK_EQ(expected, value->Int32Value(context.local()).FromJust()); } +static void CheckInterceptorLoadIC( + v8::GenericNamedPropertyGetterCallback getter, const char* source, + int expected) { + CheckInterceptorIC(getter, nullptr, source, expected); +} static void InterceptorLoadICGetter( Local name, const v8::PropertyCallbackInfo& info) { @@ -1432,6 +1439,92 @@ THREADED_TEST(InterceptorReturningZero) { 0); } +namespace { + +template +void HasICQuery(TKey name, const v8::PropertyCallbackInfo& info) { + ApiTestFuzzer::Fuzz(); + v8::Isolate* isolate = CcTest::isolate(); + CHECK_EQ(isolate, info.GetIsolate()); + info.GetReturnValue().Set(v8::Integer::New(isolate, attribute)); +} + +template +void HasICQueryToggle(TKey name, + const v8::PropertyCallbackInfo& info) { + ApiTestFuzzer::Fuzz(); + static bool toggle = false; + toggle = !toggle; + v8::Isolate* isolate = CcTest::isolate(); + CHECK_EQ(isolate, info.GetIsolate()); + info.GetReturnValue().Set(v8::Integer::New( + isolate, toggle ? v8::internal::ABSENT : v8::internal::NONE)); +} + +int named_query_counter = 0; +void NamedQueryCallback(Local name, + const v8::PropertyCallbackInfo& info) { + named_query_counter++; +} + +} // namespace + +THREADED_TEST(InterceptorHasIC) { + named_query_counter = 0; + CheckInterceptorIC(nullptr, NamedQueryCallback, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " 'x' in o;" + "}", + 0); + CHECK_EQ(1000, named_query_counter); +} + +THREADED_TEST(InterceptorHasICQueryAbsent) { + CheckInterceptorIC(nullptr, HasICQuery, v8::internal::ABSENT>, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if ('x' in o) ++result;" + "}", + 0); +} + +THREADED_TEST(InterceptorHasICQueryNone) { + CheckInterceptorIC(nullptr, HasICQuery, v8::internal::NONE>, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if ('x' in o) ++result;" + "}", + 1000); +} + +THREADED_TEST(InterceptorHasICGetter) { + CheckInterceptorIC(InterceptorLoadICGetter, nullptr, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if ('x' in o) ++result;" + "}", + 1000); +} + +THREADED_TEST(InterceptorHasICQueryGetter) { + CheckInterceptorIC(InterceptorLoadICGetter, + HasICQuery, v8::internal::ABSENT>, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if ('x' in o) ++result;" + "}", + 0); +} + +THREADED_TEST(InterceptorHasICQueryToggle) { + CheckInterceptorIC(InterceptorLoadICGetter, HasICQueryToggle>, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if ('x' in o) ++result;" + "}", + 500); +} static void InterceptorStoreICSetter( Local key, Local value, @@ -3273,6 +3366,101 @@ THREADED_TEST(IndexedInterceptorOnProto) { ExpectString(code, "PASSED"); } +namespace { + +void CheckIndexedInterceptorHasIC(v8::IndexedPropertyGetterCallback getter, + v8::IndexedPropertyQueryCallback query, + const char* source, int expected) { + v8::Isolate* isolate = CcTest::isolate(); + v8::HandleScope scope(isolate); + v8::Local templ = ObjectTemplate::New(isolate); + templ->SetHandler(v8::IndexedPropertyHandlerConfiguration( + getter, nullptr, query, nullptr, nullptr, v8_str("data"))); + LocalContext context; + context->Global() + ->Set(context.local(), v8_str("o"), + templ->NewInstance(context.local()).ToLocalChecked()) + .FromJust(); + v8::Local value = CompileRun(source); + CHECK_EQ(expected, value->Int32Value(context.local()).FromJust()); +} + +int indexed_query_counter = 0; +void IndexedQueryCallback(uint32_t index, + const v8::PropertyCallbackInfo& info) { + indexed_query_counter++; +} + +void IndexHasICQueryAbsent(uint32_t index, + const v8::PropertyCallbackInfo& info) { + ApiTestFuzzer::Fuzz(); + v8::Isolate* isolate = CcTest::isolate(); + CHECK_EQ(isolate, info.GetIsolate()); + info.GetReturnValue().Set(v8::Integer::New(isolate, v8::internal::ABSENT)); +} + +} // namespace + +THREADED_TEST(IndexedInterceptorHasIC) { + indexed_query_counter = 0; + CheckIndexedInterceptorHasIC(nullptr, IndexedQueryCallback, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " i in o;" + "}", + 0); + CHECK_EQ(1000, indexed_query_counter); +} + +THREADED_TEST(IndexedInterceptorHasICQueryAbsent) { + CheckIndexedInterceptorHasIC(nullptr, + // HasICQuery, + IndexHasICQueryAbsent, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if (i in o) ++result;" + "}", + 0); +} + +THREADED_TEST(IndexedInterceptorHasICQueryNone) { + CheckIndexedInterceptorHasIC(nullptr, + HasICQuery, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if (i in o) ++result;" + "}", + 1000); +} + +THREADED_TEST(IndexedInterceptorHasICGetter) { + CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter, nullptr, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if (i in o) ++result;" + "}", + 1000); +} + +THREADED_TEST(IndexedInterceptorHasICQueryGetter) { + CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter, + HasICQuery, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if (i in o) ++result;" + "}", + 0); +} + +THREADED_TEST(IndexedInterceptorHasICQueryToggle) { + CheckIndexedInterceptorHasIC(IdentityIndexedPropertyGetter, + HasICQueryToggle, + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " if (i in o) ++result;" + "}", + 500); +} static void NoBlockGetterX(Local name, const v8::PropertyCallbackInfo&) {} diff --git a/test/mjsunit/arguments.js b/test/mjsunit/arguments.js index ad12540c6d..6f04115c8b 100644 --- a/test/mjsunit/arguments.js +++ b/test/mjsunit/arguments.js @@ -366,3 +366,14 @@ assertEquals(117, arg_set(0xFFFFFFFF)); f7(1,2,3,4,5,6,7); f7(1,2,3,4,5,6,7,8); })(); + +(function testArgumentsHole() { + function f(a) { + arguments[3] = 1; + return arguments[2]; + }; + + assertEquals(undefined, f(1)); + assertEquals(undefined, f(1)); + assertEquals(undefined, f(1)); +})(); diff --git a/test/mjsunit/keyed-has-ic-module-export.js b/test/mjsunit/keyed-has-ic-module-export.js new file mode 100644 index 0000000000..5183157ac4 --- /dev/null +++ b/test/mjsunit/keyed-has-ic-module-export.js @@ -0,0 +1,9 @@ +// Copyright 2019 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. + +// MODULE + +export var a = "A"; +export var b = "B"; +export var c = "C"; diff --git a/test/mjsunit/keyed-has-ic-module-import.js b/test/mjsunit/keyed-has-ic-module-import.js new file mode 100644 index 0000000000..77a42925f4 --- /dev/null +++ b/test/mjsunit/keyed-has-ic-module-import.js @@ -0,0 +1,70 @@ +// Copyright 2019 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 +// MODULE + +import * as mod from "keyed-has-ic-module-export.js"; + +function testIn(obj, key) { + return key in obj; +} + +function expectTrue(obj, key) { + assertTrue(testIn(obj, key)); +} + +function expectFalse(obj, key) { + assertFalse(testIn(obj, key)); +} + +var tests = { + monomporphicTrue: function() { + expectTrue(mod, "a"); + expectTrue(mod, "a"); + expectTrue(mod, "a"); + }, + + polymprohicKeyTrue: function() { + expectTrue(mod, "a"); + expectTrue(mod, "b"); + expectTrue(mod, "c"); + }, + + monomorphicFalse: function() { + expectFalse(mod, "d"); + expectFalse(mod, "d"); + expectFalse(mod, "d"); + }, + + polymorphicKeyFalse: function() { + expectFalse(mod, "d"); + expectFalse(mod, "e"); + expectFalse(mod, "f"); + }, + + polymorphicTrue: function() { + var o = {a: "A"}; + expectTrue(mod, "a"); + expectTrue(o, "a"); + expectTrue(mod, "a"); + expectTrue(o, "a"); + }, + + polymorphicFalse: function() { + var o = {a: "A"}; + expectFalse(mod, "d"); + expectFalse(o, "d"); + expectFalse(mod, "d"); + expectFalse(o, "d"); + } +}; + +for (let test in tests) { + %DeoptimizeFunction(testIn); + %ClearFunctionFeedback(testIn); + tests[test](); + %OptimizeFunctionOnNextCall(testIn); + tests[test](); +} diff --git a/test/mjsunit/keyed-has-ic.js b/test/mjsunit/keyed-has-ic.js new file mode 100644 index 0000000000..9e6fe25cc8 --- /dev/null +++ b/test/mjsunit/keyed-has-ic.js @@ -0,0 +1,402 @@ +// Copyright 2019 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 testIn(obj, key) { + return key in obj; +} + +function expectTrue(obj, key) { + assertTrue(testIn(obj, key)); +} + +function expectFalse(obj, key) { + assertFalse(testIn(obj, key)); +} + +var tests = { + TestMonomorphicPackedSMIArray: function() { + var a = [0, 1, 2]; + expectTrue(a, 0); + expectTrue(a, 1); + expectTrue(a, 2); + expectFalse(a, 3); + }, + + TestMonomorphicPackedArrayPrototypeProperty: function() + { + var a = [0, 1, 2]; + + expectTrue(a, 0); + expectTrue(a, 1); + expectFalse(a, 3); + Array.prototype[3] = 3; + expectTrue(a, 3); + + // ensure the prototype change doesn't affect later tests + delete Array.prototype[3]; + assertFalse((3 in Array.prototype)); + expectFalse(a, 3); + }, + + TestMonomorphicPackedDoubleArray: function() { + var a = [0.0, 1.1, 2.2]; + expectTrue(a, 0); + expectTrue(a, 1); + expectTrue(a, 2); + expectFalse(a, 3); + }, + + TestMonomorphicPackedArray: function() { + var a = ["A", "B", {}]; + expectTrue(a, 0); + expectTrue(a, 1); + expectTrue(a, 2); + expectFalse(a, 3); + }, + + TestMonomorphicHoleyArray: function() { + var a = [0, 1, 2]; + a[4] = 4; + + expectTrue(a, 0); + expectTrue(a, 1); + expectTrue(a, 2); + expectFalse(a, 3); + expectTrue(a, 4); + }, + + TestMonomorphicTypedArray: function() { + var a = new Int32Array(3); + expectTrue(a, 0); + expectTrue(a, 1); + expectTrue(a, 2); + expectFalse(a, 3); + expectFalse(a, 4); + }, + + TestPolymorphicPackedArrays: function() { + var a = [0, 1, 2]; + var b = [0.0, 1.1, 2.2]; + var c = ["A", "B", {}]; + expectTrue(c, 0); + expectTrue(b, 1); + expectTrue(a, 2); + expectTrue(c, 1); + expectTrue(b, 2); + expectTrue(a, 0); + expectFalse(c, 3); + expectFalse(b, 4); + expectFalse(a, 5); + }, + + TestPolymorphicMixedArrays: function() { + var a = new Array(3); // holey SMI + var b = [0.0,1.1,2.2]; // packed double + var c = new Int8Array(3); // typed array + + expectFalse(a, 0); + expectTrue(b, 1); + expectTrue(c, 2); + expectFalse(a, 1); + expectTrue(b, 2); + expectTrue(c, 0); + expectFalse(a, 3); + expectFalse(b, 4); + expectFalse(c, 5); + }, + + TestMegamorphicArrays: function() { + var a = [0,1,2,3] // packed SMI + var b = new Array(3); // holey SMI + var c = [0.0,1.1,2.2]; // packed double + var d = ['a', 'b', 'c'] // packed + var e = new Int8Array(3); // typed array + var f = new Uint8Array(3); // typed array + var g = new Int32Array(3); // typed array + + expectTrue(a, 0); + expectFalse(b, 1); + expectTrue(c, 2); + expectFalse(d, 3); + expectFalse(e, 4); + expectFalse(f, 5); + expectFalse(g, 6); + expectFalse(a, 5); + expectFalse(b, 4); + expectFalse(c, 3); + expectTrue(d, 2); + expectTrue(e, 1); + expectTrue(f, 0); + expectTrue(g, 0); + }, + + TestMonomorphicObject: function() { + var a = { a: "A", b: "B" }; + + expectTrue(a, 'a'); + expectTrue(a, 'a'); + expectTrue(a, 'a'); + }, + + TestMonomorphicProxyHasPropertyNoTrap: function() { + var a = new Proxy({a: 'A'}, {}); + + expectTrue(a, 'a'); + expectTrue(a, 'a'); + expectTrue(a, 'a'); + }, + + TestMonomorphicProxyNoPropertyNoTrap: function() { + var a = new Proxy({}, {}); + + expectFalse(a, 'a'); + expectFalse(a, 'a'); + expectFalse(a, 'a'); + }, + + TestMonomorphicProxyHasPropertyHasTrap: function() { + var a = new Proxy({a: 'A'}, { has: function() {return false;}}); + + expectFalse(a, 'a'); + expectFalse(a, 'a'); + expectFalse(a, 'a'); + }, + + TestMonomorphicProxyNoPropertyHasTrap: function() { + var a = new Proxy({}, { has: function() { return true; }}); + + expectTrue(a, 'a'); + expectTrue(a, 'a'); + expectTrue(a, 'a'); + }, + + TestMonomorphicObjectPrototype: function() { + var a = { b: "B" }; + + expectFalse(a, 'a'); + expectFalse(a, 'a'); + expectFalse(a, 'a'); + Object.prototype.a = 'A'; + expectTrue(a, 'a'); + delete Object.prototype.a; + assertFalse((a in Object.prototype)); + expectFalse(a, 'a'); + }, + + TestPolymorphicObject: function() { + var a = { a: "A" }; + var b = { a: "A", b: "B" }; + var c = { b: "B", c: "C" }; + + expectTrue(a, 'a'); + expectTrue(a, 'a'); + expectTrue(b, 'a'); + expectFalse(c, 'a'); + expectTrue(a, 'a'); + expectTrue(b, 'a'); + expectFalse(c, 'a'); + }, + + TestMegamorphicObject: function() { + var a = { a: "A" }; + var b = { a: "A", b: "B" }; + var c = { b: "B", c: "C" }; + var d = { b: "A", a: "B" }; + var e = { e: "E", a: "A" }; + var f = { f: "F", b: "B", c: "C" }; + + expectTrue(a, 'a'); + expectTrue(a, 'a'); + expectTrue(b, 'a'); + expectFalse(c, 'a'); + expectTrue(d, 'a'); + expectTrue(e, 'a'); + expectFalse(f, 'a'); + expectTrue(a, 'a'); + expectTrue(b, 'a'); + expectFalse(c, 'a'); + expectTrue(d, 'a'); + expectTrue(e, 'a'); + expectFalse(f, 'a'); + }, + + TestPolymorphicKeys: function() { + var a = { a: "A", b: "B" }; + + expectTrue(a, 'a'); + expectTrue(a, 'b'); + expectFalse(a, 'c'); + expectTrue(a, 'a'); + expectTrue(a, 'b'); + expectFalse(a, 'c'); + expectTrue(a, 'a'); + expectTrue(a, 'b'); + expectFalse(a, 'c'); + }, + + TestPolymorphicMixed: function() { + var a = { a: "A" }; + var b = new Proxy({}, {}); + var c = new Int32Array(3); + + expectTrue(a, 'a'); + expectTrue(a, 'a'); + expectFalse(b, 'a'); + expectFalse(c, 'a'); + expectTrue(a, 'a'); + expectFalse(b, 'a'); + expectFalse(c, 'a'); + }, +}; + +for (test in tests) { + %DeoptimizeFunction(testIn); + %ClearFunctionFeedback(testIn); + tests[test](); + %OptimizeFunctionOnNextCall(testIn); + tests[test](); +} + +// test function prototypes. +(function() { + var o = function() {}; + + var proto = function() { + assertTrue("prototype" in o); + o.prototype; + } + + proto(); + proto(); + proto(); +})(); + +// `in` is not allowed on string +(function() { + function test() { + 0 in "string" + }; + + assertThrows(test, TypeError); +})(); + +// `in` is allowed on `this` even when `this` is a string +(function() { + function test() { + assertTrue("length" in this); + }; + + test.call(""); + test.call(""); + test.call(""); +})(); + +(function() { + var index = 0; + function test(i) { + return index in arguments; + }; + + assertFalse(test()) + assertFalse(test()) + assertTrue(test(0)); + assertTrue(test(0,1)); + + index = 2; + assertFalse(test()) + assertFalse(test(0)); + assertFalse(test(0,1)); + assertTrue(test(0,1,2)); +})(); + +(function() { + function test(a) { + arguments[3] = 1; + return 2 in arguments; + }; + + assertFalse(test(1)); + assertFalse(test(1)); + assertFalse(test(1)); +})(); + +(function() { + function test(o, k) { + try { + k in o; + } catch (e) { + return false; + } + return true; + } + + var str = "string"; + // this will place slow_stub in the IC for strings. + assertFalse(test(str, "length")); + assertFalse(test(str, "length")); + + // this turns the cache polymorphic, and causes generats LoadElement + // handlers for everything in the cache. This test ensures that + // KeyedLoadIC::LoadElementHandler can handle seeing string maps. + var ary = [0,1,2,3]; + assertTrue(test(ary, 1)); + assertTrue(test(ary, 1)); + + assertFalse(test(str, 0)); + assertFalse(test(str, 0)); +})(); + +(function() { + function test(o, k) { + try { + k in o; + } catch (e) { + return false; + } + return true; + } + + var str = "string"; + assertFalse(test(str, "length")); + assertFalse(test(str, "length")); + assertFalse(test(str, "length")); +})(); + +(function() { + function test(o, k) { + try { + k in o; + } catch (e) { + return false; + } + return true; + } + + var str = "string"; + assertFalse(test(str, 0)); + assertFalse(test(str, 0)); + assertFalse(test(str, 0)); +})(); + +(function() { + function test(o, k) { + try { + k in o; + } catch (e) { + return false; + } + return true; + } + + var ary = [0,1,2,3]; + assertTrue(test(ary, 1)); + assertTrue(test(ary, 1)); + + var str = "string"; + assertFalse(test(str, 0)); + assertFalse(test(str, 0)); + assertFalse(test(str, 0)); +})(); diff --git a/test/unittests/interpreter/bytecode-array-builder-unittest.cc b/test/unittests/interpreter/bytecode-array-builder-unittest.cc index 3df844c4bd..a9050d793b 100644 --- a/test/unittests/interpreter/bytecode-array-builder-unittest.cc +++ b/test/unittests/interpreter/bytecode-array-builder-unittest.cc @@ -261,7 +261,7 @@ TEST_F(BytecodeArrayBuilderTest, AllBytecodesGenerated) { .CompareOperation(Token::Value::GTE, reg, 6) .CompareTypeOf(TestTypeOfFlags::LiteralFlag::kNumber) .CompareOperation(Token::Value::INSTANCEOF, reg, 7) - .CompareOperation(Token::Value::IN, reg) + .CompareOperation(Token::Value::IN, reg, 8) .CompareReference(reg) .CompareUndetectable() .CompareUndefined() diff --git a/test/unittests/interpreter/bytecodes-unittest.cc b/test/unittests/interpreter/bytecodes-unittest.cc index 16b4e80489..6f5a11c0c7 100644 --- a/test/unittests/interpreter/bytecodes-unittest.cc +++ b/test/unittests/interpreter/bytecodes-unittest.cc @@ -89,7 +89,7 @@ TEST(OperandScaling, ScalableAndNonScalable) { 1 + 2 + 2 * scale); CHECK_EQ(Bytecodes::Size(Bytecode::kCreateObjectLiteral, operand_scale), 1 + 2 * scale + 1); - CHECK_EQ(Bytecodes::Size(Bytecode::kTestIn, operand_scale), 1 + scale); + CHECK_EQ(Bytecodes::Size(Bytecode::kTestIn, operand_scale), 1 + 2 * scale); } }