diff --git a/src/compiler/js-call-reducer.cc b/src/compiler/js-call-reducer.cc index 9891ed7b47..b7c73ec8b3 100644 --- a/src/compiler/js-call-reducer.cc +++ b/src/compiler/js-call-reducer.cc @@ -507,7 +507,7 @@ Reduction JSCallReducer::ReduceFunctionPrototypeBind(Node* node) { // - target, which is Function.prototype.bind JSFunction // - receiver, which is the [[BoundTargetFunction]] // - bound_this (optional), which is the [[BoundThis]] - // - and all the remaining value inouts are [[BoundArguments]] + // - and all the remaining value inputs are [[BoundArguments]] Node* receiver = NodeProperties::GetValueInput(node, 1); Node* bound_this = (node->op()->ValueInputCount() < 3) ? jsgraph()->UndefinedConstant() diff --git a/src/compiler/js-heap-broker.cc b/src/compiler/js-heap-broker.cc index 2f169a6d67..6c2527ea96 100644 --- a/src/compiler/js-heap-broker.cc +++ b/src/compiler/js-heap-broker.cc @@ -2155,8 +2155,9 @@ JSHeapBroker::JSHeapBroker(Isolate* isolate, Zone* broker_zone, tracing_enabled_(tracing_enabled), feedback_(zone()), bytecode_analyses_(zone()), - ais_for_loading_then_(zone()), - ais_for_loading_exec_(zone()) { + ais_for_loading_exec_(zone()), + ais_for_loading_has_instance_(zone()), + ais_for_loading_then_(zone()) { // Note that this initialization of the refs_ pointer with the minimal // initial capacity is redundant in the normal use case (concurrent // compilation enabled, standard objects to be serialized), as the map @@ -4138,8 +4139,29 @@ base::Optional JSHeapBroker::GetNameFeedback( PropertyAccessInfo JSHeapBroker::GetAccessInfoForLoadingThen(MapRef map) { auto access_info = ais_for_loading_then_.find(map); if (access_info == ais_for_loading_then_.end()) { + TRACE_BROKER_MISSING(this, + "access info for property 'then' on map " << map); + return PropertyAccessInfo::Invalid(zone()); + } + return access_info->second; +} + +PropertyAccessInfo JSHeapBroker::GetAccessInfoForLoadingHasInstance( + MapRef map) { + auto access_info = ais_for_loading_has_instance_.find(map); + if (access_info == ais_for_loading_has_instance_.end()) { TRACE_BROKER_MISSING( - this, "access info for reducing JSResolvePromise with map " << map); + this, "access info for property Symbol.hasInstance on map " << map); + return PropertyAccessInfo::Invalid(zone()); + } + return access_info->second; +} + +PropertyAccessInfo JSHeapBroker::GetAccessInfoForLoadingExec(MapRef map) { + auto access_info = ais_for_loading_exec_.find(map); + if (access_info == ais_for_loading_exec_.end()) { + TRACE_BROKER_MISSING(this, + "access info for property 'exec' on map " << map); return PropertyAccessInfo::Invalid(zone()); } return access_info->second; @@ -4157,30 +4179,27 @@ void JSHeapBroker::CreateAccessInfoForLoadingThen( } } -PropertyAccessInfo JSHeapBroker::GetAccessInfoForLoadingExec(MapRef map) { - auto access_info = ais_for_loading_exec_.find(map); - if (access_info == ais_for_loading_exec_.end()) { - TRACE_BROKER_MISSING(this, - "access info for property 'exec' on map " << map); - return PropertyAccessInfo::Invalid(zone()); - } - return access_info->second; +PropertyAccessInfo const& JSHeapBroker::CreateAccessInfoForLoadingHasInstance( + MapRef map, CompilationDependencies* dependencies) { + auto it = ais_for_loading_has_instance_.find(map); + if (it != ais_for_loading_has_instance_.end()) return it->second; + + AccessInfoFactory access_info_factory(this, dependencies, zone()); + auto access_info = access_info_factory.ComputePropertyAccessInfo( + map.object(), isolate()->factory()->has_instance_symbol(), + AccessMode::kLoad); + return ais_for_loading_has_instance_.insert({map, access_info}).first->second; } PropertyAccessInfo const& JSHeapBroker::CreateAccessInfoForLoadingExec( MapRef map, CompilationDependencies* dependencies) { - auto access_info = ais_for_loading_exec_.find(map); - if (access_info != ais_for_loading_exec_.end()) { - return access_info->second; - } + auto it = ais_for_loading_exec_.find(map); + if (it != ais_for_loading_exec_.end()) return it->second; - ZoneVector access_infos(zone()); AccessInfoFactory access_info_factory(this, dependencies, zone()); - PropertyAccessInfo ai_exec = access_info_factory.ComputePropertyAccessInfo( + auto access_info = access_info_factory.ComputePropertyAccessInfo( map.object(), isolate()->factory()->exec_string(), AccessMode::kLoad); - - auto inserted_ai = ais_for_loading_exec_.insert(std::make_pair(map, ai_exec)); - return inserted_ai.first->second; + return ais_for_loading_exec_.insert({map, access_info}).first->second; } ElementAccessFeedback const* ProcessedFeedback::AsElementAccess() const { diff --git a/src/compiler/js-heap-broker.h b/src/compiler/js-heap-broker.h index ffc10d2b93..cf688798fb 100644 --- a/src/compiler/js-heap-broker.h +++ b/src/compiler/js-heap-broker.h @@ -117,12 +117,15 @@ class V8_EXPORT_PRIVATE JSHeapBroker { // If there is no result stored for {map}, we return an Invalid // PropertyAccessInfo. - PropertyAccessInfo GetAccessInfoForLoadingThen(MapRef map); - void CreateAccessInfoForLoadingThen(MapRef map, - CompilationDependencies* dependencies); PropertyAccessInfo GetAccessInfoForLoadingExec(MapRef map); + PropertyAccessInfo GetAccessInfoForLoadingHasInstance(MapRef map); + PropertyAccessInfo GetAccessInfoForLoadingThen(MapRef map); PropertyAccessInfo const& CreateAccessInfoForLoadingExec( MapRef map, CompilationDependencies* dependencies); + PropertyAccessInfo const& CreateAccessInfoForLoadingHasInstance( + MapRef map, CompilationDependencies* dependencies); + void CreateAccessInfoForLoadingThen(MapRef map, + CompilationDependencies* dependencies); std::ostream& Trace(); void IncrementTracingIndentation(); @@ -156,8 +159,9 @@ class V8_EXPORT_PRIVATE JSHeapBroker { typedef ZoneUnorderedMap MapToAccessInfos; - MapToAccessInfos ais_for_loading_then_; MapToAccessInfos ais_for_loading_exec_; + MapToAccessInfos ais_for_loading_has_instance_; + MapToAccessInfos ais_for_loading_then_; static const size_t kMinimalRefsBucketCount = 8; // must be power of 2 static const size_t kInitialRefsBucketCount = 1024; // must be power of 2 diff --git a/src/compiler/js-native-context-specialization.cc b/src/compiler/js-native-context-specialization.cc index 9fab86ab10..14df07de14 100644 --- a/src/compiler/js-native-context-specialization.cc +++ b/src/compiler/js-native-context-specialization.cc @@ -398,22 +398,31 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) { // we have feedback from the InstanceOfIC. Handle receiver; HeapObjectMatcher m(constructor); - if (m.HasValue() && m.Value()->IsJSObject()) { - receiver = Handle::cast(m.Value()); + if (m.HasValue() && m.Ref(broker()).IsJSObject()) { + receiver = m.Ref(broker()).AsJSObject().object(); } else if (p.feedback().IsValid()) { FeedbackNexus nexus(p.feedback().vector(), p.feedback().slot()); - if (!nexus.GetConstructorFeedback().ToHandle(&receiver)) return NoChange(); + if (!nexus.GetConstructorFeedback().ToHandle(&receiver)) { + return NoChange(); + } } else { return NoChange(); } - Handle receiver_map(receiver->map(), isolate()); - // Compute property access info for @@hasInstance on the constructor. - AccessInfoFactory access_info_factory(broker(), dependencies(), - graph()->zone()); - PropertyAccessInfo access_info = - access_info_factory.ComputePropertyAccessInfo( - receiver_map, factory()->has_instance_symbol(), AccessMode::kLoad); + JSObjectRef receiver_ref(broker(), receiver); + MapRef receiver_map = receiver_ref.map(); + + PropertyAccessInfo access_info = PropertyAccessInfo::Invalid(graph()->zone()); + if (FLAG_concurrent_inlining) { + access_info = broker()->GetAccessInfoForLoadingHasInstance(receiver_map); + } else { + AccessInfoFactory access_info_factory(broker(), dependencies(), + graph()->zone()); + access_info = access_info_factory.ComputePropertyAccessInfo( + receiver_map.object(), factory()->has_instance_symbol(), + AccessMode::kLoad); + } + if (access_info.IsInvalid()) return NoChange(); access_info.RecordDependencies(dependencies()); @@ -422,7 +431,7 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) { if (access_info.IsNotFound()) { // If there's no @@hasInstance handler, the OrdinaryHasInstance operation // takes over, but that requires the constructor to be callable. - if (!receiver_map->is_callable()) return NoChange(); + if (!receiver_map.is_callable()) return NoChange(); dependencies()->DependOnStablePrototypeChains(access_info.receiver_maps(), kStartAtPrototype); @@ -441,17 +450,15 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) { } if (access_info.IsDataConstant()) { - // Determine actual holder. Handle holder; bool found_on_proto = access_info.holder().ToHandle(&holder); - if (!found_on_proto) holder = receiver; - - FieldIndex field_index = access_info.field_index(); - Handle constant = JSObject::FastPropertyAt( - holder, access_info.field_representation(), field_index); - if (!constant->IsCallable()) { + JSObjectRef holder_ref = + found_on_proto ? JSObjectRef(broker(), holder) : receiver_ref; + base::Optional constant = holder_ref.GetOwnDataProperty( + access_info.field_representation(), access_info.field_index()); + if (!constant.has_value() || !constant->IsHeapObject() || + !constant->AsHeapObject().map().is_callable()) return NoChange(); - } if (found_on_proto) { dependencies()->DependOnStablePrototypeChains( @@ -459,8 +466,6 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) { JSObjectRef(broker(), holder)); } - DCHECK(constant->IsCallable()); - // Check that {constructor} is actually {receiver}. constructor = access_builder.BuildCheckValue(constructor, &effect, control, receiver); @@ -480,7 +485,7 @@ Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) { 0, frame_state, ContinuationFrameStateMode::LAZY); // Call the @@hasInstance handler. - Node* target = jsgraph()->Constant(constant); + Node* target = jsgraph()->Constant(*constant); node->InsertInput(graph()->zone(), 0, target); node->ReplaceInput(1, constructor); node->ReplaceInput(2, object); @@ -615,7 +620,7 @@ Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance( // of the instanceof operator again. JSBoundFunctionRef function = m.Ref(broker()).AsJSBoundFunction(); if (FLAG_concurrent_inlining && !function.serialized()) { - TRACE_BROKER_MISSING(broker(), "data for function " << function); + TRACE_BROKER_MISSING(broker(), "data for JSBoundFunction " << function); return NoChange(); } @@ -634,7 +639,7 @@ Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance( JSFunctionRef function = m.Ref(broker()).AsJSFunction(); if (FLAG_concurrent_inlining && !function.serialized()) { - TRACE_BROKER_MISSING(broker(), "data for function " << function); + TRACE_BROKER_MISSING(broker(), "data for JSFunction " << function); return NoChange(); } diff --git a/src/compiler/serializer-for-background-compilation.cc b/src/compiler/serializer-for-background-compilation.cc index e9bb6f041b..3157886913 100644 --- a/src/compiler/serializer-for-background-compilation.cc +++ b/src/compiler/serializer-for-background-compilation.cc @@ -373,11 +373,17 @@ class SerializerForBackgroundCompilation { FeedbackSlot slot, AccessMode mode); void ProcessMapHintsForPromises(Hints const& receiver_hints); void ProcessHintsForPromiseResolve(Hints const& resolution_hints); - void ProcessHintsForObjectIsPrototypeOf(Hints const& value_hints); + void ProcessHintsForHasInPrototypeChain(Hints const& instance_hints); void ProcessHintsForRegExpTest(Hints const& regexp_hints); PropertyAccessInfo ProcessMapForRegExpTest(MapRef map); void ProcessHintsForFunctionCall(Hints const& target_hints); void ProcessHintsForFunctionBind(Hints const& receiver_hints); + void ProcessConstantForOrdinaryHasInstance(HeapObjectRef const& constructor, + bool* walk_prototypes); + void ProcessConstantForInstanceOf(ObjectRef const& constant, + bool* walk_prototypes); + void ProcessHintsForOrdinaryHasInstance(Hints const& constructor_hints, + Hints const& instance_hints); GlobalAccessFeedback const* ProcessFeedbackForGlobalAccess(FeedbackSlot slot); NamedAccessFeedback const* ProcessFeedbackMapsForNamedAccess( @@ -1676,14 +1682,19 @@ void SerializerForBackgroundCompilation::ProcessBuiltinCall( break; case Builtins::kObjectPrototypeIsPrototypeOf: if (arguments.size() >= 2) { - ProcessHintsForObjectIsPrototypeOf(arguments[1]); + ProcessHintsForHasInPrototypeChain(arguments[1]); + } + break; + case Builtins::kFunctionPrototypeHasInstance: + // For JSCallReducer::ReduceFunctionPrototypeHasInstance. + if (arguments.size() >= 2) { + ProcessHintsForOrdinaryHasInstance(arguments[0], arguments[1]); } break; case Builtins::kFastFunctionPrototypeBind: - if (arguments.size() >= 2 && + if (arguments.size() >= 1 && speculation_mode != SpeculationMode::kDisallowSpeculation) { - Hints const& receiver_hints = arguments[1]; - ProcessHintsForFunctionBind(receiver_hints); + ProcessHintsForFunctionBind(arguments[0]); } break; default: @@ -1691,8 +1702,22 @@ void SerializerForBackgroundCompilation::ProcessBuiltinCall( } } -void SerializerForBackgroundCompilation::ProcessHintsForObjectIsPrototypeOf( - Hints const& value_hints) { +void SerializerForBackgroundCompilation::ProcessHintsForOrdinaryHasInstance( + Hints const& constructor_hints, Hints const& instance_hints) { + bool walk_prototypes = false; + for (Handle constructor : constructor_hints.constants()) { + // For JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance. + if (constructor->IsHeapObject()) { + ProcessConstantForOrdinaryHasInstance( + HeapObjectRef(broker(), constructor), &walk_prototypes); + } + } + // For JSNativeContextSpecialization::ReduceJSHasInPrototypeChain. + if (walk_prototypes) ProcessHintsForHasInPrototypeChain(instance_hints); +} + +void SerializerForBackgroundCompilation::ProcessHintsForHasInPrototypeChain( + Hints const& instance_hints) { auto processMap = [&](Handle map_handle) { MapRef map(broker(), map_handle); while (map.IsJSObjectMap()) { @@ -1701,12 +1726,12 @@ void SerializerForBackgroundCompilation::ProcessHintsForObjectIsPrototypeOf( } }; - for (auto hint : value_hints.constants()) { + for (auto hint : instance_hints.constants()) { if (!hint->IsHeapObject()) continue; Handle object(Handle::cast(hint)); processMap(handle(object->map(), broker()->isolate())); } - for (auto map_hint : value_hints.maps()) { + for (auto map_hint : instance_hints.maps()) { processMap(map_hint); } } @@ -2237,36 +2262,84 @@ void SerializerForBackgroundCompilation::VisitTestIn( ProcessKeyedPropertyAccess(receiver, key, slot, AccessMode::kHas); } +// For JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance. +void SerializerForBackgroundCompilation::ProcessConstantForOrdinaryHasInstance( + HeapObjectRef const& constructor, bool* walk_prototypes) { + if (constructor.IsJSBoundFunction()) { + constructor.AsJSBoundFunction().Serialize(); + ProcessConstantForInstanceOf( + constructor.AsJSBoundFunction().bound_target_function(), + walk_prototypes); + } else if (constructor.IsJSFunction()) { + constructor.AsJSFunction().Serialize(); + *walk_prototypes = + *walk_prototypes || + (constructor.map().has_prototype_slot() && + constructor.AsJSFunction().has_prototype() && + !constructor.AsJSFunction().PrototypeRequiresRuntimeLookup()); + } +} + +void SerializerForBackgroundCompilation::ProcessConstantForInstanceOf( + ObjectRef const& constructor, bool* walk_prototypes) { + if (!constructor.IsHeapObject()) return; + HeapObjectRef constructor_heap_object = constructor.AsHeapObject(); + + PropertyAccessInfo const& access_info = + broker()->CreateAccessInfoForLoadingHasInstance( + constructor_heap_object.map(), dependencies()); + + if (access_info.IsNotFound()) { + ProcessConstantForOrdinaryHasInstance(constructor_heap_object, + walk_prototypes); + } else if (access_info.IsDataConstant()) { + Handle holder; + bool found_on_proto = access_info.holder().ToHandle(&holder); + JSObjectRef holder_ref = found_on_proto ? JSObjectRef(broker(), holder) + : constructor.AsJSObject(); + base::Optional constant = holder_ref.GetOwnDataProperty( + access_info.field_representation(), access_info.field_index(), true); + CHECK(constant.has_value()); + if (constant->IsJSFunction()) { + JSFunctionRef function = constant->AsJSFunction(); + function.Serialize(); + if (function.shared().HasBuiltinId() && + function.shared().builtin_id() == + Builtins::kFunctionPrototypeHasInstance) { + // For JSCallReducer::ReduceFunctionPrototypeHasInstance. + ProcessConstantForOrdinaryHasInstance(constructor_heap_object, + walk_prototypes); + } + } + } +} + void SerializerForBackgroundCompilation::VisitTestInstanceOf( BytecodeArrayIterator* iterator) { Hints const& lhs = environment()->register_hints(iterator->GetRegisterOperand(0)); - Hints const& rhs = environment()->accumulator_hints(); + Hints& rhs = environment()->accumulator_hints(); FeedbackSlot slot = iterator->GetSlotOperand(1); - // Inspect feedback (about the rhs of the operator). - Handle feedback_vector = - environment()->function().feedback_vector(); - FeedbackNexus nexus(feedback_vector, slot); - VectorSlotPair rhs_feedback(feedback_vector, slot, nexus.ic_state()); - Handle constructor; - if (rhs_feedback.IsValid() && - nexus.GetConstructorFeedback().ToHandle(&constructor)) { - if (constructor->IsJSBoundFunction()) { - JSBoundFunctionRef(broker(), constructor).Serialize(); - } else if (constructor->IsJSFunction()) { - JSFunctionRef(broker(), constructor).Serialize(); - } else { - UNREACHABLE(); + // Incorporate feedback (about the rhs of the operator) into hints. + { + Handle feedback_vector = + environment()->function().feedback_vector(); + FeedbackNexus nexus(feedback_vector, slot); + VectorSlotPair rhs_feedback(feedback_vector, slot, nexus.ic_state()); + Handle constructor; + if (rhs_feedback.IsValid() && + nexus.GetConstructorFeedback().ToHandle(&constructor)) { + rhs.AddConstant(constructor); } } - // TODO(neis): Support full instanceof semantics. - - ProcessHintsForObjectIsPrototypeOf(lhs); - - // TODO(neis): Process rhs hints. - USE(rhs); + bool walk_prototypes = false; + for (Handle constant : rhs.constants()) { + ProcessConstantForInstanceOf(ObjectRef(broker(), constant), + &walk_prototypes); + } + if (walk_prototypes) ProcessHintsForHasInPrototypeChain(lhs); } void SerializerForBackgroundCompilation::VisitStaKeyedProperty( diff --git a/test/mjsunit/compiler/instanceof4.js b/test/mjsunit/compiler/instanceof4.js new file mode 100644 index 0000000000..5074fb5f64 --- /dev/null +++ b/test/mjsunit/compiler/instanceof4.js @@ -0,0 +1,80 @@ +// 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 testFunctionPrototypeHasInstance() { + class A {}; + var a = new A; + + function foo() { + return A[Symbol.hasInstance](a); + }; + + %PrepareFunctionForOptimization(foo); + assertTrue(foo()); + assertTrue(foo()); + %OptimizeFunctionOnNextCall(foo); + assertTrue(foo()); +})(); + + +(function testFunctionPrototypeHasInstanceWithInference() { + class A {}; + var a = new A; + a.bla = 42; + + function foo() { + a.bla; + return A[Symbol.hasInstance](a); + }; + + %PrepareFunctionForOptimization(foo); + assertTrue(foo()); + assertTrue(foo()); + %OptimizeFunctionOnNextCall(foo); + assertTrue(foo()); +})(); + + +(function testFunctionPrototypeHasInstanceWithBoundFunction() { + class A {}; + var a = new A; + var f = A.bind({}); + + function foo() { + return f[Symbol.hasInstance](a); + }; + + %PrepareFunctionForOptimization(foo); + assertTrue(foo()); + assertTrue(foo()); + %OptimizeFunctionOnNextCall(foo); + assertTrue(foo()); + + // JSCallReducer::ReduceFunctionPrototypeHasInstance -> + // JSNative...::ReduceJSOrdinaryHasInstance -> + // JSNative...::ReduceJSInstanceOf (on bound_target_function) + // ~~~~~> + // JSCallReducer::ReduceFunctionPrototypeHasInstance + // JSNative...::ReduceJSOrdinaryHasInstance -> + // JSNative...::ReduceJSHasInPrototypeChain +})(); + + +(function testSimpleInstanceOf() { + class A {}; + var a = new A; + + function foo() { + return a instanceof A; + }; + + %PrepareFunctionForOptimization(foo); + assertTrue(foo()); + assertTrue(foo()); + %OptimizeFunctionOnNextCall(foo); + assertTrue(foo()); +})();