b1c09ecd8f
Currently we have things called XPtr which are not what you get from ptr().
Old world:
handle->raw() returns RawObject* (tagged)
raw_obj->ptr() returns RawObject* (untagged)
After 6fe15f6df9:
handle->raw() returns ObjectPtr
obj_ptr->ptr() returns ObjectLayout*
New world:
handle->ptr() returns ObjectPtr
obj_ptr->untag() returns UntaggedObject*
TEST=ci
Change-Id: I6c7f34014cf20737607caaf84979838300d12df2
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/149367
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
460 lines
13 KiB
C++
460 lines
13 KiB
C++
// 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/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 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();
|
|
|
|
// Promote all live objects.
|
|
void Evacuate();
|
|
|
|
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,
|
|
// Data
|
|
kStoreBufferEntries = 0,
|
|
kDataUnused1 = 1,
|
|
kDataUnused2 = 2,
|
|
kToKBAfterStoreBuffer = 3
|
|
};
|
|
|
|
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();
|
|
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) 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_
|