|  | // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file | 
|  | // for details. All rights reserved. Use of this source code is governed by a | 
|  | // BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | #ifndef RUNTIME_VM_HEAP_SCAVENGER_H_ | 
|  | #define RUNTIME_VM_HEAP_SCAVENGER_H_ | 
|  |  | 
|  | #include "platform/assert.h" | 
|  | #include "platform/utils.h" | 
|  |  | 
|  | #include "vm/dart.h" | 
|  | #include "vm/flags.h" | 
|  | #include "vm/globals.h" | 
|  | #include "vm/heap/spaces.h" | 
|  | #include "vm/isolate.h" | 
|  | #include "vm/lockers.h" | 
|  | #include "vm/raw_object.h" | 
|  | #include "vm/ring_buffer.h" | 
|  | #include "vm/virtual_memory.h" | 
|  | #include "vm/visitor.h" | 
|  |  | 
|  | namespace dart { | 
|  |  | 
|  | // Forward declarations. | 
|  | class Heap; | 
|  | class Isolate; | 
|  | class JSONObject; | 
|  | class ObjectSet; | 
|  | template <bool parallel> | 
|  | class ScavengerVisitorBase; | 
|  |  | 
|  | static constexpr intptr_t kNewPageSize = 512 * KB; | 
|  | static constexpr intptr_t kNewPageSizeInWords = kNewPageSize / kWordSize; | 
|  | static constexpr intptr_t kNewPageMask = ~(kNewPageSize - 1); | 
|  |  | 
|  | // A page containing new generation objects. | 
|  | class NewPage { | 
|  | public: | 
|  | static NewPage* Allocate(); | 
|  | void Deallocate(); | 
|  |  | 
|  | uword start() const { return memory_->start(); } | 
|  | uword end() const { return memory_->end(); } | 
|  | bool Contains(uword addr) const { return memory_->Contains(addr); } | 
|  | void WriteProtect(bool read_only) { | 
|  | memory_->Protect(read_only ? VirtualMemory::kReadOnly | 
|  | : VirtualMemory::kReadWrite); | 
|  | } | 
|  |  | 
|  | NewPage* next() const { return next_; } | 
|  | void set_next(NewPage* next) { next_ = next; } | 
|  |  | 
|  | Thread* owner() const { return owner_; } | 
|  |  | 
|  | uword object_start() const { return start() + ObjectStartOffset(); } | 
|  | uword object_end() const { return owner_ != nullptr ? owner_->top() : top_; } | 
|  | void VisitObjects(ObjectVisitor* visitor) const { | 
|  | uword addr = object_start(); | 
|  | uword end = object_end(); | 
|  | while (addr < end) { | 
|  | ObjectPtr obj = UntaggedObject::FromAddr(addr); | 
|  | visitor->VisitObject(obj); | 
|  | addr += obj->untag()->HeapSize(); | 
|  | } | 
|  | } | 
|  | void VisitObjectPointers(ObjectPointerVisitor* visitor) const { | 
|  | uword addr = object_start(); | 
|  | uword end = object_end(); | 
|  | while (addr < end) { | 
|  | ObjectPtr obj = UntaggedObject::FromAddr(addr); | 
|  | intptr_t size = obj->untag()->VisitPointers(visitor); | 
|  | addr += size; | 
|  | } | 
|  | } | 
|  |  | 
|  | static intptr_t ObjectStartOffset() { | 
|  | return Utils::RoundUp(sizeof(NewPage), kObjectAlignment) + | 
|  | kNewObjectAlignmentOffset; | 
|  | } | 
|  |  | 
|  | static NewPage* Of(ObjectPtr obj) { | 
|  | ASSERT(obj->IsHeapObject()); | 
|  | ASSERT(obj->IsNewObject()); | 
|  | return Of(static_cast<uword>(obj)); | 
|  | } | 
|  | static NewPage* Of(uword addr) { | 
|  | return reinterpret_cast<NewPage*>(addr & kNewPageMask); | 
|  | } | 
|  |  | 
|  | // Remember the limit to which objects have been copied. | 
|  | void RecordSurvivors() { survivor_end_ = object_end(); } | 
|  |  | 
|  | // Move survivor end to the end of the to_ space, making all surviving | 
|  | // objects candidates for promotion next time. | 
|  | void EarlyTenure() { survivor_end_ = end_; } | 
|  |  | 
|  | uword promo_candidate_words() const { | 
|  | return (survivor_end_ - object_start()) / kWordSize; | 
|  | } | 
|  |  | 
|  | void Acquire(Thread* thread) { | 
|  | ASSERT(owner_ == nullptr); | 
|  | owner_ = thread; | 
|  | thread->set_top(top_); | 
|  | thread->set_end(end_); | 
|  | } | 
|  | void Release(Thread* thread) { | 
|  | ASSERT(owner_ == thread); | 
|  | owner_ = nullptr; | 
|  | top_ = thread->top(); | 
|  | thread->set_top(0); | 
|  | thread->set_end(0); | 
|  | } | 
|  | void Release() { | 
|  | if (owner_ != nullptr) { | 
|  | Release(owner_); | 
|  | } | 
|  | } | 
|  |  | 
|  | uword TryAllocateGC(intptr_t size) { | 
|  | ASSERT(owner_ == nullptr); | 
|  | uword result = top_; | 
|  | uword new_top = result + size; | 
|  | if (LIKELY(new_top <= end_)) { | 
|  | top_ = new_top; | 
|  | return result; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void Unallocate(uword addr, intptr_t size) { | 
|  | ASSERT((addr + size) == top_); | 
|  | top_ -= size; | 
|  | } | 
|  |  | 
|  | bool IsSurvivor(uword raw_addr) const { return raw_addr < survivor_end_; } | 
|  | bool IsResolved() const { return top_ == resolved_top_; } | 
|  |  | 
|  | private: | 
|  | VirtualMemory* memory_; | 
|  | NewPage* next_; | 
|  |  | 
|  | // The thread using this page for allocation, otherwise NULL. | 
|  | Thread* owner_; | 
|  |  | 
|  | // The address of the next allocation. If owner is non-NULL, this value is | 
|  | // stale and the current value is at owner->top_. Called "NEXT" in the | 
|  | // original Cheney paper. | 
|  | uword top_; | 
|  |  | 
|  | // The address after the last allocatable byte in this page. | 
|  | uword end_; | 
|  |  | 
|  | // Objects below this address have survived a scavenge. | 
|  | uword survivor_end_; | 
|  |  | 
|  | // A pointer to the first unprocessed object. Resolution completes when this | 
|  | // value meets the allocation top. Called "SCAN" in the original Cheney paper. | 
|  | uword resolved_top_; | 
|  |  | 
|  | template <bool> | 
|  | friend class ScavengerVisitorBase; | 
|  |  | 
|  | DISALLOW_ALLOCATION(); | 
|  | DISALLOW_IMPLICIT_CONSTRUCTORS(NewPage); | 
|  | }; | 
|  |  | 
|  | class SemiSpace { | 
|  | public: | 
|  | static void Init(); | 
|  | static void DrainCache(); | 
|  | static void Cleanup(); | 
|  | static intptr_t CachedSize(); | 
|  |  | 
|  | explicit SemiSpace(intptr_t max_capacity_in_words); | 
|  | ~SemiSpace(); | 
|  |  | 
|  | NewPage* TryAllocatePageLocked(bool link); | 
|  |  | 
|  | bool Contains(uword addr) const; | 
|  | void WriteProtect(bool read_only); | 
|  |  | 
|  | intptr_t capacity_in_words() const { return capacity_in_words_; } | 
|  | intptr_t max_capacity_in_words() const { return max_capacity_in_words_; } | 
|  |  | 
|  | NewPage* head() const { return head_; } | 
|  |  | 
|  | void AddList(NewPage* head, NewPage* tail); | 
|  |  | 
|  | private: | 
|  | // Size of NewPages in this semi-space. | 
|  | intptr_t capacity_in_words_ = 0; | 
|  |  | 
|  | // Size of NewPages before we trigger a scavenge. | 
|  | intptr_t max_capacity_in_words_; | 
|  |  | 
|  | NewPage* head_ = nullptr; | 
|  | NewPage* tail_ = nullptr; | 
|  | }; | 
|  |  | 
|  | // Statistics for a particular scavenge. | 
|  | class ScavengeStats { | 
|  | public: | 
|  | ScavengeStats() {} | 
|  | ScavengeStats(int64_t start_micros, | 
|  | int64_t end_micros, | 
|  | SpaceUsage before, | 
|  | SpaceUsage after, | 
|  | intptr_t promo_candidates_in_words, | 
|  | intptr_t promoted_in_words, | 
|  | intptr_t abandoned_in_words) | 
|  | : start_micros_(start_micros), | 
|  | end_micros_(end_micros), | 
|  | before_(before), | 
|  | after_(after), | 
|  | promo_candidates_in_words_(promo_candidates_in_words), | 
|  | promoted_in_words_(promoted_in_words), | 
|  | abandoned_in_words_(abandoned_in_words) {} | 
|  |  | 
|  | // Of all data before scavenge, what fraction was found to be garbage? | 
|  | // If this scavenge included growth, assume the extra capacity would become | 
|  | // garbage to give the scavenger a chance to stablize at the new capacity. | 
|  | double ExpectedGarbageFraction() const { | 
|  | double work = | 
|  | after_.used_in_words + promoted_in_words_ + abandoned_in_words_; | 
|  | return 1.0 - (work / after_.capacity_in_words); | 
|  | } | 
|  |  | 
|  | // Fraction of promotion candidates that survived and was thereby promoted. | 
|  | // Returns zero if there were no promotion candidates. | 
|  | double PromoCandidatesSuccessFraction() const { | 
|  | return promo_candidates_in_words_ > 0 | 
|  | ? promoted_in_words_ / | 
|  | static_cast<double>(promo_candidates_in_words_) | 
|  | : 0.0; | 
|  | } | 
|  |  | 
|  | intptr_t UsedBeforeInWords() const { return before_.used_in_words; } | 
|  |  | 
|  | int64_t DurationMicros() const { return end_micros_ - start_micros_; } | 
|  |  | 
|  | private: | 
|  | int64_t start_micros_; | 
|  | int64_t end_micros_; | 
|  | SpaceUsage before_; | 
|  | SpaceUsage after_; | 
|  | intptr_t promo_candidates_in_words_; | 
|  | intptr_t promoted_in_words_; | 
|  | intptr_t abandoned_in_words_; | 
|  | }; | 
|  |  | 
|  | class Scavenger { | 
|  | private: | 
|  | static const intptr_t kTLABSize = 512 * KB; | 
|  |  | 
|  | public: | 
|  | Scavenger(Heap* heap, intptr_t max_semi_capacity_in_words); | 
|  | ~Scavenger(); | 
|  |  | 
|  | // Check whether this Scavenger contains this address. | 
|  | // During scavenging both the to and from spaces contain "legal" objects. | 
|  | // During a scavenge this function only returns true for addresses that will | 
|  | // be part of the surviving objects. | 
|  | bool Contains(uword addr) const { return to_->Contains(addr); } | 
|  |  | 
|  | ObjectPtr FindObject(FindObjectVisitor* visitor); | 
|  |  | 
|  | uword TryAllocate(Thread* thread, intptr_t size) { | 
|  | uword addr = TryAllocateFromTLAB(thread, size); | 
|  | if (LIKELY(addr != 0)) { | 
|  | return addr; | 
|  | } | 
|  | TryAllocateNewTLAB(thread, size); | 
|  | return TryAllocateFromTLAB(thread, size); | 
|  | } | 
|  | void AbandonRemainingTLAB(Thread* thread); | 
|  | void AbandonRemainingTLABForDebugging(Thread* thread); | 
|  |  | 
|  | // Collect the garbage in this scavenger. | 
|  | void Scavenge(GCReason reason); | 
|  |  | 
|  | // Promote all live objects. | 
|  | void Evacuate(GCReason reason); | 
|  |  | 
|  | int64_t UsedInWords() const { | 
|  | MutexLocker ml(&space_lock_); | 
|  | return to_->capacity_in_words(); | 
|  | } | 
|  | int64_t CapacityInWords() const { return to_->max_capacity_in_words(); } | 
|  | int64_t ExternalInWords() const { return external_size_ >> kWordSizeLog2; } | 
|  | SpaceUsage GetCurrentUsage() const { | 
|  | SpaceUsage usage; | 
|  | usage.used_in_words = UsedInWords(); | 
|  | usage.capacity_in_words = CapacityInWords(); | 
|  | usage.external_in_words = ExternalInWords(); | 
|  | return usage; | 
|  | } | 
|  |  | 
|  | void VisitObjects(ObjectVisitor* visitor) const; | 
|  | void VisitObjectPointers(ObjectPointerVisitor* visitor) const; | 
|  |  | 
|  | void AddRegionsToObjectSet(ObjectSet* set) const; | 
|  |  | 
|  | void WriteProtect(bool read_only); | 
|  |  | 
|  | bool ShouldPerformIdleScavenge(int64_t deadline); | 
|  |  | 
|  | void AddGCTime(int64_t micros) { gc_time_micros_ += micros; } | 
|  |  | 
|  | int64_t gc_time_micros() const { return gc_time_micros_; } | 
|  |  | 
|  | void IncrementCollections() { collections_++; } | 
|  |  | 
|  | intptr_t collections() const { return collections_; } | 
|  |  | 
|  | #ifndef PRODUCT | 
|  | void PrintToJSONObject(JSONObject* object) const; | 
|  | #endif  // !PRODUCT | 
|  |  | 
|  | void AllocatedExternal(intptr_t size) { | 
|  | ASSERT(size >= 0); | 
|  | external_size_ += size; | 
|  | ASSERT(external_size_ >= 0); | 
|  | } | 
|  | void FreedExternal(intptr_t size) { | 
|  | ASSERT(size >= 0); | 
|  | external_size_ -= size; | 
|  | ASSERT(external_size_ >= 0); | 
|  | } | 
|  |  | 
|  | void MakeNewSpaceIterable(); | 
|  | int64_t FreeSpaceInWords(Isolate* isolate) const; | 
|  |  | 
|  | void InitGrowthControl() { | 
|  | growth_control_ = true; | 
|  | } | 
|  |  | 
|  | void SetGrowthControlState(bool state) { | 
|  | growth_control_ = state; | 
|  | } | 
|  |  | 
|  | bool GrowthControlState() { return growth_control_; } | 
|  |  | 
|  | bool scavenging() const { return scavenging_; } | 
|  |  | 
|  | // The maximum number of Dart mutator threads we allow to execute at the same | 
|  | // time. | 
|  | static intptr_t MaxMutatorThreadCount() { | 
|  | // With a max new-space of 16 MB and 512kb TLABs we would allow up to 8 | 
|  | // mutator threads to run at the same time. | 
|  | const intptr_t max_parallel_tlab_usage = | 
|  | (FLAG_new_gen_semi_max_size * MB) / Scavenger::kTLABSize; | 
|  | const intptr_t max_pool_size = max_parallel_tlab_usage / 4; | 
|  | return max_pool_size > 0 ? max_pool_size : 1; | 
|  | } | 
|  |  | 
|  | NewPage* head() const { return to_->head(); } | 
|  |  | 
|  | private: | 
|  | // Ids for time and data records in Heap::GCStats. | 
|  | enum { | 
|  | // Time | 
|  | kDummyScavengeTime = 0, | 
|  | kSafePoint = 1, | 
|  | kVisitIsolateRoots = 2, | 
|  | kIterateStoreBuffers = 3, | 
|  | kProcessToSpace = 4, | 
|  | kIterateWeaks = 5, | 
|  | }; | 
|  |  | 
|  | uword TryAllocateFromTLAB(Thread* thread, intptr_t size) { | 
|  | ASSERT(Utils::IsAligned(size, kObjectAlignment)); | 
|  | ASSERT(heap_ != Dart::vm_isolate_group()->heap()); | 
|  |  | 
|  | const uword result = thread->top(); | 
|  | const intptr_t remaining = thread->end() - result; | 
|  | if (UNLIKELY(remaining < size)) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | ASSERT(to_->Contains(result)); | 
|  | ASSERT((result & kObjectAlignmentMask) == kNewObjectAlignmentOffset); | 
|  | thread->set_top(result + size); | 
|  | return result; | 
|  | } | 
|  | void TryAllocateNewTLAB(Thread* thread, intptr_t size); | 
|  |  | 
|  | SemiSpace* Prologue(GCReason reason); | 
|  | intptr_t ParallelScavenge(SemiSpace* from); | 
|  | intptr_t SerialScavenge(SemiSpace* from); | 
|  | void ReverseScavenge(SemiSpace** from); | 
|  | void IterateIsolateRoots(ObjectPointerVisitor* visitor); | 
|  | template <bool parallel> | 
|  | void IterateStoreBuffers(ScavengerVisitorBase<parallel>* visitor); | 
|  | template <bool parallel> | 
|  | void IterateRememberedCards(ScavengerVisitorBase<parallel>* visitor); | 
|  | void IterateObjectIdTable(ObjectPointerVisitor* visitor); | 
|  | template <bool parallel> | 
|  | void IterateRoots(ScavengerVisitorBase<parallel>* visitor); | 
|  | void MournWeakHandles(); | 
|  | void Epilogue(SemiSpace* from); | 
|  |  | 
|  | bool IsUnreachable(ObjectPtr* p); | 
|  |  | 
|  | void VerifyStoreBuffers(); | 
|  |  | 
|  | void UpdateMaxHeapCapacity(); | 
|  | void UpdateMaxHeapUsage(); | 
|  |  | 
|  | void MournWeakTables(); | 
|  |  | 
|  | intptr_t NewSizeInWords(intptr_t old_size_in_words, GCReason reason) const; | 
|  |  | 
|  | Heap* heap_; | 
|  |  | 
|  | SemiSpace* to_; | 
|  |  | 
|  | PromotionStack promotion_stack_; | 
|  |  | 
|  | intptr_t max_semi_capacity_in_words_; | 
|  |  | 
|  | // Keep track whether a scavenge is currently running. | 
|  | bool scavenging_; | 
|  | bool early_tenure_ = false; | 
|  | RelaxedAtomic<intptr_t> root_slices_started_; | 
|  | StoreBufferBlock* blocks_ = nullptr; | 
|  |  | 
|  | int64_t gc_time_micros_; | 
|  | intptr_t collections_; | 
|  | static const int kStatsHistoryCapacity = 4; | 
|  | RingBuffer<ScavengeStats, kStatsHistoryCapacity> stats_history_; | 
|  |  | 
|  | intptr_t scavenge_words_per_micro_; | 
|  | intptr_t idle_scavenge_threshold_in_words_; | 
|  |  | 
|  | // The total size of external data associated with objects in this scavenger. | 
|  | RelaxedAtomic<intptr_t> external_size_; | 
|  |  | 
|  | RelaxedAtomic<bool> failed_to_promote_; | 
|  | RelaxedAtomic<bool> abort_; | 
|  |  | 
|  | bool growth_control_; | 
|  |  | 
|  | // Protects new space during the allocation of new TLABs | 
|  | mutable Mutex space_lock_; | 
|  |  | 
|  | template <bool> | 
|  | friend class ScavengerVisitorBase; | 
|  | friend class ScavengerWeakVisitor; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(Scavenger); | 
|  | }; | 
|  |  | 
|  | }  // namespace dart | 
|  |  | 
|  | #endif  // RUNTIME_VM_HEAP_SCAVENGER_H_ |