dart-sdk/runtime/vm/class_table.h
Alexander Markov 591b2bd724 [vm] Add --print-object-layout-to option to gen_snapshot
This option dumps offsets of Dart fields and sizes of Dart instances
into the specified file in JSON format.
Could be useful for debugging or memory usage investigations.

Typical output:

{"class":"_JsonUtf8Parser","size":80,"fields":[{"field":"emptyChunk","static":true},{"field":"decoder","offset":56},{"field":"chunk","offset":64},{"field":"chunkEnd","offset":72}]}

TEST=runtime/tests/vm/dart/print_object_layout_test.dart
Fixes https://github.com/dart-lang/sdk/issues/47892

Change-Id: I0b2f7ed9e4991e5fcd18517d5a06aa826c6d7125
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/223662
Reviewed-by: Dan Field <dnfield@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2021-12-20 21:40:33 +00:00

496 lines
15 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_CLASS_TABLE_H_
#define RUNTIME_VM_CLASS_TABLE_H_
#include <memory>
#include "platform/allocation.h"
#include "platform/assert.h"
#include "platform/atomic.h"
#include "platform/utils.h"
#include "vm/bitfield.h"
#include "vm/class_id.h"
#include "vm/flags.h"
#include "vm/globals.h"
#include "vm/tagged_pointer.h"
namespace dart {
class Class;
class ClassTable;
class Isolate;
class IsolateGroup;
class IsolateGroupReloadContext;
class ProgramReloadContext;
class JSONArray;
class JSONObject;
class JSONStream;
template <typename T>
class MallocGrowableArray;
class ObjectPointerVisitor;
// Wraps a 64-bit integer to represent the bitmap of unboxed fields
// stored in the shared class table.
class UnboxedFieldBitmap {
public:
UnboxedFieldBitmap() : bitmap_(0) {}
explicit UnboxedFieldBitmap(uint64_t bitmap) : bitmap_(bitmap) {}
UnboxedFieldBitmap(const UnboxedFieldBitmap&) = default;
UnboxedFieldBitmap& operator=(const UnboxedFieldBitmap&) = default;
DART_FORCE_INLINE bool Get(intptr_t position) const {
if (position >= Length()) return false;
return Utils::TestBit(bitmap_, position);
}
DART_FORCE_INLINE void Set(intptr_t position) {
ASSERT(position < Length());
bitmap_ |= Utils::Bit<decltype(bitmap_)>(position);
}
DART_FORCE_INLINE uint64_t Value() const { return bitmap_; }
DART_FORCE_INLINE bool IsEmpty() const { return bitmap_ == 0; }
DART_FORCE_INLINE void Reset() { bitmap_ = 0; }
DART_FORCE_INLINE static constexpr intptr_t Length() {
return sizeof(decltype(bitmap_)) * kBitsPerByte;
}
private:
uint64_t bitmap_;
};
// Registry of all known classes and their sizes.
//
// The GC will only need the information in this shared class table to scan
// object pointers.
class SharedClassTable {
public:
SharedClassTable();
~SharedClassTable();
// Thread-safe.
intptr_t SizeAt(intptr_t index) const {
ASSERT(IsValidIndex(index));
return table_.load()[index];
}
bool HasValidClassAt(intptr_t index) const {
ASSERT(IsValidIndex(index));
ASSERT(table_.load()[index] >= 0);
return table_.load()[index] != 0;
}
void SetSizeAt(intptr_t index, intptr_t size) {
ASSERT(IsValidIndex(index));
// Ensure we never change size for a given cid from one non-zero size to
// another non-zero size.
intptr_t old_size = 0;
if (!table_.load()[index].compare_exchange_strong(old_size, size)) {
RELEASE_ASSERT(old_size == size);
}
}
bool IsValidIndex(intptr_t index) const { return index > 0 && index < top_; }
intptr_t NumCids() const { return top_; }
intptr_t Capacity() const { return capacity_; }
UnboxedFieldBitmap GetUnboxedFieldsMapAt(intptr_t index) const {
ASSERT(IsValidIndex(index));
return FLAG_precompiled_mode ? unboxed_fields_map_[index]
: UnboxedFieldBitmap();
}
void SetUnboxedFieldsMapAt(intptr_t index,
UnboxedFieldBitmap unboxed_fields_map) {
ASSERT(IsValidIndex(index));
ASSERT(unboxed_fields_map_[index].IsEmpty());
unboxed_fields_map_[index] = unboxed_fields_map;
}
// Used to drop recently added classes.
void SetNumCids(intptr_t num_cids) {
ASSERT(num_cids <= top_);
top_ = num_cids;
}
#if !defined(PRODUCT)
void SetTraceAllocationFor(intptr_t cid, bool trace) {
ASSERT(cid > 0);
ASSERT(cid < top_);
trace_allocation_table_.load()[cid] = trace ? 1 : 0;
}
bool TraceAllocationFor(intptr_t cid);
void SetCollectInstancesFor(intptr_t cid, bool trace) {
ASSERT(cid > 0);
ASSERT(cid < top_);
if (trace) {
trace_allocation_table_.load()[cid] |= 2;
} else {
trace_allocation_table_.load()[cid] &= ~2;
}
}
bool CollectInstancesFor(intptr_t cid) {
ASSERT(cid > 0);
ASSERT(cid < top_);
return (trace_allocation_table_.load()[cid] & 2) != 0;
}
#endif // !defined(PRODUCT)
void CopyBeforeHotReload(intptr_t** copy, intptr_t* copy_num_cids) {
// The [IsolateGroupReloadContext] will need to maintain a copy of the old
// class table until instances have been morphed.
const intptr_t num_cids = NumCids();
const intptr_t bytes = sizeof(intptr_t) * num_cids;
auto size_table = static_cast<intptr_t*>(malloc(bytes));
auto table = table_.load();
for (intptr_t i = 0; i < num_cids; i++) {
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
size_table[i] = table[i];
}
*copy_num_cids = num_cids;
*copy = size_table;
}
void ResetBeforeHotReload() {
// The [ProgramReloadContext] is now source-of-truth for GC.
auto table = table_.load();
for (intptr_t i = 0; i < top_; i++) {
// Don't use memset, which changes this from a relaxed atomic operation
// to a non-atomic operation.
table[i] = 0;
}
}
void ResetAfterHotReload(intptr_t* old_table,
intptr_t num_old_cids,
bool is_rollback) {
// The [ProgramReloadContext] is no longer source-of-truth for GC after we
// return, so we restore size information for all classes.
if (is_rollback) {
SetNumCids(num_old_cids);
auto table = table_.load();
for (intptr_t i = 0; i < num_old_cids; i++) {
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
table[i] = old_table[i];
}
}
// Can't free this table immediately as another thread (e.g., concurrent
// marker or sweeper) may be between loading the table pointer and loading
// the table element. The table will be freed at the next major GC or
// isolate shutdown.
AddOldTable(old_table);
}
// Deallocates table copies. Do not call during concurrent access to table.
void FreeOldTables();
// Deallocates bitmap copies. Do not call during concurrent access to table.
void FreeOldUnboxedFieldsMaps();
#if !defined(DART_PRECOMPILED_RUNTIME)
bool IsReloading() const { return reload_context_ != nullptr; }
IsolateGroupReloadContext* reload_context() { return reload_context_; }
#endif // !defined(DART_PRECOMPILED_RUNTIME)
// Returns the newly allocated cid.
//
// [index] is kIllegalCid or a predefined cid.
intptr_t Register(intptr_t index, intptr_t size);
void AllocateIndex(intptr_t index);
void Unregister(intptr_t index);
void Remap(intptr_t* old_to_new_cids);
// Used by the generated code.
#ifndef PRODUCT
static intptr_t class_heap_stats_table_offset() {
return OFFSET_OF(SharedClassTable, trace_allocation_table_);
}
#endif
// Used by the generated code.
static intptr_t ClassOffsetFor(intptr_t cid);
static const int kInitialCapacity = 512;
static const int kCapacityIncrement = 256;
private:
friend class ClassTable;
friend class GCMarker;
friend class MarkingWeakVisitor;
friend class Scavenger;
friend class ScavengerWeakVisitor;
#ifndef PRODUCT
// Copy-on-write is used for trace_allocation_table_, with old copies stored
// in old_tables_.
AcqRelAtomic<uint8_t*> trace_allocation_table_ = {nullptr};
#endif // !PRODUCT
void AddOldTable(intptr_t* old_table);
void Grow(intptr_t new_capacity);
intptr_t top_;
intptr_t capacity_;
// Copy-on-write is used for table_, with old copies stored in old_tables_.
// Maps the cid to the instance size.
AcqRelAtomic<RelaxedAtomic<intptr_t>*> table_ = {nullptr};
MallocGrowableArray<void*>* old_tables_;
IsolateGroupReloadContext* reload_context_ = nullptr;
// Stores a 64-bit bitmap for each class. There is one bit for each word in an
// instance of the class. A 0 bit indicates that the word contains a pointer
// the GC has to scan, a 1 indicates that the word is part of e.g. an unboxed
// double and does not need to be scanned. (see Class::Calculate...() where
// the bitmap is constructed)
UnboxedFieldBitmap* unboxed_fields_map_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(SharedClassTable);
};
class ClassTable {
public:
explicit ClassTable(SharedClassTable* shared_class_table_);
~ClassTable();
SharedClassTable* shared_class_table() const { return shared_class_table_; }
void CopyBeforeHotReload(ClassPtr** copy,
ClassPtr** tlc_copy,
intptr_t* copy_num_cids,
intptr_t* copy_num_tlc_cids) {
// The [ProgramReloadContext] will need to maintain a copy of the old class
// table until instances have been morphed.
const intptr_t num_cids = NumCids();
const intptr_t num_tlc_cids = NumTopLevelCids();
auto class_table =
static_cast<ClassPtr*>(malloc(sizeof(ClassPtr) * num_cids));
auto tlc_class_table =
static_cast<ClassPtr*>(malloc(sizeof(ClassPtr) * num_tlc_cids));
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
auto table = table_.load();
for (intptr_t i = 0; i < num_cids; i++) {
class_table[i] = table[i];
}
auto tlc_table = tlc_table_.load();
for (intptr_t i = 0; i < num_tlc_cids; i++) {
tlc_class_table[i] = tlc_table[i];
}
*copy = class_table;
*tlc_copy = tlc_class_table;
*copy_num_cids = num_cids;
*copy_num_tlc_cids = num_tlc_cids;
}
void ResetBeforeHotReload() {
// We cannot clear out the class pointers, because a hot-reload
// contains only a diff: If e.g. a class included in the hot-reload has a
// super class not included in the diff, it will look up in this class table
// to find the super class (e.g. `cls.SuperClass` will cause us to come
// here).
}
void ResetAfterHotReload(ClassPtr* old_table,
ClassPtr* old_tlc_table,
intptr_t num_old_cids,
intptr_t num_old_tlc_cids,
bool is_rollback) {
// The [ProgramReloadContext] is no longer source-of-truth for GC after we
// return, so we restore size information for all classes.
if (is_rollback) {
SetNumCids(num_old_cids, num_old_tlc_cids);
// Don't use memmove, which changes this from a relaxed atomic operation
// to a non-atomic operation.
auto table = table_.load();
for (intptr_t i = 0; i < num_old_cids; i++) {
table[i] = old_table[i];
}
auto tlc_table = tlc_table_.load();
for (intptr_t i = 0; i < num_old_tlc_cids; i++) {
tlc_table[i] = old_tlc_table[i];
}
} else {
CopySizesFromClassObjects();
}
// Can't free these tables immediately as another thread (e.g., concurrent
// marker or sweeper) may be between loading the table pointer and loading
// the table element. The table will be freed at the next major GC or
// isolate shutdown.
AddOldTable(old_table);
AddOldTable(old_tlc_table);
}
// Thread-safe.
ClassPtr At(intptr_t cid) const {
ASSERT(IsValidIndex(cid));
if (IsTopLevelCid(cid)) {
return tlc_table_.load()[IndexFromTopLevelCid(cid)];
}
return table_.load()[cid];
}
intptr_t SizeAt(intptr_t index) const {
if (IsTopLevelCid(index)) {
return 0;
}
return shared_class_table_->SizeAt(index);
}
void SetAt(intptr_t index, ClassPtr raw_cls);
void UpdateClassSize(intptr_t cid, ClassPtr raw_cls);
bool IsValidIndex(intptr_t cid) const {
if (IsTopLevelCid(cid)) {
return IndexFromTopLevelCid(cid) < tlc_top_;
}
return shared_class_table_->IsValidIndex(cid);
}
bool HasValidClassAt(intptr_t cid) const {
ASSERT(IsValidIndex(cid));
if (IsTopLevelCid(cid)) {
return tlc_table_.load()[IndexFromTopLevelCid(cid)] != nullptr;
}
return table_.load()[cid] != nullptr;
}
intptr_t NumCids() const { return shared_class_table_->NumCids(); }
intptr_t NumTopLevelCids() const { return tlc_top_; }
intptr_t Capacity() const { return shared_class_table_->Capacity(); }
void Register(const Class& cls);
void RegisterTopLevel(const Class& cls);
void AllocateIndex(intptr_t index);
void Unregister(intptr_t index);
void UnregisterTopLevel(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();
#if defined(DART_PRECOMPILER)
void PrintObjectLayout(const char* filename);
#endif
#ifndef PRODUCT
// Describes layout of heap stats for code generation. See offset_extractor.cc
struct ArrayTraits {
static intptr_t elements_start_offset() { return 0; }
static constexpr intptr_t kElementSize = sizeof(uint8_t);
};
#endif
#ifndef PRODUCT
void AllocationProfilePrintJSON(JSONStream* stream, bool internal);
void PrintToJSONObject(JSONObject* object);
#endif // !PRODUCT
// Deallocates table copies. Do not call during concurrent access to table.
void FreeOldTables();
static bool IsTopLevelCid(intptr_t cid) { return cid >= kTopLevelCidOffset; }
static intptr_t IndexFromTopLevelCid(intptr_t cid) {
ASSERT(IsTopLevelCid(cid));
return cid - kTopLevelCidOffset;
}
static intptr_t CidFromTopLevelIndex(intptr_t index) {
return kTopLevelCidOffset + index;
}
private:
friend class GCMarker;
friend class MarkingWeakVisitor;
friend class Scavenger;
friend class ScavengerWeakVisitor;
friend class Dart;
friend Isolate* CreateWithinExistingIsolateGroup(IsolateGroup* group,
const char* name,
char** error);
friend class IsolateGroup; // for table()
static const int kInitialCapacity = SharedClassTable::kInitialCapacity;
static const int kCapacityIncrement = SharedClassTable::kCapacityIncrement;
static const intptr_t kTopLevelCidOffset = (1 << 16);
void AddOldTable(ClassPtr* old_table);
void AllocateTopLevelIndex(intptr_t index);
void Grow(intptr_t index);
void GrowTopLevel(intptr_t index);
ClassPtr* table() { return table_.load(); }
void set_table(ClassPtr* table);
// Used to drop recently added classes.
void SetNumCids(intptr_t num_cids, intptr_t num_tlc_cids) {
shared_class_table_->SetNumCids(num_cids);
ASSERT(num_cids <= top_);
top_ = num_cids;
ASSERT(num_tlc_cids <= tlc_top_);
tlc_top_ = num_tlc_cids;
}
intptr_t top_;
intptr_t capacity_;
intptr_t tlc_top_;
intptr_t tlc_capacity_;
// Copy-on-write is used for table_, with old copies stored in
// old_class_tables_.
AcqRelAtomic<ClassPtr*> table_;
AcqRelAtomic<ClassPtr*> tlc_table_;
MallocGrowableArray<ClassPtr*>* old_class_tables_;
SharedClassTable* shared_class_table_;
DISALLOW_COPY_AND_ASSIGN(ClassTable);
};
#if !defined(PRODUCT)
DART_FORCE_INLINE bool SharedClassTable::TraceAllocationFor(intptr_t cid) {
ASSERT(cid > 0);
if (ClassTable::IsTopLevelCid(cid)) {
return false;
}
ASSERT(cid < top_);
return trace_allocation_table_.load()[cid] != 0;
}
#endif // !defined(PRODUCT)
} // namespace dart
#endif // RUNTIME_VM_CLASS_TABLE_H_