e59fa2e4a2
This was part of the VM isolate. TEST=ci Change-Id: I831e984025c5d4d17607e8a36222bdef70bf66b8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/508240 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
490 lines
16 KiB
C++
490 lines
16 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_HEAP_H_
|
|
#define RUNTIME_VM_HEAP_HEAP_H_
|
|
|
|
#if defined(SHOULD_NOT_INCLUDE_RUNTIME)
|
|
#error "Should not include runtime"
|
|
#endif
|
|
|
|
#include "include/dart_tools_api.h"
|
|
|
|
#include "platform/assert.h"
|
|
#include "vm/allocation.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/globals.h"
|
|
#include "vm/heap/pages.h"
|
|
#include "vm/heap/scavenger.h"
|
|
#include "vm/heap/spaces.h"
|
|
#include "vm/heap/weak_table.h"
|
|
#include "vm/isolate.h"
|
|
|
|
namespace dart {
|
|
|
|
// Forward declarations.
|
|
class Isolate;
|
|
class IsolateGroup;
|
|
class ObjectPointerVisitor;
|
|
class ObjectSet;
|
|
class ServiceEvent;
|
|
class TimelineEventScope;
|
|
class VirtualMemory;
|
|
|
|
class Heap {
|
|
public:
|
|
enum Space {
|
|
kNew,
|
|
kOld,
|
|
kCode,
|
|
};
|
|
|
|
enum WeakSelector {
|
|
kPeers = 0,
|
|
#if !defined(HASH_IN_OBJECT_HEADER)
|
|
kIdentityHashes,
|
|
#endif
|
|
kCanonicalHashes,
|
|
kObjectIds,
|
|
#if defined(SNAPSHOT_BACKTRACE)
|
|
kSnapshotParents,
|
|
#endif
|
|
kLoadingUnits,
|
|
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
|
|
kHeapSamplingData,
|
|
#endif
|
|
kNumWeakSelectors
|
|
};
|
|
|
|
// Pattern for unused new space and swept old space.
|
|
static constexpr uint8_t kZapByte = 0xf3;
|
|
|
|
// Maximum size of object in the new space.
|
|
static constexpr intptr_t kNewAllocatableSize = 256 * KB;
|
|
|
|
static inline bool IsAllocatableInNewSpace(intptr_t size) {
|
|
return size <= kNewAllocatableSize;
|
|
}
|
|
|
|
~Heap();
|
|
|
|
Scavenger* new_space() { return &new_space_; }
|
|
PageSpace* old_space() { return &old_space_; }
|
|
|
|
uword Allocate(Thread* thread, intptr_t size, Space space) {
|
|
switch (space) {
|
|
case kNew:
|
|
// Do not attempt to allocate very large objects in new space.
|
|
if (!IsAllocatableInNewSpace(size)) {
|
|
return AllocateOld(thread, size, /*executable*/ false);
|
|
}
|
|
return AllocateNew(thread, size);
|
|
case kOld:
|
|
return AllocateOld(thread, size, /*executable*/ false);
|
|
case kCode:
|
|
return AllocateOld(thread, size, /*executable*/ true);
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Tracks an external allocation. Returns false without tracking the
|
|
// allocation if it will make the total external size exceed
|
|
// kMaxAddrSpaceInWords.
|
|
bool AllocatedExternal(intptr_t size, Space space);
|
|
void FreedExternal(intptr_t size, Space space);
|
|
// Move external size from new to old space. Does not by itself trigger GC.
|
|
void PromotedExternal(intptr_t size);
|
|
void CheckExternalGC(Thread* thread);
|
|
|
|
// Heap contains the specified address.
|
|
bool Contains(uword addr) const;
|
|
bool CodeContains(uword addr) const;
|
|
|
|
void NotifyIdle(int64_t deadline);
|
|
void NotifyDestroyed();
|
|
|
|
Dart_PerformanceMode mode() const { return mode_; }
|
|
Dart_PerformanceMode SetMode(Dart_PerformanceMode mode);
|
|
|
|
// Collect a single generation.
|
|
void CollectGarbage(Thread* thread, GCType type, GCReason reason);
|
|
|
|
// Collect both generations by performing a mark-sweep. If incremental marking
|
|
// was in progress, perform another mark-sweep. This function will collect all
|
|
// unreachable objects, including those in inter-generational cycles or stored
|
|
// during incremental marking.
|
|
void CollectAllGarbage(GCReason reason = GCReason::kFull,
|
|
bool compact = false);
|
|
|
|
void CheckCatchUp(Thread* thread);
|
|
void CheckConcurrentMarking(Thread* thread, GCReason reason, intptr_t size);
|
|
void CheckFinalizeMarking(Thread* thread);
|
|
void StartConcurrentMarking(Thread* thread, GCReason reason);
|
|
void WaitForMarkerTasks(Thread* thread);
|
|
void WaitForSweeperTasks(Thread* thread);
|
|
|
|
// Protect access to the heap. Note: Code pages are made
|
|
// executable/non-executable when 'read_only' is true/false, respectively.
|
|
void WriteProtectCode(bool read_only) {
|
|
old_space_.WriteProtectCode(read_only);
|
|
}
|
|
|
|
// Initialize the heap and register it with the isolate.
|
|
static void Init(IsolateGroup* isolate_group,
|
|
intptr_t max_new_gen_words,
|
|
intptr_t max_old_gen_words);
|
|
|
|
// Verify that all pointers in the heap point to the heap.
|
|
bool Verify(const char* msg,
|
|
MarkExpectation mark_expectation = kForbidMarked);
|
|
|
|
// Print heap sizes.
|
|
void PrintSizes() const;
|
|
|
|
// Return amount of memory used and capacity in a space, excluding external.
|
|
intptr_t UsedInWords(Space space) const;
|
|
intptr_t CapacityInWords(Space space) const;
|
|
intptr_t ExternalInWords(Space space) const;
|
|
|
|
intptr_t TotalUsedInWords() const;
|
|
intptr_t TotalCapacityInWords() const;
|
|
intptr_t TotalExternalInWords() const;
|
|
// Return the amount of GCing in microseconds.
|
|
int64_t GCTimeInMicros(Space space) const;
|
|
|
|
intptr_t Collections(Space space) const;
|
|
|
|
ObjectSet* CreateAllocatedObjectSet(Zone* zone,
|
|
MarkExpectation mark_expectation);
|
|
|
|
static const char* GCTypeToString(GCType type);
|
|
static const char* GCReasonToString(GCReason reason);
|
|
|
|
// Associate a peer with an object. A nonexistent peer is equal to nullptr.
|
|
void SetPeer(ObjectPtr raw_obj, void* peer) {
|
|
SetWeakEntry(raw_obj, kPeers, reinterpret_cast<intptr_t>(peer));
|
|
}
|
|
void* GetPeer(ObjectPtr raw_obj) const {
|
|
return reinterpret_cast<void*>(GetWeakEntry(raw_obj, kPeers));
|
|
}
|
|
int64_t PeerCount() const;
|
|
|
|
#if !defined(HASH_IN_OBJECT_HEADER)
|
|
// Associate an identity hashCode with an object. An nonexistent hashCode
|
|
// is equal to 0.
|
|
intptr_t SetHashIfNotSet(ObjectPtr raw_obj, intptr_t hash) {
|
|
return SetWeakEntryIfNonExistent(raw_obj, kIdentityHashes, hash);
|
|
}
|
|
intptr_t GetHash(ObjectPtr raw_obj) const {
|
|
return GetWeakEntry(raw_obj, kIdentityHashes);
|
|
}
|
|
#endif
|
|
|
|
void SetCanonicalHash(ObjectPtr raw_obj, intptr_t hash) {
|
|
SetWeakEntry(raw_obj, kCanonicalHashes, hash);
|
|
}
|
|
intptr_t GetCanonicalHash(ObjectPtr raw_obj) const {
|
|
return GetWeakEntry(raw_obj, kCanonicalHashes);
|
|
}
|
|
void ResetCanonicalHashTable();
|
|
|
|
// Associate an id with an object (used when serializing an object).
|
|
// A non-existant id is equal to 0.
|
|
void SetObjectId(ObjectPtr raw_obj, intptr_t object_id) {
|
|
ASSERT(Thread::Current()->IsDartMutatorThread());
|
|
SetWeakEntry(raw_obj, kObjectIds, object_id);
|
|
}
|
|
intptr_t GetObjectId(ObjectPtr raw_obj) const {
|
|
ASSERT(Thread::Current()->IsDartMutatorThread());
|
|
return GetWeakEntry(raw_obj, kObjectIds);
|
|
}
|
|
void ResetObjectIdTable();
|
|
|
|
#if defined(SNAPSHOT_BACKTRACE)
|
|
void SetSnapshotParent(ObjectPtr obj, Object* parent) {
|
|
SetWeakEntry(obj, kSnapshotParents, reinterpret_cast<intptr_t>(parent));
|
|
}
|
|
Object* GetSnapshotParent(ObjectPtr obj) const {
|
|
return reinterpret_cast<Object*>(GetWeakEntry(obj, kSnapshotParents));
|
|
}
|
|
void ResetSnapshotParentTable();
|
|
#endif
|
|
|
|
void SetLoadingUnit(ObjectPtr raw_obj, intptr_t unit_id) {
|
|
ASSERT(Thread::Current()->IsDartMutatorThread());
|
|
SetWeakEntry(raw_obj, kLoadingUnits, unit_id);
|
|
}
|
|
intptr_t GetLoadingUnit(ObjectPtr raw_obj) const {
|
|
ASSERT(Thread::Current()->IsDartMutatorThread());
|
|
return GetWeakEntry(raw_obj, kLoadingUnits);
|
|
}
|
|
|
|
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
|
|
void SetHeapSamplingData(ObjectPtr obj, void* data) {
|
|
SetWeakEntry(obj, kHeapSamplingData, reinterpret_cast<intptr_t>(data));
|
|
}
|
|
#endif
|
|
|
|
// Used by the GC algorithms to propagate weak entries.
|
|
intptr_t GetWeakEntry(ObjectPtr raw_obj, WeakSelector sel) const;
|
|
void SetWeakEntry(ObjectPtr raw_obj, WeakSelector sel, intptr_t val);
|
|
intptr_t SetWeakEntryIfNonExistent(ObjectPtr raw_obj,
|
|
WeakSelector sel,
|
|
intptr_t val);
|
|
|
|
WeakTable* GetWeakTable(Space space, WeakSelector selector) const {
|
|
if (space == kNew) {
|
|
return new_weak_tables_[selector];
|
|
}
|
|
ASSERT(space == kOld);
|
|
return old_weak_tables_[selector];
|
|
}
|
|
void SetWeakTable(Space space, WeakSelector selector, WeakTable* value) {
|
|
if (space == kNew) {
|
|
new_weak_tables_[selector] = value;
|
|
} else {
|
|
ASSERT(space == kOld);
|
|
old_weak_tables_[selector] = value;
|
|
}
|
|
}
|
|
|
|
void ForwardWeakEntries(ObjectPtr before_object, ObjectPtr after_object);
|
|
void ForwardWeakTables(ObjectPointerVisitor* visitor);
|
|
|
|
#if !defined(PRODUCT) || defined(FORCE_INCLUDE_SAMPLING_HEAP_PROFILER)
|
|
void ReportSurvivingAllocations(Dart_HeapSamplingReportCallback callback,
|
|
void* context) {
|
|
new_weak_tables_[kHeapSamplingData]->ReportSurvivingAllocations(callback,
|
|
context);
|
|
old_weak_tables_[kHeapSamplingData]->ReportSurvivingAllocations(callback,
|
|
context);
|
|
}
|
|
#endif
|
|
|
|
void UpdateGlobalMaxUsed();
|
|
|
|
#ifndef PRODUCT
|
|
void PrintToJSONObject(Space space, JSONObject* object) const;
|
|
|
|
// Returns a JSON object with total memory usage statistics for both new and
|
|
// old space combined.
|
|
void PrintMemoryUsageJSON(JSONStream* stream) const;
|
|
void PrintMemoryUsageJSON(JSONObject* jsobj) const;
|
|
|
|
// The heap map contains the sizes and class ids for the objects in each page.
|
|
void PrintHeapMapToJSONStream(IsolateGroup* isolate_group,
|
|
JSONStream* stream) {
|
|
old_space_.PrintHeapMapToJSONStream(isolate_group, stream);
|
|
}
|
|
#endif // PRODUCT
|
|
|
|
intptr_t ReachabilityBarrier() { return old_space_.collections(); }
|
|
|
|
IsolateGroup* isolate_group() const { return isolate_group_; }
|
|
|
|
void SetupImagePage(void* pointer, uword size, bool is_executable) {
|
|
old_space_.SetupImagePage(pointer, size, is_executable);
|
|
}
|
|
|
|
Space SpaceForExternal(intptr_t size) const;
|
|
|
|
void CollectOnNthAllocation(intptr_t num_allocations);
|
|
|
|
private:
|
|
class GCStats : public ValueObject {
|
|
public:
|
|
GCStats() {}
|
|
intptr_t num_;
|
|
GCType type_;
|
|
GCReason reason_;
|
|
|
|
class Data : public ValueObject {
|
|
public:
|
|
Data() {}
|
|
int64_t micros_;
|
|
SpaceUsage new_;
|
|
SpaceUsage old_;
|
|
intptr_t store_buffer_;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(Data);
|
|
};
|
|
|
|
Data before_;
|
|
Data after_;
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(GCStats);
|
|
};
|
|
|
|
Heap(IsolateGroup* isolate_group,
|
|
intptr_t max_new_gen_semi_words, // Max capacity of new semi-space.
|
|
intptr_t max_old_gen_words);
|
|
|
|
uword AllocateNew(Thread* thread, intptr_t size);
|
|
uword AllocateOld(Thread* thread, intptr_t size, bool executable);
|
|
|
|
// Visit all pointers. Caller must ensure concurrent sweeper is not running,
|
|
// and the visitor must not allocate.
|
|
void VisitObjectPointers(ObjectPointerVisitor* visitor);
|
|
|
|
// Visit all objects, including FreeListElement "objects". Caller must ensure
|
|
// concurrent sweeper is not running, and the visitor must not allocate.
|
|
void VisitObjects(ObjectVisitor* visitor);
|
|
void VisitObjectsNoImagePages(ObjectVisitor* visitor);
|
|
void VisitObjectsImagePages(ObjectVisitor* visitor) const;
|
|
|
|
// Like Verify, but does not wait for concurrent sweeper, so caller must
|
|
// ensure thread-safety.
|
|
bool VerifyGC(const char* msg,
|
|
MarkExpectation mark_expectation = kForbidMarked);
|
|
|
|
// Helper functions for garbage collection.
|
|
void CollectNewSpaceGarbage(Thread* thread, GCType type, GCReason reason);
|
|
void CollectOldSpaceGarbage(Thread* thread, GCType type, GCReason reason);
|
|
|
|
// GC stats collection.
|
|
void RecordBeforeGC(GCType type, GCReason reason);
|
|
void RecordAfterGC(GCType type);
|
|
void PrintStats();
|
|
void PrintStatsToTimeline(TimelineEventScope* event, GCReason reason);
|
|
|
|
void AddRegionsToObjectSet(ObjectSet* set) const;
|
|
|
|
// Trigger major GC if 'gc_on_nth_allocation_' is set.
|
|
void CollectForDebugging(Thread* thread);
|
|
|
|
IsolateGroup* const isolate_group_;
|
|
|
|
// The different spaces used for allocation.
|
|
Scavenger new_space_;
|
|
PageSpace old_space_;
|
|
|
|
WeakTable* new_weak_tables_[kNumWeakSelectors];
|
|
WeakTable* old_weak_tables_[kNumWeakSelectors];
|
|
|
|
// GC stats collection.
|
|
GCStats stats_;
|
|
|
|
RelaxedAtomic<Dart_PerformanceMode> mode_ = {Dart_PerformanceMode_Default};
|
|
|
|
bool assume_scavenge_will_fail_;
|
|
|
|
static constexpr intptr_t kNoForcedGarbageCollection = -1;
|
|
|
|
// Whether the next heap allocation (new or old) should trigger
|
|
// CollectAllGarbage. Used within unit tests for testing GC on certain
|
|
// sensitive codepaths.
|
|
intptr_t gc_on_nth_allocation_;
|
|
|
|
friend class Become; // VisitObjectPointers
|
|
friend class GCCompactor; // VisitObjectPointers
|
|
friend class Precompiler; // VisitObjects
|
|
friend class ServiceEvent;
|
|
friend class Scavenger; // VerifyGC
|
|
friend class PageSpace; // VerifyGC
|
|
friend class ProgramReloadContext; // VisitObjects
|
|
friend class ClassFinalizer; // VisitObjects
|
|
friend class HeapIterationScope; // VisitObjects
|
|
friend class GCMarker; // VisitObjects
|
|
friend class ProgramVisitor; // VisitObjectsImagePages
|
|
friend class Serializer; // VisitObjectsImagePages
|
|
friend class HeapTestHelper;
|
|
friend class GCTestHelper;
|
|
friend class GCIncrementalCompactor;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(Heap);
|
|
};
|
|
|
|
class HeapIterationScope : public ThreadStackResource {
|
|
public:
|
|
explicit HeapIterationScope(Thread* thread, bool writable = false);
|
|
~HeapIterationScope();
|
|
|
|
void IterateObjects(ObjectVisitor* visitor) const;
|
|
void IterateObjectsNoImagePages(ObjectVisitor* visitor) const;
|
|
void IterateOldObjects(ObjectVisitor* visitor) const;
|
|
void IterateOldObjectsNoImagePages(ObjectVisitor* visitor) const;
|
|
|
|
void IterateObjectPointers(ObjectPointerVisitor* visitor,
|
|
ValidationPolicy validate_frames);
|
|
void IterateStackPointers(ObjectPointerVisitor* visitor,
|
|
ValidationPolicy validate_frames);
|
|
|
|
private:
|
|
Heap* heap_;
|
|
PageSpace* old_space_;
|
|
bool writable_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(HeapIterationScope);
|
|
};
|
|
|
|
class ForceGrowthScope : public ThreadStackResource {
|
|
public:
|
|
explicit ForceGrowthScope(Thread* thread);
|
|
~ForceGrowthScope();
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(ForceGrowthScope);
|
|
};
|
|
|
|
class WritableCodePages : StackResource {
|
|
public:
|
|
WritableCodePages(Thread* thread, IsolateGroup* isolate_group);
|
|
~WritableCodePages();
|
|
|
|
private:
|
|
IsolateGroup* isolate_group_;
|
|
};
|
|
|
|
#if defined(TESTING)
|
|
class GCTestHelper : public AllStatic {
|
|
public:
|
|
// Collect new gen without triggering any side effects. The normal call to
|
|
// CollectGarbage(Heap::kNew) could potentially trigger an old gen collection
|
|
// if there is enough promotion, and this can perturb some tests.
|
|
static void CollectNewSpace() {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(thread->execution_state() == Thread::kThreadInVM);
|
|
thread->heap()->CollectGarbage(thread, GCType::kScavenge,
|
|
GCReason::kDebugging);
|
|
}
|
|
|
|
// Fully collect old gen and wait for the sweeper to finish. The normal call
|
|
// to CollectGarbage(Heap::kOld) may leave so-called "floating garbage",
|
|
// objects that were seen by the incremental barrier but later made
|
|
// unreachable, and this can perturb some tests.
|
|
static void CollectOldSpace() {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(thread->execution_state() == Thread::kThreadInVM);
|
|
if (thread->is_marking()) {
|
|
thread->heap()->CollectGarbage(thread, GCType::kMarkSweep,
|
|
GCReason::kDebugging);
|
|
}
|
|
thread->heap()->CollectGarbage(thread, GCType::kMarkSweep,
|
|
GCReason::kDebugging);
|
|
WaitForGCTasks();
|
|
}
|
|
|
|
static void CollectAllGarbage(bool compact = false) {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(thread->execution_state() == Thread::kThreadInVM);
|
|
thread->heap()->CollectAllGarbage(GCReason::kDebugging, compact);
|
|
}
|
|
|
|
static void WaitForGCTasks() {
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(thread->execution_state() == Thread::kThreadInVM);
|
|
thread->heap()->WaitForMarkerTasks(thread);
|
|
thread->heap()->WaitForSweeperTasks(thread);
|
|
}
|
|
};
|
|
#endif // TESTING
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_HEAP_HEAP_H_
|