// 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_CLASS_TABLE_H_ #define RUNTIME_VM_CLASS_TABLE_H_ #include "platform/assert.h" #include "platform/atomic.h" #include "vm/bitfield.h" #include "vm/globals.h" namespace dart { class Class; class ClassStats; class ClassTable; class JSONArray; class JSONObject; class JSONStream; template class MallocGrowableArray; class ObjectPointerVisitor; class RawClass; class ClassAndSize { public: ClassAndSize() : class_(NULL), size_(0) {} explicit ClassAndSize(RawClass* clazz); ClassAndSize(RawClass* clazz, intptr_t size) : class_(clazz), size_(size) {} RawClass* get_raw_class() const { return class_; } intptr_t size() const { return size_; } private: RawClass* class_; intptr_t size_; friend class ClassTable; friend class IsolateReloadContext; // For VisitObjectPointers. }; #ifndef PRODUCT template class AllocStats { public: T new_count; T new_size; T new_external_size; T old_count; T old_size; T old_external_size; void ResetNew() { new_count = 0; new_size = 0; new_external_size = 0; old_external_size = 0; } void AddNew(T size) { AtomicOperations::IncrementBy(&new_count, 1); AtomicOperations::IncrementBy(&new_size, size); } void AddNewGC(T size) { new_count += 1; new_size += size; } void AddNewExternal(T size) { AtomicOperations::IncrementBy(&new_external_size, size); } void ResetOld() { old_count = 0; old_size = 0; old_external_size = 0; new_external_size = 0; } void AddOld(T size, T count = 1) { AtomicOperations::IncrementBy(&old_count, count); AtomicOperations::IncrementBy(&old_size, size); } void AddOldGC(T size, T count = 1) { old_count += count; old_size += size; } void AddOldExternal(T size) { AtomicOperations::IncrementBy(&old_external_size, size); } void Reset() { ResetNew(); ResetOld(); } // For classes with fixed instance size we do not emit code to update // the size statistics. Update them by calling this method. void UpdateSize(intptr_t instance_size) { ASSERT(instance_size > 0); old_size = old_count * instance_size; new_size = new_count * instance_size; } void Verify() { ASSERT(new_count >= 0); ASSERT(new_size >= 0); ASSERT(new_external_size >= 0); ASSERT(old_count >= 0); ASSERT(old_size >= 0); ASSERT(old_external_size >= 0); } }; class ClassHeapStats { public: // Snapshot before GC. AllocStats pre_gc; // Live after GC. AllocStats post_gc; // Allocations since the last GC. AllocStats recent; // Accumulated (across GC) allocations . AllocStats accumulated; // Snapshot of recent at the time of the last reset. AllocStats last_reset; // Promoted from new to old by last new GC. intptr_t promoted_count; intptr_t promoted_size; static intptr_t allocated_since_gc_new_space_offset() { return OFFSET_OF(ClassHeapStats, recent) + OFFSET_OF(AllocStats, new_count); } static intptr_t allocated_since_gc_old_space_offset() { return OFFSET_OF(ClassHeapStats, recent) + OFFSET_OF(AllocStats, old_count); } static intptr_t allocated_size_since_gc_new_space_offset() { return OFFSET_OF(ClassHeapStats, recent) + OFFSET_OF(AllocStats, new_size); } static intptr_t allocated_size_since_gc_old_space_offset() { return OFFSET_OF(ClassHeapStats, recent) + OFFSET_OF(AllocStats, old_size); } static intptr_t state_offset() { return OFFSET_OF(ClassHeapStats, state_); } static intptr_t TraceAllocationMask() { return (1 << kTraceAllocationBit); } void Initialize(); void ResetAtNewGC(); void ResetAtOldGC(); void ResetAccumulator(); void UpdatePromotedAfterNewGC(); void UpdateSize(intptr_t instance_size); #ifndef PRODUCT void PrintToJSONObject(const Class& cls, JSONObject* obj, bool internal) const; #endif void Verify(); bool trace_allocation() const { return TraceAllocationBit::decode(state_); } void set_trace_allocation(bool trace_allocation) { state_ = TraceAllocationBit::update(trace_allocation, state_); } private: enum StateBits { kTraceAllocationBit = 0, }; class TraceAllocationBit : public BitField {}; // Recent old at start of last new GC (used to compute promoted_*). intptr_t old_pre_new_gc_count_; intptr_t old_pre_new_gc_size_; intptr_t state_; intptr_t align_; // Make SIMARM and ARM agree on the size of ClassHeapStats. }; #endif // !PRODUCT class ClassTable { public: ClassTable(); // Creates a shallow copy of the original class table for some read-only // access, without support for stats data. explicit ClassTable(ClassTable* original); ~ClassTable(); // Thread-safe. RawClass* At(intptr_t index) const { ASSERT(IsValidIndex(index)); return table_[index].class_; } intptr_t SizeAt(intptr_t index) const { ASSERT(IsValidIndex(index)); return table_[index].size_; } ClassAndSize PairAt(intptr_t index) const { ASSERT(IsValidIndex(index)); return table_[index]; } void SetAt(intptr_t index, RawClass* raw_cls); bool IsValidIndex(intptr_t index) const { return (index > 0) && (index < top_); } bool HasValidClassAt(intptr_t index) const { ASSERT(IsValidIndex(index)); return table_[index].class_ != NULL; } intptr_t NumCids() const { return top_; } intptr_t Capacity() const { return capacity_; } // Used to drop recently added classes. void SetNumCids(intptr_t num_cids) { ASSERT(num_cids <= top_); top_ = num_cids; } void Register(const Class& cls); void AllocateIndex(intptr_t index); void Unregister(intptr_t index); void Remap(intptr_t* old_to_new_cids); void VisitObjectPointers(ObjectPointerVisitor* visitor); // If a snapshot reader has populated the class table then the // sizes in the class table are not correct. Iterates through the // table, updating the sizes. void CopySizesFromClassObjects(); void Validate(); void Print(); // Used by the generated code. static intptr_t table_offset() { return OFFSET_OF(ClassTable, table_); } // Used by the generated code. static intptr_t ClassOffsetFor(intptr_t cid); #ifndef PRODUCT // Describes layout of heap stats for code generation. See offset_extractor.cc struct ArrayLayout { static intptr_t elements_start_offset() { return 0; } static constexpr intptr_t kElementSize = sizeof(ClassHeapStats); }; #endif #if defined(ARCH_IS_32_BIT) static constexpr int kSizeOfClassPairLog2 = 3; #else static constexpr int kSizeOfClassPairLog2 = 4; #endif static_assert( (1 << kSizeOfClassPairLog2) == sizeof(ClassAndSize), "Mismatch between sizeof(ClassAndSize) and kSizeOfClassPairLog2"); #ifndef PRODUCT // Called whenever a class is allocated in the runtime. void UpdateAllocatedNew(intptr_t cid, intptr_t size) { ClassHeapStats* stats = PreliminaryStatsAt(cid); ASSERT(stats != NULL); ASSERT(size != 0); stats->recent.AddNew(size); } void UpdateAllocatedOld(intptr_t cid, intptr_t size) { ClassHeapStats* stats = PreliminaryStatsAt(cid); ASSERT(stats != NULL); ASSERT(size != 0); stats->recent.AddOld(size); } void UpdateAllocatedOldGC(intptr_t cid, intptr_t size); void UpdateAllocatedExternalNew(intptr_t cid, intptr_t size); void UpdateAllocatedExternalOld(intptr_t cid, intptr_t size); // Called whenever a old GC occurs. void ResetCountersOld(); // Called whenever a new GC occurs. void ResetCountersNew(); // Called immediately after a new GC. void UpdatePromoted(); // Used by the generated code. static intptr_t class_heap_stats_table_offset() { return OFFSET_OF(ClassTable, class_heap_stats_table_); } // Used by the generated code. static intptr_t NewSpaceCounterOffsetFor(intptr_t cid); // Used by the generated code. static intptr_t StateOffsetFor(intptr_t cid); // Used by the generated code. static intptr_t NewSpaceSizeOffsetFor(intptr_t cid); ClassHeapStats* StatsWithUpdatedSize(intptr_t cid); void AllocationProfilePrintJSON(JSONStream* stream, bool internal); void ResetAllocationAccumulators(); void PrintToJSONObject(JSONObject* object); void SetTraceAllocationFor(intptr_t cid, bool trace) { ClassHeapStats* stats = PreliminaryStatsAt(cid); stats->set_trace_allocation(trace); } bool TraceAllocationFor(intptr_t cid) { ClassHeapStats* stats = PreliminaryStatsAt(cid); return stats->trace_allocation(); } #endif // !PRODUCT void AddOldTable(ClassAndSize* old_table); // Deallocates table copies. Do not call during concurrent access to table. void FreeOldTables(); private: friend class GCMarker; friend class MarkingWeakVisitor; friend class Scavenger; friend class ScavengerWeakVisitor; friend class ClassHeapStatsTestHelper; friend class HeapTestsHelper; static const int initial_capacity_ = 512; static const int capacity_increment_ = 256; static bool ShouldUpdateSizeForClassId(intptr_t cid); intptr_t top_; intptr_t capacity_; // Copy-on-write is used for table_, with old copies stored in old_tables_. ClassAndSize* table_; MallocGrowableArray* old_tables_; #ifndef PRODUCT ClassHeapStats* class_heap_stats_table_; // May not have updated size for variable size classes. ClassHeapStats* PreliminaryStatsAt(intptr_t cid) { ASSERT(cid > 0); ASSERT(cid < top_); return &class_heap_stats_table_[cid]; } void UpdateLiveOld(intptr_t cid, intptr_t size, intptr_t count = 1); void UpdateLiveNew(intptr_t cid, intptr_t size); void UpdateLiveNewGC(intptr_t cid, intptr_t size); void UpdateLiveOldExternal(intptr_t cid, intptr_t size); void UpdateLiveNewExternal(intptr_t cid, intptr_t size); #endif // !PRODUCT DISALLOW_COPY_AND_ASSIGN(ClassTable); }; } // namespace dart #endif // RUNTIME_VM_CLASS_TABLE_H_