| // Copyright 2018 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. |
| |
| #ifndef V8_COMPILER_JS_HEAP_BROKER_H_ |
| #define V8_COMPILER_JS_HEAP_BROKER_H_ |
| |
| #include "src/base/compiler-specific.h" |
| #include "src/base/optional.h" |
| #include "src/common/globals.h" |
| #include "src/compiler/access-info.h" |
| #include "src/compiler/feedback-source.h" |
| #include "src/compiler/globals.h" |
| #include "src/compiler/processed-feedback.h" |
| #include "src/compiler/refs-map.h" |
| #include "src/compiler/serializer-hints.h" |
| #include "src/execution/local-isolate.h" |
| #include "src/handles/handles.h" |
| #include "src/handles/persistent-handles.h" |
| #include "src/heap/local-heap.h" |
| #include "src/interpreter/bytecode-array-accessor.h" |
| #include "src/objects/code-kind.h" |
| #include "src/objects/feedback-vector.h" |
| #include "src/objects/function-kind.h" |
| #include "src/objects/objects.h" |
| #include "src/utils/address-map.h" |
| #include "src/utils/identity-map.h" |
| #include "src/utils/ostreams.h" |
| #include "src/zone/zone-containers.h" |
| |
| namespace v8 { |
| namespace internal { |
| namespace compiler { |
| |
| class BytecodeAnalysis; |
| class ObjectRef; |
| |
| std::ostream& operator<<(std::ostream& os, const ObjectRef& ref); |
| |
| #define TRACE_BROKER(broker, x) \ |
| do { \ |
| if (broker->tracing_enabled() && FLAG_trace_heap_broker_verbose) \ |
| StdoutStream{} << broker->Trace() << x << '\n'; \ |
| } while (false) |
| |
| #define TRACE_BROKER_MEMORY(broker, x) \ |
| do { \ |
| if (broker->tracing_enabled() && FLAG_trace_heap_broker_memory) \ |
| StdoutStream{} << broker->Trace() << x << std::endl; \ |
| } while (false) |
| |
| #define TRACE_BROKER_MISSING(broker, x) \ |
| do { \ |
| if (broker->tracing_enabled()) \ |
| StdoutStream{} << broker->Trace() << "Missing " << x << " (" << __FILE__ \ |
| << ":" << __LINE__ << ")" << std::endl; \ |
| } while (false) |
| |
| struct PropertyAccessTarget { |
| MapRef map; |
| NameRef name; |
| AccessMode mode; |
| |
| struct Hash { |
| size_t operator()(const PropertyAccessTarget& pair) const { |
| return base::hash_combine( |
| base::hash_combine(pair.map.object().address(), |
| pair.name.object().address()), |
| static_cast<int>(pair.mode)); |
| } |
| }; |
| struct Equal { |
| bool operator()(const PropertyAccessTarget& lhs, |
| const PropertyAccessTarget& rhs) const { |
| return lhs.map.equals(rhs.map) && lhs.name.equals(rhs.name) && |
| lhs.mode == rhs.mode; |
| } |
| }; |
| }; |
| |
| class V8_EXPORT_PRIVATE JSHeapBroker { |
| public: |
| JSHeapBroker(Isolate* isolate, Zone* broker_zone, bool tracing_enabled, |
| bool is_concurrent_inlining, CodeKind code_kind); |
| |
| // For use only in tests, sets default values for some arguments. Avoids |
| // churn when new flags are added. |
| JSHeapBroker(Isolate* isolate, Zone* broker_zone) |
| : JSHeapBroker(isolate, broker_zone, FLAG_trace_heap_broker, false, |
| CodeKind::TURBOFAN) {} |
| |
| ~JSHeapBroker(); |
| |
| // The compilation target's native context. We need the setter because at |
| // broker construction time we don't yet have the canonical handle. |
| NativeContextRef target_native_context() const { |
| return target_native_context_.value(); |
| } |
| void SetTargetNativeContextRef(Handle<NativeContext> native_context); |
| |
| void InitializeAndStartSerializing(Handle<NativeContext> native_context); |
| |
| Isolate* isolate() const { return isolate_; } |
| Zone* zone() const { return zone_; } |
| bool tracing_enabled() const { return tracing_enabled_; } |
| bool is_concurrent_inlining() const { return is_concurrent_inlining_; } |
| bool is_native_context_independent() const { |
| return code_kind_ == CodeKind::NATIVE_CONTEXT_INDEPENDENT; |
| } |
| bool generate_full_feedback_collection() const { |
| // NCI code currently collects full feedback. |
| DCHECK_IMPLIES(is_native_context_independent(), |
| CollectFeedbackInGenericLowering()); |
| return is_native_context_independent(); |
| } |
| bool is_turboprop() const { return code_kind_ == CodeKind::TURBOPROP; } |
| |
| NexusConfig feedback_nexus_config() const { |
| // TODO(mvstanton): when the broker gathers feedback on the background |
| // thread, this should return a local NexusConfig object which points |
| // to the associated LocalHeap. |
| return NexusConfig::FromMainThread(isolate()); |
| } |
| |
| enum BrokerMode { kDisabled, kSerializing, kSerialized, kRetired }; |
| BrokerMode mode() const { return mode_; } |
| |
| void StopSerializing(); |
| void Retire(); |
| bool SerializingAllowed() const; |
| |
| // Remember the local isolate and initialize its local heap with the |
| // persistent and canonical handles provided by {info}. |
| void AttachLocalIsolate(OptimizedCompilationInfo* info, |
| LocalIsolate* local_isolate); |
| // Forget about the local isolate and pass the persistent and canonical |
| // handles provided back to {info}. {info} is responsible for disposing of |
| // them. |
| void DetachLocalIsolate(OptimizedCompilationInfo* info); |
| |
| bool StackHasOverflowed() const; |
| |
| #ifdef DEBUG |
| void PrintRefsAnalysis() const; |
| #endif // DEBUG |
| |
| // Retruns the handle from root index table for read only heap objects. |
| Handle<Object> GetRootHandle(Object object); |
| |
| // Never returns nullptr. |
| ObjectData* GetOrCreateData(Handle<Object>); |
| // Like the previous but wraps argument in handle first (for convenience). |
| ObjectData* GetOrCreateData(Object); |
| |
| // Check if {object} is any native context's %ArrayPrototype% or |
| // %ObjectPrototype%. |
| bool IsArrayOrObjectPrototype(const JSObjectRef& object) const; |
| |
| bool HasFeedback(FeedbackSource const& source) const; |
| void SetFeedback(FeedbackSource const& source, |
| ProcessedFeedback const* feedback); |
| ProcessedFeedback const& GetFeedback(FeedbackSource const& source) const; |
| FeedbackSlotKind GetFeedbackSlotKind(FeedbackSource const& source) const; |
| |
| // TODO(neis): Move these into serializer when we're always in the background. |
| ElementAccessFeedback const& ProcessFeedbackMapsForElementAccess( |
| MapHandles const& maps, KeyedAccessMode const& keyed_mode, |
| FeedbackSlotKind slot_kind); |
| BytecodeAnalysis const& GetBytecodeAnalysis( |
| Handle<BytecodeArray> bytecode_array, BailoutId osr_offset, |
| bool analyze_liveness, |
| SerializationPolicy policy = SerializationPolicy::kAssumeSerialized); |
| |
| // Binary, comparison and for-in hints can be fully expressed via |
| // an enum. Insufficient feedback is signaled by <Hint enum>::kNone. |
| BinaryOperationHint GetFeedbackForBinaryOperation( |
| FeedbackSource const& source); |
| CompareOperationHint GetFeedbackForCompareOperation( |
| FeedbackSource const& source); |
| ForInHint GetFeedbackForForIn(FeedbackSource const& source); |
| |
| ProcessedFeedback const& GetFeedbackForCall(FeedbackSource const& source); |
| ProcessedFeedback const& GetFeedbackForGlobalAccess( |
| FeedbackSource const& source); |
| ProcessedFeedback const& GetFeedbackForInstanceOf( |
| FeedbackSource const& source); |
| ProcessedFeedback const& GetFeedbackForArrayOrObjectLiteral( |
| FeedbackSource const& source); |
| ProcessedFeedback const& GetFeedbackForRegExpLiteral( |
| FeedbackSource const& source); |
| ProcessedFeedback const& GetFeedbackForTemplateObject( |
| FeedbackSource const& source); |
| ProcessedFeedback const& GetFeedbackForPropertyAccess( |
| FeedbackSource const& source, AccessMode mode, |
| base::Optional<NameRef> static_name); |
| |
| ProcessedFeedback const& ProcessFeedbackForBinaryOperation( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForCall(FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForCompareOperation( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForForIn( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForGlobalAccess( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForInstanceOf( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForPropertyAccess( |
| FeedbackSource const& source, AccessMode mode, |
| base::Optional<NameRef> static_name); |
| ProcessedFeedback const& ProcessFeedbackForArrayOrObjectLiteral( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForRegExpLiteral( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ProcessFeedbackForTemplateObject( |
| FeedbackSource const& source); |
| |
| bool FeedbackIsInsufficient(FeedbackSource const& source) const; |
| |
| base::Optional<NameRef> GetNameFeedback(FeedbackNexus const& nexus); |
| |
| // If {policy} is {kAssumeSerialized} and the broker doesn't know about the |
| // combination of {map}, {name}, and {access_mode}, returns Invalid. |
| PropertyAccessInfo GetPropertyAccessInfo( |
| MapRef map, NameRef name, AccessMode access_mode, |
| CompilationDependencies* dependencies = nullptr, |
| SerializationPolicy policy = SerializationPolicy::kAssumeSerialized); |
| |
| MinimorphicLoadPropertyAccessInfo GetPropertyAccessInfo( |
| MinimorphicLoadPropertyAccessFeedback const& feedback, |
| FeedbackSource const& source, |
| SerializationPolicy policy = SerializationPolicy::kAssumeSerialized); |
| |
| StringRef GetTypedArrayStringTag(ElementsKind kind); |
| |
| bool ShouldBeSerializedForCompilation(const SharedFunctionInfoRef& shared, |
| const FeedbackVectorRef& feedback, |
| const HintsVector& arguments) const; |
| void SetSerializedForCompilation(const SharedFunctionInfoRef& shared, |
| const FeedbackVectorRef& feedback, |
| const HintsVector& arguments); |
| bool IsSerializedForCompilation(const SharedFunctionInfoRef& shared, |
| const FeedbackVectorRef& feedback) const; |
| |
| LocalIsolate* local_isolate() const { return local_isolate_; } |
| |
| // Return the corresponding canonical persistent handle for {object}. Create |
| // one if it does not exist. |
| // If we have the canonical map, we can create the canonical & persistent |
| // handle through it. This commonly happens during the Execute phase. |
| // If we don't, that means we are calling this method from serialization. If |
| // that happens, we should be inside a canonical and a persistent handle |
| // scope. Then, we would just use the regular handle creation. |
| template <typename T> |
| Handle<T> CanonicalPersistentHandle(T object) { |
| if (canonical_handles_) { |
| Address address = object.ptr(); |
| if (Internals::HasHeapObjectTag(address)) { |
| RootIndex root_index; |
| if (root_index_map_.Lookup(address, &root_index)) { |
| return Handle<T>(isolate_->root_handle(root_index).location()); |
| } |
| } |
| |
| Object obj(address); |
| auto find_result = canonical_handles_->FindOrInsert(obj); |
| if (!find_result.already_exists) { |
| // Allocate new PersistentHandle if one wasn't created before. |
| DCHECK_NOT_NULL(local_isolate()); |
| *find_result.entry = |
| local_isolate()->heap()->NewPersistentHandle(obj).location(); |
| } |
| return Handle<T>(*find_result.entry); |
| } else { |
| return Handle<T>(object, isolate()); |
| } |
| } |
| |
| template <typename T> |
| Handle<T> CanonicalPersistentHandle(Handle<T> object) { |
| return CanonicalPersistentHandle<T>(*object); |
| } |
| |
| // Find the corresponding handle in the CanonicalHandlesMap. The entry must be |
| // found. |
| template <typename T> |
| Handle<T> FindCanonicalPersistentHandleForTesting(Object object) { |
| Address** entry = canonical_handles_->Find(object); |
| return Handle<T>(*entry); |
| } |
| |
| // Set the persistent handles and copy the canonical handles over to the |
| // JSHeapBroker. |
| void SetPersistentAndCopyCanonicalHandlesForTesting( |
| std::unique_ptr<PersistentHandles> persistent_handles, |
| std::unique_ptr<CanonicalHandlesMap> canonical_handles); |
| std::string Trace() const; |
| void IncrementTracingIndentation(); |
| void DecrementTracingIndentation(); |
| |
| RootIndexMap const& root_index_map() { return root_index_map_; } |
| |
| private: |
| friend class HeapObjectRef; |
| friend class ObjectRef; |
| friend class ObjectData; |
| |
| bool CanUseFeedback(const FeedbackNexus& nexus) const; |
| const ProcessedFeedback& NewInsufficientFeedback(FeedbackSlotKind kind) const; |
| |
| // Bottleneck FeedbackNexus access here, for storage in the broker |
| // or on-the-fly usage elsewhere in the compiler. |
| ProcessedFeedback const& ReadFeedbackForArrayOrObjectLiteral( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ReadFeedbackForBinaryOperation( |
| FeedbackSource const& source) const; |
| ProcessedFeedback const& ReadFeedbackForCall(FeedbackSource const& source); |
| ProcessedFeedback const& ReadFeedbackForCompareOperation( |
| FeedbackSource const& source) const; |
| ProcessedFeedback const& ReadFeedbackForForIn( |
| FeedbackSource const& source) const; |
| ProcessedFeedback const& ReadFeedbackForGlobalAccess( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ReadFeedbackForInstanceOf( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ReadFeedbackForPropertyAccess( |
| FeedbackSource const& source, AccessMode mode, |
| base::Optional<NameRef> static_name); |
| ProcessedFeedback const& ReadFeedbackForRegExpLiteral( |
| FeedbackSource const& source); |
| ProcessedFeedback const& ReadFeedbackForTemplateObject( |
| FeedbackSource const& source); |
| |
| void CollectArrayAndObjectPrototypes(); |
| |
| PerIsolateCompilerCache* compiler_cache() const { return compiler_cache_; } |
| |
| void set_persistent_handles( |
| std::unique_ptr<PersistentHandles> persistent_handles) { |
| DCHECK_NULL(ph_); |
| ph_ = std::move(persistent_handles); |
| DCHECK_NOT_NULL(ph_); |
| } |
| std::unique_ptr<PersistentHandles> DetachPersistentHandles() { |
| DCHECK_NOT_NULL(ph_); |
| return std::move(ph_); |
| } |
| |
| void set_canonical_handles( |
| std::unique_ptr<CanonicalHandlesMap> canonical_handles) { |
| DCHECK_NULL(canonical_handles_); |
| canonical_handles_ = std::move(canonical_handles); |
| DCHECK_NOT_NULL(canonical_handles_); |
| } |
| |
| std::unique_ptr<CanonicalHandlesMap> DetachCanonicalHandles() { |
| DCHECK_NOT_NULL(canonical_handles_); |
| return std::move(canonical_handles_); |
| } |
| |
| // Copy the canonical handles over to the JSHeapBroker. |
| void CopyCanonicalHandlesForTesting( |
| std::unique_ptr<CanonicalHandlesMap> canonical_handles); |
| |
| Isolate* const isolate_; |
| Zone* const zone_ = nullptr; |
| base::Optional<NativeContextRef> target_native_context_; |
| RefsMap* refs_; |
| RootIndexMap root_index_map_; |
| ZoneUnorderedSet<Handle<JSObject>, Handle<JSObject>::hash, |
| Handle<JSObject>::equal_to> |
| array_and_object_prototypes_; |
| BrokerMode mode_ = kDisabled; |
| bool const tracing_enabled_; |
| bool const is_concurrent_inlining_; |
| CodeKind const code_kind_; |
| std::unique_ptr<PersistentHandles> ph_; |
| LocalIsolate* local_isolate_ = nullptr; |
| std::unique_ptr<CanonicalHandlesMap> canonical_handles_; |
| unsigned trace_indentation_ = 0; |
| PerIsolateCompilerCache* compiler_cache_ = nullptr; |
| ZoneUnorderedMap<FeedbackSource, ProcessedFeedback const*, |
| FeedbackSource::Hash, FeedbackSource::Equal> |
| feedback_; |
| ZoneUnorderedMap<ObjectData*, BytecodeAnalysis*> bytecode_analyses_; |
| ZoneUnorderedMap<PropertyAccessTarget, PropertyAccessInfo, |
| PropertyAccessTarget::Hash, PropertyAccessTarget::Equal> |
| property_access_infos_; |
| ZoneUnorderedMap<FeedbackSource, MinimorphicLoadPropertyAccessInfo, |
| FeedbackSource::Hash, FeedbackSource::Equal> |
| minimorphic_property_access_infos_; |
| |
| ZoneVector<ObjectData*> typed_array_string_tags_; |
| |
| struct SerializedFunction { |
| SharedFunctionInfoRef shared; |
| FeedbackVectorRef feedback; |
| |
| bool operator<(const SerializedFunction& other) const { |
| if (shared.object().address() < other.shared.object().address()) { |
| return true; |
| } |
| if (shared.object().address() == other.shared.object().address()) { |
| return feedback.object().address() < other.feedback.object().address(); |
| } |
| return false; |
| } |
| }; |
| ZoneMultimap<SerializedFunction, HintsVector> serialized_functions_; |
| |
| static const size_t kMaxSerializedFunctionsCacheSize = 200; |
| static const uint32_t kMinimalRefsBucketCount = 8; // must be power of 2 |
| static const uint32_t kInitialRefsBucketCount = 1024; // must be power of 2 |
| }; |
| |
| class TraceScope { |
| public: |
| TraceScope(JSHeapBroker* broker, const char* label) |
| : TraceScope(broker, static_cast<void*>(broker), label) {} |
| |
| TraceScope(JSHeapBroker* broker, ObjectData* data, const char* label) |
| : TraceScope(broker, static_cast<void*>(data), label) {} |
| |
| TraceScope(JSHeapBroker* broker, void* subject, const char* label) |
| : broker_(broker) { |
| TRACE_BROKER(broker_, "Running " << label << " on " << subject); |
| broker_->IncrementTracingIndentation(); |
| } |
| |
| ~TraceScope() { broker_->DecrementTracingIndentation(); } |
| |
| private: |
| JSHeapBroker* const broker_; |
| }; |
| |
| #define ASSIGN_RETURN_NO_CHANGE_IF_DATA_MISSING(something_var, \ |
| optionally_something) \ |
| auto optionally_something_ = optionally_something; \ |
| if (!optionally_something_) \ |
| return NoChangeBecauseOfMissingData(broker(), __FUNCTION__, __LINE__); \ |
| something_var = *optionally_something_; |
| |
| class Reduction; |
| Reduction NoChangeBecauseOfMissingData(JSHeapBroker* broker, |
| const char* function, int line); |
| |
| // Miscellaneous definitions that should be moved elsewhere once concurrent |
| // compilation is finished. |
| bool CanInlineElementAccess(MapRef const& map); |
| |
| class OffHeapBytecodeArray final : public interpreter::AbstractBytecodeArray { |
| public: |
| explicit OffHeapBytecodeArray(BytecodeArrayRef bytecode_array); |
| |
| int length() const override; |
| int parameter_count() const override; |
| uint8_t get(int index) const override; |
| void set(int index, uint8_t value) override; |
| Address GetFirstBytecodeAddress() const override; |
| Handle<Object> GetConstantAtIndex(int index, Isolate* isolate) const override; |
| bool IsConstantAtIndexSmi(int index) const override; |
| Smi GetConstantAtIndexAsSmi(int index) const override; |
| |
| private: |
| BytecodeArrayRef array_; |
| }; |
| |
| // Scope that unparks the LocalHeap, if: |
| // a) We have a JSHeapBroker, |
| // b) Said JSHeapBroker has a LocalIsolate and thus a LocalHeap, |
| // c) Said LocalHeap has been parked and |
| // d) The given condition evaluates to true. |
| // Used, for example, when printing the graph with --trace-turbo with a |
| // previously parked LocalHeap. |
| class UnparkedScopeIfNeeded { |
| public: |
| explicit UnparkedScopeIfNeeded(JSHeapBroker* broker, |
| bool extra_condition = true) { |
| if (broker != nullptr && extra_condition) { |
| LocalIsolate* local_isolate = broker->local_isolate(); |
| if (local_isolate != nullptr && local_isolate->heap()->IsParked()) { |
| unparked_scope.emplace(local_isolate->heap()); |
| } |
| } |
| } |
| |
| private: |
| base::Optional<UnparkedScope> unparked_scope; |
| }; |
| |
| } // namespace compiler |
| } // namespace internal |
| } // namespace v8 |
| |
| #endif // V8_COMPILER_JS_HEAP_BROKER_H_ |