1e34ad08ee
Using memmove/memset bypasses non-trivial assignment. In particular, it changes relaxed atomic operations into non-atomic operations. Fixes -Werror=class-memaccess errors in gcc. Change-Id: Ic4589fe7a564a268a578a856058c2d2183961b46 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/142549 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Alexander Aprelev <aam@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
521 lines
17 KiB
C++
521 lines
17 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.
|
|
|
|
#include "vm/class_table.h"
|
|
|
|
#include <memory>
|
|
|
|
#include "platform/atomic.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/growable_array.h"
|
|
#include "vm/heap/heap.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_graph.h"
|
|
#include "vm/raw_object.h"
|
|
#include "vm/visitor.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(bool, print_class_table, false, "Print initial class table.");
|
|
|
|
SharedClassTable::SharedClassTable()
|
|
: top_(kNumPredefinedCids),
|
|
capacity_(0),
|
|
old_tables_(new MallocGrowableArray<void*>()) {
|
|
if (Dart::vm_isolate() == NULL) {
|
|
ASSERT(kInitialCapacity >= kNumPredefinedCids);
|
|
capacity_ = kInitialCapacity;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
table_.store(reinterpret_cast<RelaxedAtomic<intptr_t>*>(
|
|
calloc(capacity_, sizeof(RelaxedAtomic<intptr_t>))));
|
|
} else {
|
|
// Duplicate the class table from the VM isolate.
|
|
auto vm_shared_class_table = Dart::vm_isolate()->group()->class_table();
|
|
capacity_ = vm_shared_class_table->capacity_;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
RelaxedAtomic<intptr_t>* table = reinterpret_cast<RelaxedAtomic<intptr_t>*>(
|
|
calloc(capacity_, sizeof(RelaxedAtomic<intptr_t>)));
|
|
// The following cids don't have a corresponding class object in Dart code.
|
|
// We therefore need to initialize them eagerly.
|
|
for (intptr_t i = kObjectCid; i < kInstanceCid; i++) {
|
|
table[i] = vm_shared_class_table->SizeAt(i);
|
|
}
|
|
table[kTypeArgumentsCid] = vm_shared_class_table->SizeAt(kTypeArgumentsCid);
|
|
table[kFreeListElement] = vm_shared_class_table->SizeAt(kFreeListElement);
|
|
table[kForwardingCorpse] = vm_shared_class_table->SizeAt(kForwardingCorpse);
|
|
table[kDynamicCid] = vm_shared_class_table->SizeAt(kDynamicCid);
|
|
table[kVoidCid] = vm_shared_class_table->SizeAt(kVoidCid);
|
|
table[kNeverCid] = vm_shared_class_table->SizeAt(kNeverCid);
|
|
table_.store(table);
|
|
}
|
|
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
unboxed_fields_map_ = static_cast<UnboxedFieldBitmap*>(
|
|
calloc(capacity_, sizeof(UnboxedFieldBitmap)));
|
|
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
#ifndef PRODUCT
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
trace_allocation_table_.store(
|
|
static_cast<uint8_t*>(calloc(capacity_, sizeof(uint8_t))));
|
|
#endif // !PRODUCT
|
|
}
|
|
SharedClassTable::~SharedClassTable() {
|
|
if (old_tables_ != NULL) {
|
|
FreeOldTables();
|
|
delete old_tables_;
|
|
}
|
|
free(table_.load());
|
|
free(unboxed_fields_map_);
|
|
|
|
NOT_IN_PRODUCT(free(trace_allocation_table_.load()));
|
|
}
|
|
|
|
ClassTable::ClassTable(SharedClassTable* shared_class_table)
|
|
: top_(kNumPredefinedCids),
|
|
capacity_(0),
|
|
table_(NULL),
|
|
old_class_tables_(new MallocGrowableArray<RawClass**>()),
|
|
shared_class_table_(shared_class_table) {
|
|
if (Dart::vm_isolate() == NULL) {
|
|
ASSERT(kInitialCapacity >= kNumPredefinedCids);
|
|
capacity_ = kInitialCapacity;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
table_.store(static_cast<RawClass**>(calloc(capacity_, sizeof(RawClass*))));
|
|
} else {
|
|
// Duplicate the class table from the VM isolate.
|
|
ClassTable* vm_class_table = Dart::vm_isolate()->class_table();
|
|
capacity_ = vm_class_table->capacity_;
|
|
// Note that [calloc] will zero-initialize the memory.
|
|
RawClass** table =
|
|
static_cast<RawClass**>(calloc(capacity_, sizeof(RawClass*)));
|
|
// The following cids don't have a corresponding class object in Dart code.
|
|
// We therefore need to initialize them eagerly.
|
|
for (intptr_t i = kObjectCid; i < kInstanceCid; i++) {
|
|
table[i] = vm_class_table->At(i);
|
|
}
|
|
table[kTypeArgumentsCid] = vm_class_table->At(kTypeArgumentsCid);
|
|
table[kFreeListElement] = vm_class_table->At(kFreeListElement);
|
|
table[kForwardingCorpse] = vm_class_table->At(kForwardingCorpse);
|
|
table[kDynamicCid] = vm_class_table->At(kDynamicCid);
|
|
table[kVoidCid] = vm_class_table->At(kVoidCid);
|
|
table[kNeverCid] = vm_class_table->At(kNeverCid);
|
|
table_.store(table);
|
|
}
|
|
}
|
|
|
|
ClassTable::~ClassTable() {
|
|
if (old_class_tables_ != nullptr) {
|
|
FreeOldTables();
|
|
delete old_class_tables_;
|
|
}
|
|
free(table_.load());
|
|
}
|
|
|
|
void ClassTable::AddOldTable(RawClass** old_class_table) {
|
|
ASSERT(Thread::Current()->IsMutatorThread());
|
|
old_class_tables_->Add(old_class_table);
|
|
}
|
|
|
|
void ClassTable::FreeOldTables() {
|
|
while (old_class_tables_->length() > 0) {
|
|
free(old_class_tables_->RemoveLast());
|
|
}
|
|
}
|
|
|
|
void SharedClassTable::AddOldTable(intptr_t* old_table) {
|
|
ASSERT(Thread::Current()->IsMutatorThread());
|
|
old_tables_->Add(old_table);
|
|
}
|
|
|
|
void SharedClassTable::FreeOldTables() {
|
|
while (old_tables_->length() > 0) {
|
|
free(old_tables_->RemoveLast());
|
|
}
|
|
}
|
|
|
|
void ClassTable::Register(const Class& cls) {
|
|
ASSERT(Thread::Current()->IsMutatorThread());
|
|
|
|
const intptr_t index = cls.id();
|
|
|
|
// During the transition period we would like [SharedClassTable] to operate in
|
|
// parallel to [ClassTable].
|
|
|
|
const intptr_t instance_size =
|
|
cls.is_abstract() ? 0 : Class::host_instance_size(cls.raw());
|
|
|
|
const intptr_t expected_cid =
|
|
shared_class_table_->Register(index, instance_size);
|
|
|
|
if (index != kIllegalCid) {
|
|
ASSERT(index > 0 && index < kNumPredefinedCids && index < top_);
|
|
ASSERT(table_.load()[index] == nullptr);
|
|
table_.load()[index] = cls.raw();
|
|
} else {
|
|
if (top_ == capacity_) {
|
|
const intptr_t new_capacity = capacity_ + kCapacityIncrement;
|
|
Grow(new_capacity);
|
|
}
|
|
ASSERT(top_ < capacity_);
|
|
cls.set_id(top_);
|
|
table_.load()[top_] = cls.raw();
|
|
top_++; // Increment next index.
|
|
}
|
|
ASSERT(expected_cid == cls.id());
|
|
}
|
|
|
|
intptr_t SharedClassTable::Register(intptr_t index, intptr_t size) {
|
|
if (!Class::is_valid_id(top_)) {
|
|
FATAL1("Fatal error in SharedClassTable::Register: invalid index %" Pd "\n",
|
|
top_);
|
|
}
|
|
|
|
ASSERT(Thread::Current()->IsMutatorThread());
|
|
if (index != kIllegalCid) {
|
|
// We are registring the size of a predefined class.
|
|
ASSERT(index > 0 && index < kNumPredefinedCids);
|
|
SetSizeAt(index, size);
|
|
return index;
|
|
} else {
|
|
ASSERT(size == 0);
|
|
if (top_ == capacity_) {
|
|
const intptr_t new_capacity = capacity_ + kCapacityIncrement;
|
|
Grow(new_capacity);
|
|
}
|
|
ASSERT(top_ < capacity_);
|
|
table_.load()[top_] = size;
|
|
return top_++; // Increment next index.
|
|
}
|
|
}
|
|
|
|
void ClassTable::AllocateIndex(intptr_t index) {
|
|
// This is called by a snapshot reader.
|
|
shared_class_table_->AllocateIndex(index);
|
|
ASSERT(Class::is_valid_id(index));
|
|
|
|
if (index >= capacity_) {
|
|
const intptr_t new_capacity = index + kCapacityIncrement;
|
|
Grow(new_capacity);
|
|
}
|
|
|
|
ASSERT(table_.load()[index] == nullptr);
|
|
if (index >= top_) {
|
|
top_ = index + 1;
|
|
}
|
|
|
|
ASSERT(top_ == shared_class_table_->top_);
|
|
ASSERT(capacity_ == shared_class_table_->capacity_);
|
|
}
|
|
|
|
void ClassTable::Grow(intptr_t new_capacity) {
|
|
ASSERT(new_capacity > capacity_);
|
|
|
|
auto old_table = table_.load();
|
|
auto new_table = static_cast<RawClass**>(
|
|
malloc(new_capacity * sizeof(RawClass*))); // NOLINT
|
|
intptr_t i;
|
|
for (i = 0; i < capacity_; i++) {
|
|
// Don't use memmove, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_table[i] = old_table[i];
|
|
}
|
|
for (; i < new_capacity; i++) {
|
|
// Don't use memset, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_table[i] = 0;
|
|
}
|
|
old_class_tables_->Add(old_table);
|
|
table_.store(new_table);
|
|
|
|
capacity_ = new_capacity;
|
|
}
|
|
|
|
void SharedClassTable::AllocateIndex(intptr_t index) {
|
|
// This is called by a snapshot reader.
|
|
ASSERT(Class::is_valid_id(index));
|
|
|
|
if (index >= capacity_) {
|
|
const intptr_t new_capacity = index + kCapacityIncrement;
|
|
Grow(new_capacity);
|
|
}
|
|
|
|
ASSERT(table_.load()[index] == 0);
|
|
if (index >= top_) {
|
|
top_ = index + 1;
|
|
}
|
|
}
|
|
|
|
void SharedClassTable::Grow(intptr_t new_capacity) {
|
|
ASSERT(new_capacity >= capacity_);
|
|
|
|
RelaxedAtomic<intptr_t>* old_table = table_.load();
|
|
RelaxedAtomic<intptr_t>* new_table =
|
|
reinterpret_cast<RelaxedAtomic<intptr_t>*>(
|
|
malloc(new_capacity * sizeof(RelaxedAtomic<intptr_t>))); // NOLINT
|
|
|
|
intptr_t i;
|
|
for (i = 0; i < capacity_; i++) {
|
|
// Don't use memmove, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_table[i] = old_table[i];
|
|
}
|
|
for (; i < new_capacity; i++) {
|
|
// Don't use memset, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_table[i] = 0;
|
|
}
|
|
|
|
#if !defined(PRODUCT)
|
|
auto old_trace_table = trace_allocation_table_.load();
|
|
auto new_trace_table =
|
|
static_cast<uint8_t*>(malloc(new_capacity * sizeof(uint8_t))); // NOLINT
|
|
for (i = 0; i < capacity_; i++) {
|
|
// Don't use memmove, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_trace_table[i] = old_trace_table[i];
|
|
}
|
|
for (; i < new_capacity; i++) {
|
|
// Don't use memset, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_trace_table[i] = 0;
|
|
}
|
|
#endif
|
|
|
|
old_tables_->Add(old_table);
|
|
table_.store(new_table);
|
|
NOT_IN_PRODUCT(old_tables_->Add(old_trace_table));
|
|
NOT_IN_PRODUCT(trace_allocation_table_.store(new_trace_table));
|
|
|
|
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
auto old_unboxed_fields_map = unboxed_fields_map_;
|
|
auto new_unboxed_fields_map = static_cast<UnboxedFieldBitmap*>(
|
|
malloc(new_capacity * sizeof(UnboxedFieldBitmap)));
|
|
for (i = 0; i < capacity_; i++) {
|
|
// Don't use memmove, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_unboxed_fields_map[i] = old_unboxed_fields_map[i];
|
|
}
|
|
for (; i < new_capacity; i++) {
|
|
// Don't use memset, which changes this from a relaxed atomic operation
|
|
// to a non-atomic operation.
|
|
new_unboxed_fields_map[i] = UnboxedFieldBitmap(0);
|
|
}
|
|
old_tables_->Add(old_unboxed_fields_map);
|
|
unboxed_fields_map_ = new_unboxed_fields_map;
|
|
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
|
|
capacity_ = new_capacity;
|
|
}
|
|
|
|
void ClassTable::Unregister(intptr_t index) {
|
|
shared_class_table_->Unregister(index);
|
|
table_.load()[index] = nullptr;
|
|
}
|
|
|
|
void SharedClassTable::Unregister(intptr_t index) {
|
|
table_.load()[index] = 0;
|
|
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
unboxed_fields_map_[index].Reset();
|
|
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
}
|
|
|
|
void ClassTable::Remap(intptr_t* old_to_new_cid) {
|
|
ASSERT(Thread::Current()->IsAtSafepoint());
|
|
const intptr_t num_cids = NumCids();
|
|
std::unique_ptr<RawClass*[]> cls_by_old_cid(new RawClass*[num_cids]);
|
|
auto* table = table_.load();
|
|
memmove(cls_by_old_cid.get(), table, sizeof(RawClass*) * num_cids);
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
table[old_to_new_cid[i]] = cls_by_old_cid[i];
|
|
}
|
|
}
|
|
|
|
void SharedClassTable::Remap(intptr_t* old_to_new_cid) {
|
|
ASSERT(Thread::Current()->IsAtSafepoint());
|
|
const intptr_t num_cids = NumCids();
|
|
std::unique_ptr<intptr_t[]> size_by_old_cid(new intptr_t[num_cids]);
|
|
auto* table = table_.load();
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
size_by_old_cid[i] = table[i];
|
|
}
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
table[old_to_new_cid[i]] = size_by_old_cid[i];
|
|
}
|
|
|
|
#if defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
std::unique_ptr<UnboxedFieldBitmap[]> unboxed_fields_by_old_cid(
|
|
new UnboxedFieldBitmap[num_cids]);
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
unboxed_fields_by_old_cid[i] = unboxed_fields_map_[i];
|
|
}
|
|
for (intptr_t i = 0; i < num_cids; i++) {
|
|
unboxed_fields_map_[old_to_new_cid[i]] = unboxed_fields_by_old_cid[i];
|
|
}
|
|
#endif // defined(SUPPORT_UNBOXED_INSTANCE_FIELDS)
|
|
}
|
|
|
|
void ClassTable::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
|
ASSERT(visitor != NULL);
|
|
visitor->set_gc_root_type("class table");
|
|
if (top_ != 0) {
|
|
auto* table = table_.load();
|
|
RawObject** from = reinterpret_cast<RawObject**>(&table[0]);
|
|
RawObject** to = reinterpret_cast<RawObject**>(&table[top_ - 1]);
|
|
visitor->VisitPointers(from, to);
|
|
}
|
|
visitor->clear_gc_root_type();
|
|
}
|
|
|
|
void ClassTable::CopySizesFromClassObjects() {
|
|
ASSERT(kIllegalCid == 0);
|
|
for (intptr_t i = 1; i < top_; i++) {
|
|
SetAt(i, At(i));
|
|
}
|
|
}
|
|
|
|
void ClassTable::Validate() {
|
|
Class& cls = Class::Handle();
|
|
for (intptr_t cid = kNumPredefinedCids; cid < top_; cid++) {
|
|
// Some of the class table entries maybe NULL as we create some
|
|
// top level classes but do not add them to the list of anonymous
|
|
// classes in a library if there are no top level fields or functions.
|
|
// Since there are no references to these top level classes they are
|
|
// not written into a full snapshot and will not be recreated when
|
|
// we read back the full snapshot. These class slots end up with NULL
|
|
// entries.
|
|
if (HasValidClassAt(cid)) {
|
|
cls = At(cid);
|
|
ASSERT(cls.IsClass());
|
|
ASSERT(cls.id() == cid);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClassTable::Print() {
|
|
Class& cls = Class::Handle();
|
|
String& name = String::Handle();
|
|
|
|
for (intptr_t i = 1; i < top_; i++) {
|
|
if (!HasValidClassAt(i)) {
|
|
continue;
|
|
}
|
|
cls = At(i);
|
|
if (cls.raw() != reinterpret_cast<RawClass*>(0)) {
|
|
name = cls.Name();
|
|
OS::PrintErr("%" Pd ": %s\n", i, name.ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
void ClassTable::SetAt(intptr_t index, RawClass* raw_cls) {
|
|
// This is called by snapshot reader and class finalizer.
|
|
ASSERT(index < capacity_);
|
|
const intptr_t size =
|
|
raw_cls == nullptr ? 0 : Class::host_instance_size(raw_cls);
|
|
shared_class_table_->SetSizeAt(index, size);
|
|
table_.load()[index] = raw_cls;
|
|
}
|
|
|
|
#ifndef PRODUCT
|
|
void ClassTable::PrintToJSONObject(JSONObject* object) {
|
|
Class& cls = Class::Handle();
|
|
object->AddProperty("type", "ClassList");
|
|
{
|
|
JSONArray members(object, "classes");
|
|
for (intptr_t i = 1; i < top_; i++) {
|
|
if (HasValidClassAt(i)) {
|
|
cls = At(i);
|
|
members.AddValue(cls);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SharedClassTable::ShouldUpdateSizeForClassId(intptr_t cid) {
|
|
return !RawObject::IsVariableSizeClassId(cid);
|
|
}
|
|
|
|
intptr_t SharedClassTable::ClassOffsetFor(intptr_t cid) {
|
|
return cid * sizeof(uint8_t); // NOLINT
|
|
}
|
|
|
|
|
|
void ClassTable::AllocationProfilePrintJSON(JSONStream* stream, bool internal) {
|
|
Isolate* isolate = Isolate::Current();
|
|
ASSERT(isolate != NULL);
|
|
auto isolate_group = isolate->group();
|
|
Heap* heap = isolate_group->heap();
|
|
ASSERT(heap != NULL);
|
|
JSONObject obj(stream);
|
|
obj.AddProperty("type", "AllocationProfile");
|
|
if (isolate_group->last_allocationprofile_accumulator_reset_timestamp() !=
|
|
0) {
|
|
obj.AddPropertyF(
|
|
"dateLastAccumulatorReset", "%" Pd64 "",
|
|
isolate_group->last_allocationprofile_accumulator_reset_timestamp());
|
|
}
|
|
if (isolate_group->last_allocationprofile_gc_timestamp() != 0) {
|
|
obj.AddPropertyF("dateLastServiceGC", "%" Pd64 "",
|
|
isolate_group->last_allocationprofile_gc_timestamp());
|
|
}
|
|
|
|
if (internal) {
|
|
JSONObject heaps(&obj, "_heaps");
|
|
{ heap->PrintToJSONObject(Heap::kNew, &heaps); }
|
|
{ heap->PrintToJSONObject(Heap::kOld, &heaps); }
|
|
}
|
|
|
|
{
|
|
JSONObject memory(&obj, "memoryUsage");
|
|
{ heap->PrintMemoryUsageJSON(&memory); }
|
|
}
|
|
|
|
Thread* thread = Thread::Current();
|
|
CountObjectsVisitor visitor(thread, NumCids());
|
|
{
|
|
HeapIterationScope iter(thread);
|
|
iter.IterateObjects(&visitor);
|
|
isolate->group()->VisitWeakPersistentHandles(&visitor);
|
|
}
|
|
|
|
{
|
|
JSONArray arr(&obj, "members");
|
|
Class& cls = Class::Handle();
|
|
for (intptr_t i = 3; i < top_; i++) {
|
|
if (!HasValidClassAt(i)) continue;
|
|
|
|
cls = At(i);
|
|
if (cls.IsNull()) continue;
|
|
|
|
JSONObject obj(&arr);
|
|
obj.AddProperty("type", "ClassHeapStats");
|
|
obj.AddProperty("class", cls);
|
|
intptr_t count = visitor.new_count_[i] + visitor.old_count_[i];
|
|
intptr_t size = visitor.new_size_[i] + visitor.old_size_[i];
|
|
obj.AddProperty64("instancesAccumulated", count);
|
|
obj.AddProperty64("accumulatedSize", size);
|
|
obj.AddProperty64("instancesCurrent", count);
|
|
obj.AddProperty64("bytesCurrent", size);
|
|
|
|
if (internal) {
|
|
{
|
|
JSONArray new_stats(&obj, "_new");
|
|
new_stats.AddValue(visitor.new_count_[i]);
|
|
new_stats.AddValue(visitor.new_size_[i]);
|
|
new_stats.AddValue(visitor.new_external_size_[i]);
|
|
}
|
|
{
|
|
JSONArray old_stats(&obj, "_old");
|
|
old_stats.AddValue(visitor.old_count_[i]);
|
|
old_stats.AddValue(visitor.old_size_[i]);
|
|
old_stats.AddValue(visitor.old_external_size_[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif // !PRODUCT
|
|
|
|
} // namespace dart
|