// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/heap/object-stats.h"
#include "src/compilation-cache.h"
#include "src/counters.h"
#include "src/heap/heap-inl.h"
#include "src/isolate.h"
#include "src/macro-assembler.h"
#include "src/utils.h"
namespace v8 {
namespace internal {
static base::LazyMutex object_stats_mutex = LAZY_MUTEX_INITIALIZER;
void ObjectStats::ClearObjectStats(bool clear_last_time_stats) {
memset(object_counts_, 0, sizeof(object_counts_));
memset(object_sizes_, 0, sizeof(object_sizes_));
memset(over_allocated_, 0, sizeof(over_allocated_));
memset(size_histogram_, 0, sizeof(size_histogram_));
memset(over_allocated_histogram_, 0, sizeof(over_allocated_histogram_));
if (clear_last_time_stats) {
memset(object_counts_last_time_, 0, sizeof(object_counts_last_time_));
memset(object_sizes_last_time_, 0, sizeof(object_sizes_last_time_));
}
visited_fixed_array_sub_types_.clear();
}
// Tell the compiler to never inline this: occasionally, the optimizer will
// decide to inline this and unroll the loop, making the compiled code more than
// 100KB larger.
V8_NOINLINE static void PrintJSONArray(size_t* array, const int len) {
PrintF("[ ");
for (int i = 0; i < len; i++) {
PrintF("%zu", array[i]);
if (i != (len - 1)) PrintF(", ");
}
PrintF(" ]");
}
V8_NOINLINE static void DumpJSONArray(std::stringstream& stream, size_t* array,
const int len) {
stream << "[";
for (int i = 0; i < len; i++) {
stream << array[i];
if (i != (len - 1)) stream << ",";
}
stream << "]";
}
void ObjectStats::PrintKeyAndId(const char* key, int gc_count) {
PrintF("\"isolate\": \"%p\", \"id\": %d, \"key\": \"%s\", ",
reinterpret_cast<void*>(isolate()), gc_count, key);
}
void ObjectStats::PrintInstanceTypeJSON(const char* key, int gc_count,
const char* name, int index) {
PrintF("{ ");
PrintKeyAndId(key, gc_count);
PrintF("\"type\": \"instance_type_data\", ");
PrintF("\"instance_type\": %d, ", index);
PrintF("\"instance_type_name\": \"%s\", ", name);
PrintF("\"overall\": %zu, ", object_sizes_[index]);
PrintF("\"count\": %zu, ", object_counts_[index]);
PrintF("\"over_allocated\": %zu, ", over_allocated_[index]);
PrintF("\"histogram\": ");
PrintJSONArray(size_histogram_[index], kNumberOfBuckets);
PrintF(",");
PrintF("\"over_allocated_histogram\": ");
PrintJSONArray(over_allocated_histogram_[index], kNumberOfBuckets);
PrintF(" }\n");
}
void ObjectStats::PrintJSON(const char* key) {
double time = isolate()->time_millis_since_init();
int gc_count = heap()->gc_count();
// gc_descriptor
PrintF("{ ");
PrintKeyAndId(key, gc_count);
PrintF("\"type\": \"gc_descriptor\", \"time\": %f }\n", time);
// bucket_sizes
PrintF("{ ");
PrintKeyAndId(key, gc_count);
PrintF("\"type\": \"bucket_sizes\", \"sizes\": [ ");
for (int i = 0; i < kNumberOfBuckets; i++) {
PrintF("%d", 1 << (kFirstBucketShift + i));
if (i != (kNumberOfBuckets - 1)) PrintF(", ");
}
PrintF(" ] }\n");
#define INSTANCE_TYPE_WRAPPER(name) \
PrintInstanceTypeJSON(key, gc_count, #name, name);
#define CODE_KIND_WRAPPER(name) \
PrintInstanceTypeJSON(key, gc_count, "*CODE_" #name, \
FIRST_CODE_KIND_SUB_TYPE + Code::name);
#define FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER(name) \
PrintInstanceTypeJSON(key, gc_count, "*FIXED_ARRAY_" #name, \
FIRST_FIXED_ARRAY_SUB_TYPE + name);
#define CODE_AGE_WRAPPER(name) \
PrintInstanceTypeJSON( \
key, gc_count, "*CODE_AGE_" #name, \
FIRST_CODE_AGE_SUB_TYPE + Code::k##name##CodeAge - Code::kFirstCodeAge);
INSTANCE_TYPE_LIST(INSTANCE_TYPE_WRAPPER)
CODE_KIND_LIST(CODE_KIND_WRAPPER)
FIXED_ARRAY_SUB_INSTANCE_TYPE_LIST(FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER)
CODE_AGE_LIST_COMPLETE(CODE_AGE_WRAPPER)
#undef INSTANCE_TYPE_WRAPPER
#undef CODE_KIND_WRAPPER
#undef FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER
#undef CODE_AGE_WRAPPER
#undef PRINT_INSTANCE_TYPE_DATA
#undef PRINT_KEY_AND_ID
}
void ObjectStats::DumpInstanceTypeData(std::stringstream& stream,
const char* name, int index) {
stream << "\"" << name << "\":{";
stream << "\"type\":" << static_cast<int>(index) << ",";
stream << "\"overall\":" << object_sizes_[index] << ",";
stream << "\"count\":" << object_counts_[index] << ",";
stream << "\"over_allocated\":" << over_allocated_[index] << ",";
stream << "\"histogram\":";
DumpJSONArray(stream, size_histogram_[index], kNumberOfBuckets);
stream << ",\"over_allocated_histogram\":";
DumpJSONArray(stream, over_allocated_histogram_[index], kNumberOfBuckets);
stream << "},";
}
void ObjectStats::Dump(std::stringstream& stream) {
double time = isolate()->time_millis_since_init();
int gc_count = heap()->gc_count();
stream << "{";
stream << "\"isolate\":\"" << reinterpret_cast<void*>(isolate()) << "\",";
stream << "\"id\":" << gc_count << ",";
stream << "\"time\":" << time << ",";
stream << "\"bucket_sizes\":[";
for (int i = 0; i < kNumberOfBuckets; i++) {
stream << (1 << (kFirstBucketShift + i));
if (i != (kNumberOfBuckets - 1)) stream << ",";
}
stream << "],";
stream << "\"type_data\":{";
#define INSTANCE_TYPE_WRAPPER(name) DumpInstanceTypeData(stream, #name, name);
#define CODE_KIND_WRAPPER(name) \
DumpInstanceTypeData(stream, "*CODE_" #name, \
FIRST_CODE_KIND_SUB_TYPE + Code::name);
#define FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER(name) \
DumpInstanceTypeData(stream, "*FIXED_ARRAY_" #name, \
FIRST_FIXED_ARRAY_SUB_TYPE + name);
#define CODE_AGE_WRAPPER(name) \
DumpInstanceTypeData( \
stream, "*CODE_AGE_" #name, \
FIRST_CODE_AGE_SUB_TYPE + Code::k##name##CodeAge - Code::kFirstCodeAge);
INSTANCE_TYPE_LIST(INSTANCE_TYPE_WRAPPER);
CODE_KIND_LIST(CODE_KIND_WRAPPER);
FIXED_ARRAY_SUB_INSTANCE_TYPE_LIST(FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER);
CODE_AGE_LIST_COMPLETE(CODE_AGE_WRAPPER);
stream << "\"END\":{}}}";
#undef INSTANCE_TYPE_WRAPPER
#undef CODE_KIND_WRAPPER
#undef FIXED_ARRAY_SUB_INSTANCE_TYPE_WRAPPER
#undef CODE_AGE_WRAPPER
#undef PRINT_INSTANCE_TYPE_DATA
}
void ObjectStats::CheckpointObjectStats() {
base::LockGuard<base::Mutex> lock_guard(object_stats_mutex.Pointer());
Counters* counters = isolate()->counters();
#define ADJUST_LAST_TIME_OBJECT_COUNT(name) \
counters->count_of_##name()->Increment( \
static_cast<int>(object_counts_[name])); \
counters->count_of_##name()->Decrement( \
static_cast<int>(object_counts_last_time_[name])); \
counters->size_of_##name()->Increment( \
static_cast<int>(object_sizes_[name])); \
counters->size_of_##name()->Decrement( \
static_cast<int>(object_sizes_last_time_[name]));
INSTANCE_TYPE_LIST(ADJUST_LAST_TIME_OBJECT_COUNT)
#undef ADJUST_LAST_TIME_OBJECT_COUNT
int index;
#define ADJUST_LAST_TIME_OBJECT_COUNT(name) \
index = FIRST_CODE_KIND_SUB_TYPE + Code::name; \
counters->count_of_CODE_TYPE_##name()->Increment( \
static_cast<int>(object_counts_[index])); \
counters->count_of_CODE_TYPE_##name()->Decrement( \
static_cast<int>(object_counts_last_time_[index])); \
counters->size_of_CODE_TYPE_##name()->Increment( \
static_cast<int>(object_sizes_[index])); \
counters->size_of_CODE_TYPE_##name()->Decrement( \
static_cast<int>(object_sizes_last_time_[index]));
CODE_KIND_LIST(ADJUST_LAST_TIME_OBJECT_COUNT)
#undef ADJUST_LAST_TIME_OBJECT_COUNT
#define ADJUST_LAST_TIME_OBJECT_COUNT(name) \
index = FIRST_FIXED_ARRAY_SUB_TYPE + name; \
counters->count_of_FIXED_ARRAY_##name()->Increment( \
static_cast<int>(object_counts_[index])); \
counters->count_of_FIXED_ARRAY_##name()->Decrement( \
static_cast<int>(object_counts_last_time_[index])); \
counters->size_of_FIXED_ARRAY_##name()->Increment( \
static_cast<int>(object_sizes_[index])); \
counters->size_of_FIXED_ARRAY_##name()->Decrement( \
static_cast<int>(object_sizes_last_time_[index]));
FIXED_ARRAY_SUB_INSTANCE_TYPE_LIST(ADJUST_LAST_TIME_OBJECT_COUNT)
#undef ADJUST_LAST_TIME_OBJECT_COUNT
#define ADJUST_LAST_TIME_OBJECT_COUNT(name) \
index = \
FIRST_CODE_AGE_SUB_TYPE + Code::k##name##CodeAge - Code::kFirstCodeAge; \
counters->count_of_CODE_AGE_##name()->Increment( \
static_cast<int>(object_counts_[index])); \
counters->count_of_CODE_AGE_##name()->Decrement( \
static_cast<int>(object_counts_last_time_[index])); \
counters->size_of_CODE_AGE_##name()->Increment( \
static_cast<int>(object_sizes_[index])); \
counters->size_of_CODE_AGE_##name()->Decrement( \
static_cast<int>(object_sizes_last_time_[index]));
CODE_AGE_LIST_COMPLETE(ADJUST_LAST_TIME_OBJECT_COUNT)
#undef ADJUST_LAST_TIME_OBJECT_COUNT
MemCopy(object_counts_last_time_, object_counts_, sizeof(object_counts_));
MemCopy(object_sizes_last_time_, object_sizes_, sizeof(object_sizes_));
ClearObjectStats();
}
Isolate* ObjectStats::isolate() { return heap()->isolate(); }
void ObjectStatsCollector::CollectStatistics(HeapObject* obj) {
Map* map = obj->map();
// Record for the InstanceType.
int object_size = obj->Size();
stats_->RecordObjectStats(map->instance_type(), object_size);
// Record specific sub types where possible.
if (obj->IsMap()) RecordMapDetails(Map::cast(obj));
if (obj->IsObjectTemplateInfo() || obj->IsFunctionTemplateInfo()) {
RecordTemplateInfoDetails(TemplateInfo::cast(obj));
}
if (obj->IsBytecodeArray()) {
RecordBytecodeArrayDetails(BytecodeArray::cast(obj));
}
if (obj->IsCode()) RecordCodeDetails(Code::cast(obj));
if (obj->IsSharedFunctionInfo()) {
RecordSharedFunctionInfoDetails(SharedFunctionInfo::cast(obj));
}
if (obj->IsFixedArray()) RecordFixedArrayDetails(FixedArray::cast(obj));
if (obj->IsJSObject()) RecordJSObjectDetails(JSObject::cast(obj));
if (obj->IsJSWeakCollection()) {
RecordJSWeakCollectionDetails(JSWeakCollection::cast(obj));
}
if (obj->IsJSCollection()) {
RecordJSCollectionDetails(JSObject::cast(obj));
}
if (obj->IsJSFunction()) RecordJSFunctionDetails(JSFunction::cast(obj));
if (obj->IsScript()) RecordScriptDetails(Script::cast(obj));
}
class ObjectStatsCollector::CompilationCacheTableVisitor
: public ObjectVisitor {
public:
explicit CompilationCacheTableVisitor(ObjectStatsCollector* parent)
: parent_(parent) {}
void VisitPointers(Object** start, Object** end) override {
for (Object** current = start; current < end; current++) {
HeapObject* obj = HeapObject::cast(*current);
if (obj->IsUndefined(parent_->heap_->isolate())) continue;
CHECK(obj->IsCompilationCacheTable());
parent_->RecordHashTableHelper(nullptr, CompilationCacheTable::cast(obj),
COMPILATION_CACHE_TABLE_SUB_TYPE);
}
}
private:
ObjectStatsCollector* parent_;
};
void ObjectStatsCollector::CollectGlobalStatistics() {
// Global FixedArrays.
RecordFixedArrayHelper(nullptr, heap_->weak_new_space_object_to_code_list(),
WEAK_NEW_SPACE_OBJECT_TO_CODE_SUB_TYPE, 0);
RecordFixedArrayHelper(nullptr, heap_->serialized_templates(),
SERIALIZED_TEMPLATES_SUB_TYPE, 0);
RecordFixedArrayHelper(nullptr, heap_->number_string_cache(),
NUMBER_STRING_CACHE_SUB_TYPE, 0);
RecordFixedArrayHelper(nullptr, heap_->single_character_string_cache(),
SINGLE_CHARACTER_STRING_CACHE_SUB_TYPE, 0);
RecordFixedArrayHelper(nullptr, heap_->string_split_cache(),
STRING_SPLIT_CACHE_SUB_TYPE, 0);
RecordFixedArrayHelper(nullptr, heap_->regexp_multiple_cache(),
REGEXP_MULTIPLE_CACHE_SUB_TYPE, 0);
RecordFixedArrayHelper(nullptr, heap_->retained_maps(),
RETAINED_MAPS_SUB_TYPE, 0);
// Global weak FixedArrays.
RecordFixedArrayHelper(
nullptr, WeakFixedArray::cast(heap_->noscript_shared_function_infos()),
NOSCRIPT_SHARED_FUNCTION_INFOS_SUB_TYPE, 0);
RecordFixedArrayHelper(nullptr, WeakFixedArray::cast(heap_->script_list()),
SCRIPT_LIST_SUB_TYPE, 0);
// Global hash tables.
RecordHashTableHelper(nullptr, heap_->string_table(), STRING_TABLE_SUB_TYPE);
RecordHashTableHelper(nullptr, heap_->weak_object_to_code_table(),
OBJECT_TO_CODE_SUB_TYPE);
RecordHashTableHelper(nullptr, heap_->code_stubs(),
CODE_STUBS_TABLE_SUB_TYPE);
RecordHashTableHelper(nullptr, heap_->empty_properties_dictionary(),
EMPTY_PROPERTIES_DICTIONARY_SUB_TYPE);
CompilationCache* compilation_cache = heap_->isolate()->compilation_cache();
CompilationCacheTableVisitor v(this);
compilation_cache->Iterate(&v);
}
static bool CanRecordFixedArray(Heap* heap, FixedArrayBase* array) {
return array->map()->instance_type() == FIXED_ARRAY_TYPE &&
array->map() != heap->fixed_double_array_map() &&
array != heap->empty_fixed_array() &&
array != heap->empty_byte_array() &&
array != heap->empty_literals_array() &&
array != heap->empty_sloppy_arguments_elements() &&
array != heap->empty_slow_element_dictionary() &&
array != heap->empty_descriptor_array() &&
array != heap->empty_properties_dictionary();
}
static bool IsCowArray(Heap* heap, FixedArrayBase* array) {
return array->map() == heap->fixed_cow_array_map();
}
static bool SameLiveness(HeapObject* obj1, HeapObject* obj2) {
return obj1 == nullptr || obj2 == nullptr ||
ObjectMarking::Color(obj1) == ObjectMarking::Color(obj2);
}
bool ObjectStatsCollector::RecordFixedArrayHelper(HeapObject* parent,
FixedArray* array,
int subtype,
size_t overhead) {
if (SameLiveness(parent, array) && CanRecordFixedArray(heap_, array) &&
!IsCowArray(heap_, array)) {
return stats_->RecordFixedArraySubTypeStats(array, subtype, array->Size(),
overhead);
}
return false;
}
void ObjectStatsCollector::RecursivelyRecordFixedArrayHelper(HeapObject* parent,
FixedArray* array,
int subtype) {
if (RecordFixedArrayHelper(parent, array, subtype, 0)) {
for (int i = 0; i < array->length(); i++) {
if (array->get(i)->IsFixedArray()) {
RecursivelyRecordFixedArrayHelper(
parent, FixedArray::cast(array->get(i)), subtype);
}
}
}
}
template <class HashTable>
void ObjectStatsCollector::RecordHashTableHelper(HeapObject* parent,
HashTable* array,
int subtype) {
int used = array->NumberOfElements() * HashTable::kEntrySize * kPointerSize;
CHECK_GE(array->Size(), used);
size_t overhead = array->Size() - used -
HashTable::kElementsStartIndex * kPointerSize -
FixedArray::kHeaderSize;
RecordFixedArrayHelper(parent, array, subtype, overhead);
}
void ObjectStatsCollector::RecordJSObjectDetails(JSObject* object) {
size_t overhead = 0;
FixedArrayBase* elements = object->elements();
if (CanRecordFixedArray(heap_, elements) && !IsCowArray(heap_, elements)) {
if (elements->IsDictionary() && SameLiveness(object, elements)) {
SeededNumberDictionary* dict = SeededNumberDictionary::cast(elements);
RecordHashTableHelper(object, dict, DICTIONARY_ELEMENTS_SUB_TYPE);
} else {
if (IsFastHoleyElementsKind(object->GetElementsKind())) {
int used = object->GetFastElementsUsage() * kPointerSize;
if (object->GetElementsKind() == FAST_HOLEY_DOUBLE_ELEMENTS) used *= 2;
CHECK_GE(elements->Size(), used);
overhead = elements->Size() - used - FixedArray::kHeaderSize;
}
stats_->RecordFixedArraySubTypeStats(elements, FAST_ELEMENTS_SUB_TYPE,
elements->Size(), overhead);
}
}
overhead = 0;
FixedArrayBase* properties = object->properties();
if (CanRecordFixedArray(heap_, properties) &&
SameLiveness(object, properties) && !IsCowArray(heap_, properties)) {
if (properties->IsDictionary()) {
NameDictionary* dict = NameDictionary::cast(properties);
RecordHashTableHelper(object, dict, DICTIONARY_PROPERTIES_SUB_TYPE);
} else {
stats_->RecordFixedArraySubTypeStats(properties, FAST_PROPERTIES_SUB_TYPE,
properties->Size(), overhead);
}
}
}
void ObjectStatsCollector::RecordJSWeakCollectionDetails(
JSWeakCollection* obj) {
if (obj->table()->IsHashTable()) {
ObjectHashTable* table = ObjectHashTable::cast(obj->table());
int used = table->NumberOfElements() * ObjectHashTable::kEntrySize;
size_t overhead = table->Size() - used;
RecordFixedArrayHelper(obj, table, JS_WEAK_COLLECTION_SUB_TYPE, overhead);
}
}
void ObjectStatsCollector::RecordJSCollectionDetails(JSObject* obj) {
// The JS versions use a different HashTable implementation that cannot use
// the regular helper. Since overall impact is usually small just record
// without overhead.
if (obj->IsJSMap()) {
RecordFixedArrayHelper(nullptr, FixedArray::cast(JSMap::cast(obj)->table()),
JS_COLLECTION_SUB_TYPE, 0);
}
if (obj->IsJSSet()) {
RecordFixedArrayHelper(nullptr, FixedArray::cast(JSSet::cast(obj)->table()),
JS_COLLECTION_SUB_TYPE, 0);
}
}
void ObjectStatsCollector::RecordScriptDetails(Script* obj) {
Object* infos = WeakFixedArray::cast(obj->shared_function_infos());
if (infos->IsWeakFixedArray())
RecordFixedArrayHelper(obj, WeakFixedArray::cast(infos),
SHARED_FUNCTION_INFOS_SUB_TYPE, 0);
}
void ObjectStatsCollector::RecordMapDetails(Map* map_obj) {
DescriptorArray* array = map_obj->instance_descriptors();
if (map_obj->owns_descriptors() && array != heap_->empty_descriptor_array() &&
SameLiveness(map_obj, array)) {
RecordFixedArrayHelper(map_obj, array, DESCRIPTOR_ARRAY_SUB_TYPE, 0);
if (array->HasEnumCache()) {
RecordFixedArrayHelper(array, array->GetEnumCache(), ENUM_CACHE_SUB_TYPE,
0);
}
if (array->HasEnumIndicesCache()) {
RecordFixedArrayHelper(array, array->GetEnumIndicesCache(),
ENUM_INDICES_CACHE_SUB_TYPE, 0);
}
}
if (map_obj->has_code_cache()) {
FixedArray* code_cache = map_obj->code_cache();
if (code_cache->IsCodeCacheHashTable()) {
RecordHashTableHelper(map_obj, CodeCacheHashTable::cast(code_cache),
MAP_CODE_CACHE_SUB_TYPE);
} else {
RecordFixedArrayHelper(map_obj, code_cache, MAP_CODE_CACHE_SUB_TYPE, 0);
}
}
for (DependentCode* cur_dependent_code = map_obj->dependent_code();
cur_dependent_code != heap_->empty_fixed_array();
cur_dependent_code = DependentCode::cast(
cur_dependent_code->get(DependentCode::kNextLinkIndex))) {
RecordFixedArrayHelper(map_obj, cur_dependent_code, DEPENDENT_CODE_SUB_TYPE,
0);
}
if (map_obj->is_prototype_map()) {
if (map_obj->prototype_info()->IsPrototypeInfo()) {
PrototypeInfo* info = PrototypeInfo::cast(map_obj->prototype_info());
Object* users = info->prototype_users();
if (users->IsWeakFixedArray()) {
RecordFixedArrayHelper(map_obj, WeakFixedArray::cast(users),
PROTOTYPE_USERS_SUB_TYPE, 0);
}
}
}
}
void ObjectStatsCollector::RecordTemplateInfoDetails(TemplateInfo* obj) {
if (obj->property_accessors()->IsFixedArray()) {
RecordFixedArrayHelper(obj, FixedArray::cast(obj->property_accessors()),
TEMPLATE_INFO_SUB_TYPE, 0);
}
if (obj->property_list()->IsFixedArray()) {
RecordFixedArrayHelper(obj, FixedArray::cast(obj->property_list()),
TEMPLATE_INFO_SUB_TYPE, 0);
}
}
void ObjectStatsCollector::RecordBytecodeArrayDetails(BytecodeArray* obj) {
RecordFixedArrayHelper(obj, obj->constant_pool(),
BYTECODE_ARRAY_CONSTANT_POOL_SUB_TYPE, 0);
RecordFixedArrayHelper(obj, obj->handler_table(),
BYTECODE_ARRAY_HANDLER_TABLE_SUB_TYPE, 0);
}
void ObjectStatsCollector::RecordCodeDetails(Code* code) {
stats_->RecordCodeSubTypeStats(code->kind(), code->GetAge(), code->Size());
RecordFixedArrayHelper(code, code->deoptimization_data(),
DEOPTIMIZATION_DATA_SUB_TYPE, 0);
if (code->kind() == Code::Kind::OPTIMIZED_FUNCTION) {
DeoptimizationInputData* input_data =
DeoptimizationInputData::cast(code->deoptimization_data());
if (input_data->length() > 0) {
RecordFixedArrayHelper(code->deoptimization_data(),
input_data->LiteralArray(),
OPTIMIZED_CODE_LITERALS_SUB_TYPE, 0);
}
}
RecordFixedArrayHelper(code, code->handler_table(), HANDLER_TABLE_SUB_TYPE,
0);
int const mode_mask = RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT);
for (RelocIterator it(code, mode_mask); !it.done(); it.next()) {
RelocInfo::Mode mode = it.rinfo()->rmode();
if (mode == RelocInfo::EMBEDDED_OBJECT) {
Object* target = it.rinfo()->target_object();
if (target->IsFixedArray()) {
RecursivelyRecordFixedArrayHelper(code, FixedArray::cast(target),
EMBEDDED_OBJECT_SUB_TYPE);
}
}
}
}
void ObjectStatsCollector::RecordSharedFunctionInfoDetails(
SharedFunctionInfo* sfi) {
FixedArray* scope_info = sfi->scope_info();
RecordFixedArrayHelper(sfi, scope_info, SCOPE_INFO_SUB_TYPE, 0);
TypeFeedbackMetadata* feedback_metadata = sfi->feedback_metadata();
if (!feedback_metadata->is_empty()) {
RecordFixedArrayHelper(sfi, feedback_metadata,
TYPE_FEEDBACK_METADATA_SUB_TYPE, 0);
Object* names =
feedback_metadata->get(TypeFeedbackMetadata::kNamesTableIndex);
if (!names->IsSmi()) {
UnseededNumberDictionary* names = UnseededNumberDictionary::cast(
feedback_metadata->get(TypeFeedbackMetadata::kNamesTableIndex));
RecordHashTableHelper(sfi, names, TYPE_FEEDBACK_METADATA_SUB_TYPE);
}
}
if (!sfi->OptimizedCodeMapIsCleared()) {
FixedArray* optimized_code_map = sfi->optimized_code_map();
RecordFixedArrayHelper(sfi, optimized_code_map, OPTIMIZED_CODE_MAP_SUB_TYPE,
0);
// Optimized code map should be small, so skip accounting.
int len = optimized_code_map->length();
for (int i = SharedFunctionInfo::kEntriesStart; i < len;
i += SharedFunctionInfo::kEntryLength) {
Object* slot =
optimized_code_map->get(i + SharedFunctionInfo::kLiteralsOffset);
LiteralsArray* literals = nullptr;
if (slot->IsWeakCell()) {
WeakCell* cell = WeakCell::cast(slot);
if (!cell->cleared()) {
literals = LiteralsArray::cast(cell->value());
}
} else {
literals = LiteralsArray::cast(slot);
}
if (literals != nullptr) {
RecordFixedArrayHelper(sfi, literals, LITERALS_ARRAY_SUB_TYPE, 0);
RecordFixedArrayHelper(sfi, literals->feedback_vector(),
TYPE_FEEDBACK_VECTOR_SUB_TYPE, 0);
}
}
}
}
void ObjectStatsCollector::RecordJSFunctionDetails(JSFunction* function) {
LiteralsArray* literals = function->literals();
RecordFixedArrayHelper(function, literals, LITERALS_ARRAY_SUB_TYPE, 0);
RecordFixedArrayHelper(function, literals->feedback_vector(),
TYPE_FEEDBACK_VECTOR_SUB_TYPE, 0);
}
void ObjectStatsCollector::RecordFixedArrayDetails(FixedArray* array) {
if (array->IsContext()) {
RecordFixedArrayHelper(nullptr, array, CONTEXT_SUB_TYPE, 0);
}
if (IsCowArray(heap_, array) && CanRecordFixedArray(heap_, array)) {
stats_->RecordFixedArraySubTypeStats(array, COPY_ON_WRITE_SUB_TYPE,
array->Size(), 0);
}
if (array->IsNativeContext()) {
Context* native_ctx = Context::cast(array);
RecordHashTableHelper(array,
native_ctx->slow_template_instantiations_cache(),
SLOW_TEMPLATE_INSTANTIATIONS_CACHE_SUB_TYPE);
FixedArray* fast_cache = native_ctx->fast_template_instantiations_cache();
stats_->RecordFixedArraySubTypeStats(
fast_cache, FAST_TEMPLATE_INSTANTIATIONS_CACHE_SUB_TYPE,
fast_cache->Size(), 0);
}
}
} // namespace internal
} // namespace v8