mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
6f633c364a
This reverts commit 0bd5a2b05e
.
Fixes https://github.com/dart-lang/sdk/issues/51023
TEST=CQ
Change-Id: I4b643976a59adba8bd228a96ac75ae25ef8e206c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279120
Commit-Queue: Ben Konyi <bkonyi@google.com>
Reviewed-by: Derek Xu <derekx@google.com>
604 lines
18 KiB
C++
604 lines
18 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 <tuple>
|
|
#include <utility>
|
|
|
|
#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 JSONArray;
|
|
class JSONObject;
|
|
class JSONStream;
|
|
template <typename T>
|
|
class MallocGrowableArray;
|
|
class ObjectPointerVisitor;
|
|
class PersistentHandle;
|
|
|
|
// A 64-bit bitmap describing unboxed fields in a class.
|
|
//
|
|
// There is a bit for each word in an instance of the class.
|
|
//
|
|
// Words corresponding to set bits must be ignored by the GC because they
|
|
// don't contain pointers. All words beyond the first 64 words of an object
|
|
// are expected to contain pointers.
|
|
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 void Clear(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_;
|
|
};
|
|
|
|
// Allocator used to manage memory for ClassTable arrays and ClassTable
|
|
// objects themselves.
|
|
//
|
|
// This allocator provides delayed free functionality: normally class tables
|
|
// can't be freed unless all mutator and helper threads are stopped because
|
|
// some of these threads might be holding a pointer to a table which we
|
|
// want to free. Instead of stopping the world whenever we need to free
|
|
// a table (e.g. freeing old table after growing) we delay freeing until an
|
|
// occasional GC which will need to stop the world anyway.
|
|
class ClassTableAllocator : public ValueObject {
|
|
public:
|
|
ClassTableAllocator();
|
|
~ClassTableAllocator();
|
|
|
|
// Allocate an array of T with |len| elements.
|
|
//
|
|
// Does *not* initialize the memory.
|
|
template <class T>
|
|
inline T* Alloc(intptr_t len) {
|
|
return reinterpret_cast<T*>(dart::malloc(len * sizeof(T)));
|
|
}
|
|
|
|
// Allocate a zero initialized array of T with |len| elements.
|
|
template <class T>
|
|
inline T* AllocZeroInitialized(intptr_t len) {
|
|
return reinterpret_cast<T*>(dart::calloc(len, sizeof(T)));
|
|
}
|
|
|
|
// Clone the given |array| with |size| elements.
|
|
template <class T>
|
|
inline T* Clone(T* array, intptr_t size) {
|
|
if (array == nullptr) {
|
|
ASSERT(size == 0);
|
|
return nullptr;
|
|
}
|
|
auto result = Alloc<T>(size);
|
|
memmove(result, array, size * sizeof(T));
|
|
return result;
|
|
}
|
|
|
|
// Copy |size| elements from the given |array| into a new
|
|
// array with space for |new_size| elements. Then |Free|
|
|
// the original |array|.
|
|
//
|
|
// |new_size| is expected to be larger than |size|.
|
|
template <class T>
|
|
inline T* Realloc(T* array, intptr_t size, intptr_t new_size) {
|
|
ASSERT(size < new_size);
|
|
auto result = AllocZeroInitialized<T>(new_size);
|
|
if (size != 0) {
|
|
ASSERT(result != nullptr);
|
|
memmove(result, array, size * sizeof(T));
|
|
}
|
|
Free(array);
|
|
return result;
|
|
}
|
|
|
|
// Schedule deletion of the given ClassTable.
|
|
void Free(ClassTable* table);
|
|
|
|
// Schedule freeing of the given pointer.
|
|
void Free(void* ptr);
|
|
|
|
// Free all objects which were scheduled by |Free|. Expected to only be
|
|
// called on |IsolateGroup| shutdown or when the world is stopped and no
|
|
// thread can be using a stale class table pointer.
|
|
void FreePending();
|
|
|
|
private:
|
|
typedef void (*Deleter)(void*);
|
|
MallocGrowableArray<std::pair<void*, Deleter>>* pending_freed_;
|
|
};
|
|
|
|
// A table with the given |Columns| indexed by class id.
|
|
//
|
|
// Each column is a continuous array of a the given type. All columns have
|
|
// the same number of used elements (|num_cids()|) and the same capacity.
|
|
template <typename CidType, typename... Columns>
|
|
class CidIndexedTable {
|
|
public:
|
|
explicit CidIndexedTable(ClassTableAllocator* allocator)
|
|
: allocator_(allocator) {}
|
|
|
|
~CidIndexedTable() {
|
|
std::apply([&](auto&... column) { (allocator_->Free(column.load()), ...); },
|
|
columns_);
|
|
}
|
|
|
|
CidIndexedTable(const CidIndexedTable& other) = delete;
|
|
|
|
void SetNumCidsAndCapacity(intptr_t new_num_cids, intptr_t new_capacity) {
|
|
columns_ = std::apply(
|
|
[&](auto&... column) {
|
|
return std::make_tuple(
|
|
allocator_->Realloc(column.load(), num_cids_, new_capacity)...);
|
|
},
|
|
columns_);
|
|
capacity_ = new_capacity;
|
|
SetNumCids(new_num_cids);
|
|
}
|
|
|
|
void AllocateIndex(intptr_t index, bool* did_grow) {
|
|
*did_grow = EnsureCapacity(index);
|
|
SetNumCids(Utils::Maximum(num_cids_, index + 1));
|
|
}
|
|
|
|
intptr_t AddRow(bool* did_grow) {
|
|
*did_grow = EnsureCapacity(num_cids_);
|
|
intptr_t id = num_cids_;
|
|
SetNumCids(num_cids_ + 1);
|
|
return id;
|
|
}
|
|
|
|
void ShrinkTo(intptr_t new_num_cids) {
|
|
ASSERT(new_num_cids <= num_cids_);
|
|
num_cids_ = new_num_cids;
|
|
}
|
|
|
|
bool IsValidIndex(intptr_t index) const {
|
|
return 0 <= index && index < num_cids_;
|
|
}
|
|
|
|
void CopyFrom(const CidIndexedTable& other) {
|
|
ASSERT(allocator_ == other.allocator_);
|
|
|
|
std::apply([&](auto&... column) { (allocator_->Free(column.load()), ...); },
|
|
columns_);
|
|
|
|
columns_ = std::apply(
|
|
[&](auto&... column) {
|
|
return std::make_tuple(
|
|
allocator_->Clone(column.load(), other.num_cids_)...);
|
|
},
|
|
other.columns_);
|
|
capacity_ = num_cids_ = other.num_cids_;
|
|
}
|
|
|
|
void Remap(intptr_t* old_to_new_cid) {
|
|
CidIndexedTable clone(allocator_);
|
|
clone.CopyFrom(*this);
|
|
RemapAllColumns(clone, old_to_new_cid,
|
|
std::index_sequence_for<Columns...>{});
|
|
}
|
|
|
|
template <
|
|
intptr_t kColumnIndex,
|
|
typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
|
|
T* GetColumn() {
|
|
return std::get<kColumnIndex>(columns_).load();
|
|
}
|
|
|
|
template <
|
|
intptr_t kColumnIndex,
|
|
typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
|
|
const T* GetColumn() const {
|
|
return std::get<kColumnIndex>(columns_).load();
|
|
}
|
|
|
|
template <
|
|
intptr_t kColumnIndex,
|
|
typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
|
|
T& At(intptr_t index) {
|
|
ASSERT(IsValidIndex(index));
|
|
return GetColumn<kColumnIndex>()[index];
|
|
}
|
|
|
|
template <
|
|
intptr_t kColumnIndex,
|
|
typename T = std::tuple_element_t<kColumnIndex, std::tuple<Columns...>>>
|
|
const T& At(intptr_t index) const {
|
|
ASSERT(IsValidIndex(index));
|
|
return GetColumn<kColumnIndex>()[index];
|
|
}
|
|
|
|
intptr_t num_cids() const { return num_cids_; }
|
|
intptr_t capacity() const { return capacity_; }
|
|
|
|
private:
|
|
friend class ClassTable;
|
|
|
|
// Wrapper around AcqRelAtomic<T*> which makes it assignable and copyable
|
|
// so that we could put it inside an std::tuple.
|
|
template <typename T>
|
|
struct Ptr {
|
|
Ptr() : ptr(nullptr) {}
|
|
Ptr(T* ptr) : ptr(ptr) {} // NOLINT
|
|
|
|
Ptr(const Ptr& other) { ptr.store(other.ptr.load()); }
|
|
|
|
Ptr& operator=(const Ptr& other) {
|
|
ptr.store(other.load());
|
|
return *this;
|
|
}
|
|
|
|
T* load() const { return ptr.load(); }
|
|
|
|
AcqRelAtomic<T*> ptr = {nullptr};
|
|
};
|
|
|
|
void SetNumCids(intptr_t new_num_cids) {
|
|
if (new_num_cids > kClassIdTagMax) {
|
|
FATAL("Too many classes");
|
|
}
|
|
num_cids_ = new_num_cids;
|
|
}
|
|
|
|
bool EnsureCapacity(intptr_t index) {
|
|
if (index >= capacity_) {
|
|
SetNumCidsAndCapacity(num_cids_, index + kCapacityIncrement);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <intptr_t kColumnIndex>
|
|
void RemapColumn(const CidIndexedTable& old, intptr_t* old_to_new_cid) {
|
|
auto new_column = GetColumn<kColumnIndex>();
|
|
auto old_column = old.GetColumn<kColumnIndex>();
|
|
for (intptr_t i = 0; i < num_cids_; i++) {
|
|
new_column[old_to_new_cid[i]] = old_column[i];
|
|
}
|
|
}
|
|
|
|
template <std::size_t... Is>
|
|
void RemapAllColumns(const CidIndexedTable& old,
|
|
intptr_t* old_to_new_cid,
|
|
std::index_sequence<Is...>) {
|
|
(RemapColumn<Is>(old, old_to_new_cid), ...);
|
|
}
|
|
|
|
static constexpr intptr_t kCapacityIncrement = 256;
|
|
|
|
ClassTableAllocator* allocator_;
|
|
intptr_t num_cids_ = 0;
|
|
intptr_t capacity_ = 0;
|
|
std::tuple<Ptr<Columns>...> columns_;
|
|
};
|
|
|
|
// Registry of all known classes.
|
|
//
|
|
// The GC will only use information about instance size and unboxed field maps
|
|
// to scan instances and will not access class objects themselves. This
|
|
// information is stored in separate columns of the |classes_| table.
|
|
//
|
|
// # Concurrency & atomicity
|
|
//
|
|
// This table is read concurrently without locking (e.g. by GC threads) so
|
|
// there are some invariants that need to be observed when working with it.
|
|
//
|
|
// * When table is updated (e.g. when the table is grown or a new class is
|
|
// registered in a table) there must be a release barrier after the update.
|
|
// Such barrier will ensure that stores which populate the table are not
|
|
// reordered past the store which exposes the new grown table or exposes
|
|
// a new class id;
|
|
// * Old versions of the table can only be freed when the world is stopped:
|
|
// no mutator and no helper threads are running. To avoid freeing a table
|
|
// which some other thread is reading from.
|
|
//
|
|
// Note that torn reads are not a concern (e.g. it is fine to use
|
|
// memmove to copy class table contents) as long as an appropriate
|
|
// barrier is issued before the copy of the table can be observed.
|
|
//
|
|
// # Hot reload
|
|
//
|
|
// Each IsolateGroup contains two ClassTable fields: |class_table| and
|
|
// |heap_walk_class_table|. GC visitors use the second field to get ClassTable
|
|
// instance which they will use for visiting pointers inside instances in
|
|
// the heap. Usually these two fields will be pointing to the same table,
|
|
// except when IsolateGroup is in the middle of reload.
|
|
//
|
|
// When reloading |class_table| will be pointing to a copy of the original
|
|
// table. Kernel loading will be modifying this table, while GC
|
|
// workers can continue using original table still available through
|
|
// |heap_walk_class_table|. If hot reload succeeds, |heap_walk_class_table|
|
|
// will be dropped and |class_table| will become the source of truth. Otherwise,
|
|
// original table will be restored from |heap_walk_class_table|.
|
|
//
|
|
// See IsolateGroup methods CloneClassTableForReload, RestoreOriginalClassTable,
|
|
// DropOriginalClassTable.
|
|
class ClassTable : public MallocAllocated {
|
|
public:
|
|
explicit ClassTable(ClassTableAllocator* allocator);
|
|
|
|
~ClassTable();
|
|
|
|
ClassTable* Clone() const { return new ClassTable(*this); }
|
|
|
|
ClassPtr At(intptr_t cid) const {
|
|
if (IsTopLevelCid(cid)) {
|
|
return top_level_classes_.At<kClassIndex>(IndexFromTopLevelCid(cid));
|
|
}
|
|
return classes_.At<kClassIndex>(cid);
|
|
}
|
|
|
|
int32_t SizeAt(intptr_t index) const {
|
|
if (IsTopLevelCid(index)) {
|
|
return 0;
|
|
}
|
|
return classes_.At<kSizeIndex>(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 top_level_classes_.IsValidIndex(IndexFromTopLevelCid(cid));
|
|
}
|
|
return classes_.IsValidIndex(cid);
|
|
}
|
|
|
|
bool HasValidClassAt(intptr_t cid) const { return At(cid) != nullptr; }
|
|
|
|
UnboxedFieldBitmap GetUnboxedFieldsMapAt(intptr_t cid) const {
|
|
ASSERT(IsValidIndex(cid));
|
|
return classes_.At<kUnboxedFieldBitmapIndex>(cid);
|
|
}
|
|
|
|
void SetUnboxedFieldsMapAt(intptr_t cid, UnboxedFieldBitmap map) {
|
|
ASSERT(IsValidIndex(cid));
|
|
classes_.At<kUnboxedFieldBitmapIndex>(cid) = map;
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
bool ShouldTraceAllocationFor(intptr_t cid) {
|
|
return !IsTopLevelCid(cid) &&
|
|
(classes_.At<kAllocationTracingStateIndex>(cid) != kTracingDisabled);
|
|
}
|
|
|
|
void SetTraceAllocationFor(intptr_t cid, bool trace) {
|
|
classes_.At<kAllocationTracingStateIndex>(cid) =
|
|
trace ? kTraceAllocationBit : kTracingDisabled;
|
|
}
|
|
|
|
void SetCollectInstancesFor(intptr_t cid, bool trace) {
|
|
auto& slot = classes_.At<kAllocationTracingStateIndex>(cid);
|
|
if (trace) {
|
|
slot |= kCollectInstancesBit;
|
|
} else {
|
|
slot &= ~kCollectInstancesBit;
|
|
}
|
|
}
|
|
|
|
bool CollectInstancesFor(intptr_t cid) {
|
|
auto& slot = classes_.At<kAllocationTracingStateIndex>(cid);
|
|
return (slot & kCollectInstancesBit) != 0;
|
|
}
|
|
|
|
void UpdateCachedAllocationTracingStateTablePointer() {
|
|
cached_allocation_tracing_state_table_.store(
|
|
classes_.GetColumn<kAllocationTracingStateIndex>());
|
|
}
|
|
|
|
void PopulateUserVisibleNames();
|
|
|
|
const char* UserVisibleNameFor(intptr_t cid) {
|
|
if (!classes_.IsValidIndex(cid)) {
|
|
return nullptr;
|
|
}
|
|
return classes_.At<kClassNameIndex>(cid);
|
|
}
|
|
|
|
void SetUserVisibleNameFor(intptr_t cid, const char* name) {
|
|
ASSERT(classes_.At<kClassNameIndex>(cid) == nullptr);
|
|
classes_.At<kClassNameIndex>(cid) = name;
|
|
}
|
|
#else
|
|
void UpdateCachedAllocationTracingStateTablePointer() {}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
intptr_t NumCids() const {
|
|
return classes_.num_cids();
|
|
}
|
|
intptr_t Capacity() const {
|
|
return classes_.capacity();
|
|
}
|
|
|
|
intptr_t NumTopLevelCids() const {
|
|
return top_level_classes_.num_cids();
|
|
}
|
|
|
|
void Register(const Class& cls);
|
|
void AllocateIndex(intptr_t index);
|
|
|
|
void RegisterTopLevel(const Class& cls);
|
|
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);
|
|
};
|
|
|
|
static intptr_t allocation_tracing_state_table_offset() {
|
|
static_assert(sizeof(cached_allocation_tracing_state_table_) == kWordSize);
|
|
return OFFSET_OF(ClassTable, cached_allocation_tracing_state_table_);
|
|
}
|
|
|
|
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 ClassTableAllocator;
|
|
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 = 512;
|
|
|
|
static const intptr_t kTopLevelCidOffset = kClassIdTagMax + 1;
|
|
|
|
ClassTable(const ClassTable& original)
|
|
: allocator_(original.allocator_),
|
|
classes_(original.allocator_),
|
|
top_level_classes_(original.allocator_) {
|
|
classes_.CopyFrom(original.classes_);
|
|
|
|
#if !defined(PRODUCT)
|
|
// Copying classes_ doesn't perform a deep copy. Ensure we duplicate
|
|
// the class names to avoid double free crashes at shutdown.
|
|
for (intptr_t cid = 1; cid < classes_.num_cids(); ++cid) {
|
|
if (classes_.IsValidIndex(cid)) {
|
|
const char* cls_name = classes_.At<kClassNameIndex>(cid);
|
|
if (cls_name != nullptr) {
|
|
classes_.At<kClassNameIndex>(cid) = Utils::StrDup(cls_name);
|
|
}
|
|
}
|
|
}
|
|
#endif // !defined(PRODUCT)
|
|
|
|
top_level_classes_.CopyFrom(original.top_level_classes_);
|
|
UpdateCachedAllocationTracingStateTablePointer();
|
|
}
|
|
|
|
void AllocateTopLevelIndex(intptr_t index);
|
|
|
|
ClassPtr* table() {
|
|
return classes_.GetColumn<kClassIndex>();
|
|
}
|
|
|
|
// Used to drop recently added classes.
|
|
void SetNumCids(intptr_t num_cids, intptr_t num_tlc_cids) {
|
|
classes_.ShrinkTo(num_cids);
|
|
top_level_classes_.ShrinkTo(num_tlc_cids);
|
|
}
|
|
|
|
ClassTableAllocator* allocator_;
|
|
|
|
// Unfortunately std::tuple used by CidIndexedTable does not have a stable
|
|
// layout so we can't refer to its elements from generated code.
|
|
NOT_IN_PRODUCT(AcqRelAtomic<uint8_t*> cached_allocation_tracing_state_table_ =
|
|
{nullptr});
|
|
|
|
enum {
|
|
kClassIndex = 0,
|
|
kSizeIndex,
|
|
kUnboxedFieldBitmapIndex,
|
|
#if !defined(PRODUCT)
|
|
kAllocationTracingStateIndex,
|
|
kClassNameIndex,
|
|
#endif
|
|
};
|
|
|
|
#if !defined(PRODUCT)
|
|
CidIndexedTable<ClassIdTagType,
|
|
ClassPtr,
|
|
uint32_t,
|
|
UnboxedFieldBitmap,
|
|
uint8_t,
|
|
const char*>
|
|
classes_;
|
|
#else
|
|
CidIndexedTable<ClassIdTagType, ClassPtr, uint32_t, UnboxedFieldBitmap>
|
|
classes_;
|
|
#endif
|
|
|
|
#ifndef PRODUCT
|
|
enum {
|
|
kTracingDisabled = 0,
|
|
kTraceAllocationBit = (1 << 0),
|
|
kCollectInstancesBit = (1 << 1),
|
|
};
|
|
#endif // !PRODUCT
|
|
|
|
CidIndexedTable<classid_t, ClassPtr> top_level_classes_;
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
#endif // RUNTIME_VM_CLASS_TABLE_H_
|