Add initial version of retainers heap profile.

The profile is taken together with constructors profile. In theory, it
should represent a complete heap graph. However, this takes a lot of memory,
so it is reduced to a more compact, but still useful form. Namely:

 - objects are aggregated by their constructors, except for Array and Object
   instances, that are too hetereogeneous;

 - for Arrays and Objects, initially every instance is concerned, but then
   they are grouped together based on their retainer graph paths similarity (e.g.
   if two objects has the same retainer, they are considered equal);

Review URL: http://codereview.chromium.org/200132

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2903 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 40471b41
......@@ -42,14 +42,15 @@ SOURCES = {
'debug.cc', 'debug-agent.cc', 'disassembler.cc', 'execution.cc',
'factory.cc', 'flags.cc', 'frame-element.cc', 'frames.cc',
'func-name-inferrer.cc', 'global-handles.cc', 'handles.cc',
'hashmap.cc', 'heap.cc', 'ic.cc', 'interpreter-irregexp.cc',
'jsregexp.cc', 'jump-target.cc', 'log.cc', 'log-utils.cc',
'mark-compact.cc', 'messages.cc', 'objects.cc', 'oprofile-agent.cc',
'parser.cc', 'property.cc', 'regexp-macro-assembler.cc',
'regexp-macro-assembler-irregexp.cc', 'regexp-stack.cc',
'register-allocator.cc', 'rewriter.cc', 'runtime.cc', 'scanner.cc',
'scopeinfo.cc', 'scopes.cc', 'serialize.cc', 'snapshot-common.cc',
'spaces.cc', 'string-stream.cc', 'stub-cache.cc', 'token.cc', 'top.cc',
'hashmap.cc', 'heap.cc', 'heap-profiler.cc', 'ic.cc',
'interpreter-irregexp.cc', 'jsregexp.cc', 'jump-target.cc',
'log.cc', 'log-utils.cc', 'mark-compact.cc', 'messages.cc',
'objects.cc', 'oprofile-agent.cc', 'parser.cc', 'property.cc',
'regexp-macro-assembler.cc', 'regexp-macro-assembler-irregexp.cc',
'regexp-stack.cc', 'register-allocator.cc', 'rewriter.cc',
'runtime.cc', 'scanner.cc', 'scopeinfo.cc', 'scopes.cc',
'serialize.cc', 'snapshot-common.cc', 'spaces.cc',
'string-stream.cc', 'stub-cache.cc', 'token.cc', 'top.cc',
'unicode.cc', 'usage-analyzer.cc', 'utils.cc', 'v8-counters.cc',
'v8.cc', 'v8threads.cc', 'variables.cc', 'version.cc',
'virtual-frame.cc', 'zone.cc'
......
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "v8.h"
#include "heap-profiler.h"
#include "string-stream.h"
namespace v8 {
namespace internal {
#ifdef ENABLE_LOGGING_AND_PROFILING
namespace {
// JSStatsHelper provides service functions for examining
// JS objects allocated on heap. It is run during garbage
// collection cycle, thus it doesn't need to use handles.
class JSStatsHelper {
public:
static int CalculateNetworkSize(JSObject* obj);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(JSStatsHelper);
};
int JSStatsHelper::CalculateNetworkSize(JSObject* obj) {
int size = obj->Size();
// If 'properties' and 'elements' are non-empty (thus, non-shared),
// take their size into account.
if (FixedArray::cast(obj->properties())->length() != 0) {
size += obj->properties()->Size();
}
if (FixedArray::cast(obj->elements())->length() != 0) {
size += obj->elements()->Size();
}
return size;
}
// A helper class for recording back references.
class ReferencesExtractor : public ObjectVisitor {
public:
ReferencesExtractor(
const JSObjectsCluster& cluster, RetainerHeapProfile* profile)
: cluster_(cluster),
profile_(profile),
insideArray_(false) {
}
void VisitPointer(Object** o) {
if ((*o)->IsJSObject() || (*o)->IsString()) {
profile_->StoreReference(cluster_, *o);
} else if ((*o)->IsFixedArray() && !insideArray_) {
// Traverse one level deep for data members that are fixed arrays.
// This covers the case of 'elements' and 'properties' of JSObject,
// and function contexts.
insideArray_ = true;
FixedArray::cast(*o)->Iterate(this);
insideArray_ = false;
}
}
void VisitPointers(Object** start, Object** end) {
for (Object** p = start; p < end; p++) VisitPointer(p);
}
private:
const JSObjectsCluster& cluster_;
RetainerHeapProfile* profile_;
bool insideArray_;
};
// A printer interface implementation for the Retainers profile.
class RetainersPrinter : public RetainerHeapProfile::Printer {
public:
void PrintRetainers(const StringStream& retainers) {
LOG(HeapSampleJSRetainersEvent(*(retainers.ToCString())));
}
};
} // namespace
const ConstructorHeapProfile::TreeConfig::Key
ConstructorHeapProfile::TreeConfig::kNoKey = NULL;
const ConstructorHeapProfile::TreeConfig::Value
ConstructorHeapProfile::TreeConfig::kNoValue;
ConstructorHeapProfile::ConstructorHeapProfile()
: zscope_(DELETE_ON_EXIT) {
}
void ConstructorHeapProfile::Call(String* name,
const NumberAndSizeInfo& number_and_size) {
ASSERT(name != NULL);
SmartPointer<char> s_name(
name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
LOG(HeapSampleJSConstructorEvent(*s_name,
number_and_size.number(),
number_and_size.bytes()));
}
void ConstructorHeapProfile::CollectStats(HeapObject* obj) {
String* constructor = NULL;
int size;
if (obj->IsString()) {
constructor = Heap::String_symbol();
size = obj->Size();
} else if (obj->IsJSObject()) {
JSObject* js_obj = JSObject::cast(obj);
constructor = js_obj->constructor_name();
size = JSStatsHelper::CalculateNetworkSize(js_obj);
} else {
return;
}
JSObjectsInfoTree::Locator loc;
if (!js_objects_info_tree_.Find(constructor, &loc)) {
js_objects_info_tree_.Insert(constructor, &loc);
}
NumberAndSizeInfo number_and_size = loc.value();
number_and_size.increment_number(1);
number_and_size.increment_bytes(size);
loc.set_value(number_and_size);
}
void ConstructorHeapProfile::PrintStats() {
js_objects_info_tree_.ForEach(this);
}
void JSObjectsCluster::Print(StringStream* accumulator) const {
ASSERT(!is_null());
if (constructor_ == FromSpecialCase(ROOTS)) {
accumulator->Add("(roots)");
} else if (constructor_ == FromSpecialCase(GLOBAL_PROPERTY)) {
accumulator->Add("(global property)");
} else {
SmartPointer<char> s_name(
constructor_->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
accumulator->Add("%s", (*s_name)[0] != '\0' ? *s_name : "(anonymous)");
if (instance_ != NULL) {
accumulator->Add(":%p", static_cast<void*>(instance_));
}
}
}
void JSObjectsCluster::DebugPrint(StringStream* accumulator) const {
if (!is_null()) {
Print(accumulator);
} else {
accumulator->Add("(null cluster)");
}
}
inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
const JSObjectsCluster& cluster_)
: cluster(cluster_), refs(INITIAL_BACKREFS_LIST_CAPACITY) {
}
inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs(
const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& src)
: cluster(src.cluster), refs(src.refs.capacity()) {
refs.AddAll(src.refs);
}
inline ClustersCoarser::ClusterBackRefs::ClusterBackRefs&
ClustersCoarser::ClusterBackRefs::operator=(
const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& src) {
if (this == &src) return *this;
cluster = src.cluster;
refs.Clear();
refs.AddAll(src.refs);
return *this;
}
inline int ClustersCoarser::ClusterBackRefs::Compare(
const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& a,
const ClustersCoarser::ClusterBackRefs::ClusterBackRefs& b) {
int cmp = JSObjectsCluster::CompareConstructors(a.cluster, b.cluster);
if (cmp != 0) return cmp;
if (a.refs.length() < b.refs.length()) return -1;
if (a.refs.length() > b.refs.length()) return 1;
for (int i = 0; i < a.refs.length(); ++i) {
int cmp = JSObjectsCluster::Compare(a.refs[i], b.refs[i]);
if (cmp != 0) return cmp;
}
return 0;
}
ClustersCoarser::ClustersCoarser()
: zscope_(DELETE_ON_EXIT),
simList_(ClustersCoarser::INITIAL_SIMILARITY_LIST_CAPACITY),
currentPair_(NULL) {
}
void ClustersCoarser::Call(
const JSObjectsCluster& cluster, JSObjectsClusterTree* tree) {
if (tree != NULL) {
// First level of retainer graph.
if (!cluster.can_be_coarsed()) return;
ClusterBackRefs pair(cluster);
ASSERT(currentPair_ == NULL);
currentPair_ = &pair;
currentSet_ = new JSObjectsClusterTree();
tree->ForEach(this);
simList_.Add(pair);
currentPair_ = NULL;
currentSet_ = NULL;
} else {
// Second level of retainer graph.
ASSERT(currentPair_ != NULL);
ASSERT(currentSet_ != NULL);
JSObjectsCluster eq = GetCoarseEquivalent(cluster);
JSObjectsClusterTree::Locator loc;
if (!eq.is_null()) {
if (currentSet_->Find(eq, &loc)) return;
currentPair_->refs.Add(eq);
currentSet_->Insert(eq, &loc);
} else {
currentPair_->refs.Add(cluster);
}
}
}
void ClustersCoarser::Process(JSObjectsClusterTree* tree) {
int last_eq_clusters = -1;
for (int i = 0; i < MAX_PASSES_COUNT; ++i) {
simList_.Clear();
const int curr_eq_clusters = DoProcess(tree);
// If no new cluster equivalents discovered, abort processing.
if (last_eq_clusters == curr_eq_clusters) break;
last_eq_clusters = curr_eq_clusters;
}
}
int ClustersCoarser::DoProcess(JSObjectsClusterTree* tree) {
tree->ForEach(this);
// To sort similarity list properly, references list of a cluster is
// required to be sorted, thus 'O1 <- A, B' and 'O2 <- B, A' would
// be considered equivalent. But we don't sort them explicitly
// because we know that they come from a splay tree traversal, so
// they are already sorted.
simList_.Sort(ClusterBackRefsCmp);
return FillEqualityTree();
}
JSObjectsCluster ClustersCoarser::GetCoarseEquivalent(
const JSObjectsCluster& cluster) {
if (!cluster.can_be_coarsed()) return JSObjectsCluster();
EqualityTree::Locator loc;
return eqTree_.Find(cluster, &loc) ? loc.value() : JSObjectsCluster();
}
bool ClustersCoarser::HasAnEquivalent(const JSObjectsCluster& cluster) {
// Return true for coarsible clusters that have a non-identical equivalent.
return cluster.can_be_coarsed() &&
JSObjectsCluster::Compare(cluster, GetCoarseEquivalent(cluster)) != 0;
}
int ClustersCoarser::FillEqualityTree() {
int eqClustersCount = 0;
int eqTo = 0;
bool firstAdded = false;
for (int i = 1; i < simList_.length(); ++i) {
if (ClusterBackRefs::Compare(simList_[i], simList_[eqTo]) == 0) {
EqualityTree::Locator loc;
if (!firstAdded) {
// Add self-equivalence, if we have more than one item in this
// equivalence class.
eqTree_.Insert(simList_[eqTo].cluster, &loc);
loc.set_value(simList_[eqTo].cluster);
firstAdded = true;
}
eqTree_.Insert(simList_[i].cluster, &loc);
loc.set_value(simList_[eqTo].cluster);
++eqClustersCount;
} else {
eqTo = i;
firstAdded = false;
}
}
return eqClustersCount;
}
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoKey;
const JSObjectsCluster ClustersCoarser::ClusterEqualityConfig::kNoValue;
const JSObjectsClusterTreeConfig::Key JSObjectsClusterTreeConfig::kNoKey;
const JSObjectsClusterTreeConfig::Value JSObjectsClusterTreeConfig::kNoValue =
NULL;
RetainerHeapProfile::RetainerHeapProfile()
: zscope_(DELETE_ON_EXIT),
coarse_cluster_tree_(NULL),
retainers_printed_(0),
current_printer_(NULL),
current_stream_(NULL) {
ReferencesExtractor extractor(
JSObjectsCluster(JSObjectsCluster::ROOTS), this);
Heap::IterateRoots(&extractor);
}
JSObjectsCluster RetainerHeapProfile::Clusterize(Object* obj) {
if (obj->IsJSObject()) {
String* constructor = JSObject::cast(obj)->constructor_name();
// Differentiate Object and Array instances.
if (constructor == Heap::Object_symbol() ||
constructor == Heap::Array_symbol()) {
return JSObjectsCluster(constructor, obj);
} else {
return JSObjectsCluster(constructor);
}
} else if (obj->IsString()) {
return JSObjectsCluster(Heap::String_symbol());
} else {
UNREACHABLE();
return JSObjectsCluster();
}
}
void RetainerHeapProfile::StoreReference(
const JSObjectsCluster& cluster,
Object* ref) {
JSObjectsCluster ref_cluster = Clusterize(ref);
JSObjectsClusterTree::Locator ref_loc;
if (retainers_tree_.Insert(ref_cluster, &ref_loc)) {
ref_loc.set_value(new JSObjectsClusterTree());
}
JSObjectsClusterTree* referencedBy = ref_loc.value();
JSObjectsClusterTree::Locator obj_loc;
referencedBy->Insert(cluster, &obj_loc);
}
void RetainerHeapProfile::CollectStats(HeapObject* obj) {
if (obj->IsJSObject()) {
const JSObjectsCluster cluster = Clusterize(JSObject::cast(obj));
ReferencesExtractor extractor(cluster, this);
obj->Iterate(&extractor);
} else if (obj->IsJSGlobalPropertyCell()) {
ReferencesExtractor extractor(
JSObjectsCluster(JSObjectsCluster::GLOBAL_PROPERTY), this);
obj->Iterate(&extractor);
}
}
void RetainerHeapProfile::DebugPrintStats(
RetainerHeapProfile::Printer* printer) {
coarser_.Process(&retainers_tree_);
ASSERT(current_printer_ == NULL);
current_printer_ = printer;
retainers_tree_.ForEach(this);
current_printer_ = NULL;
}
void RetainerHeapProfile::PrintStats() {
RetainersPrinter printer;
DebugPrintStats(&printer);
}
void RetainerHeapProfile::Call(
const JSObjectsCluster& cluster,
JSObjectsClusterTree* tree) {
ASSERT(current_printer_ != NULL);
if (tree != NULL) {
// First level of retainer graph.
if (coarser_.HasAnEquivalent(cluster)) return;
ASSERT(current_stream_ == NULL);
HeapStringAllocator allocator;
StringStream stream(&allocator);
current_stream_ = &stream;
cluster.Print(current_stream_);
ASSERT(coarse_cluster_tree_ == NULL);
coarse_cluster_tree_ = new JSObjectsClusterTree();
retainers_printed_ = 0;
tree->ForEach(this);
coarse_cluster_tree_ = NULL;
current_printer_->PrintRetainers(stream);
current_stream_ = NULL;
} else {
// Second level of retainer graph.
ASSERT(coarse_cluster_tree_ != NULL);
ASSERT(current_stream_ != NULL);
if (retainers_printed_ >= MAX_RETAINERS_TO_PRINT) {
if (retainers_printed_ == MAX_RETAINERS_TO_PRINT) {
// TODO(mnaganov): Print the exact count.
current_stream_->Add(",...");
++retainers_printed_; // avoid printing ellipsis next time.
}
return;
}
JSObjectsCluster eq = coarser_.GetCoarseEquivalent(cluster);
if (eq.is_null()) {
current_stream_->Put(',');
cluster.Print(current_stream_);
++retainers_printed_;
} else {
JSObjectsClusterTree::Locator loc;
if (coarse_cluster_tree_->Insert(eq, &loc)) {
current_stream_->Put(',');
eq.Print(current_stream_);
++retainers_printed_;
}
}
}
}
//
// HeapProfiler class implementation.
//
void HeapProfiler::CollectStats(HeapObject* obj, HistogramInfo* info) {
InstanceType type = obj->map()->instance_type();
ASSERT(0 <= type && type <= LAST_TYPE);
info[type].increment_number(1);
info[type].increment_bytes(obj->Size());
}
void HeapProfiler::WriteSample() {
LOG(HeapSampleBeginEvent("Heap", "allocated"));
LOG(HeapSampleStats(
"Heap", "allocated", Heap::Capacity(), Heap::SizeOfObjects()));
HistogramInfo info[LAST_TYPE+1];
#define DEF_TYPE_NAME(name) info[name].set_name(#name);
INSTANCE_TYPE_LIST(DEF_TYPE_NAME)
#undef DEF_TYPE_NAME
ConstructorHeapProfile js_cons_profile;
RetainerHeapProfile js_retainer_profile;
HeapIterator iterator;
while (iterator.has_next()) {
HeapObject* obj = iterator.next();
CollectStats(obj, info);
js_cons_profile.CollectStats(obj);
js_retainer_profile.CollectStats(obj);
}
// Lump all the string types together.
int string_number = 0;
int string_bytes = 0;
#define INCREMENT_SIZE(type, size, name, camel_name) \
string_number += info[type].number(); \
string_bytes += info[type].bytes();
STRING_TYPE_LIST(INCREMENT_SIZE)
#undef INCREMENT_SIZE
if (string_bytes > 0) {
LOG(HeapSampleItemEvent("STRING_TYPE", string_number, string_bytes));
}
for (int i = FIRST_NONSTRING_TYPE; i <= LAST_TYPE; ++i) {
if (info[i].bytes() > 0) {
LOG(HeapSampleItemEvent(info[i].name(), info[i].number(),
info[i].bytes()));
}
}
js_cons_profile.PrintStats();
js_retainer_profile.PrintStats();
LOG(HeapSampleEndEvent("Heap", "allocated"));
}
#endif // ENABLE_LOGGING_AND_PROFILING
} } // namespace v8::internal
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef V8_HEAP_PROFILER_H_
#define V8_HEAP_PROFILER_H_
namespace v8 {
namespace internal {
#ifdef ENABLE_LOGGING_AND_PROFILING
// The HeapProfiler writes data to the log files, which can be postprocessed
// to generate .hp files for use by the GHC/Valgrind tool hp2ps.
class HeapProfiler {
public:
// Write a single heap sample to the log file.
static void WriteSample();
private:
// Update the array info with stats from obj.
static void CollectStats(HeapObject* obj, HistogramInfo* info);
};
// ConstructorHeapProfile is responsible for gathering and logging
// "constructor profile" of JS objects allocated on heap.
// It is run during garbage collection cycle, thus it doesn't need
// to use handles.
class ConstructorHeapProfile BASE_EMBEDDED {
public:
ConstructorHeapProfile();
virtual ~ConstructorHeapProfile() {}
void CollectStats(HeapObject* obj);
void PrintStats();
// Used by ZoneSplayTree::ForEach. Made virtual to allow overriding in tests.
virtual void Call(String* name, const NumberAndSizeInfo& number_and_size);
private:
struct TreeConfig {
typedef String* Key;
typedef NumberAndSizeInfo Value;
static const Key kNoKey;
static const Value kNoValue;
static int Compare(const Key& a, const Key& b) {
// Strings are unique, so it is sufficient to compare their pointers.
return a == b ? 0 : (a < b ? -1 : 1);
}
};
typedef ZoneSplayTree<TreeConfig> JSObjectsInfoTree;
ZoneScope zscope_;
JSObjectsInfoTree js_objects_info_tree_;
};
// JSObjectsCluster describes a group of JS objects that are
// considered equivalent in terms of retainer profile.
class JSObjectsCluster BASE_EMBEDDED {
public:
enum SpecialCase {
ROOTS = 1,
GLOBAL_PROPERTY = 2
};
JSObjectsCluster() : constructor_(NULL), instance_(NULL) {}
explicit JSObjectsCluster(String* constructor)
: constructor_(constructor), instance_(NULL) {}
explicit JSObjectsCluster(SpecialCase special)
: constructor_(FromSpecialCase(special)), instance_(NULL) {}
JSObjectsCluster(String* constructor, Object* instance)
: constructor_(constructor), instance_(instance) {}
static int CompareConstructors(
const JSObjectsCluster& a, const JSObjectsCluster& b) {
// Strings are unique, so it is sufficient to compare their pointers.
return a.constructor_ == b.constructor_ ? 0
: (a.constructor_ < b.constructor_ ? -1 : 1);
}
static int Compare(const JSObjectsCluster& a, const JSObjectsCluster& b) {
// Strings are unique, so it is sufficient to compare their pointers.
const int cons_cmp = CompareConstructors(a, b);
return cons_cmp == 0 ?
(a.instance_ == b.instance_ ? 0 : (a.instance_ < b.instance_ ? -1 : 1))
: cons_cmp;
}
bool is_null() const { return constructor_ == NULL; }
bool can_be_coarsed() const { return instance_ != NULL; }
void Print(StringStream* accumulator) const;
// Allows null clusters to be printed.
void DebugPrint(StringStream* accumulator) const;
private:
static String* FromSpecialCase(SpecialCase special) {
// We use symbols that are illegal JS identifiers to identify special cases.
// Their actual value is irrelevant for us.
switch (special) {
case ROOTS: return Heap::result_symbol();
case GLOBAL_PROPERTY: return Heap::code_symbol();
default:
UNREACHABLE();
return NULL;
}
}
String* constructor_;
Object* instance_;
};
struct JSObjectsClusterTreeConfig;
typedef ZoneSplayTree<JSObjectsClusterTreeConfig> JSObjectsClusterTree;
// JSObjectsClusterTree is used to represent retainer graphs using
// adjacency list form. That is, the first level maps JS object
// clusters to adjacency lists, which in their turn are degenerate
// JSObjectsClusterTrees (their values are NULLs.)
struct JSObjectsClusterTreeConfig {
typedef JSObjectsCluster Key;
typedef JSObjectsClusterTree* Value;
static const Key kNoKey;
static const Value kNoValue;
static int Compare(const Key& a, const Key& b) {
return Key::Compare(a, b);
}
};
class ClustersCoarser BASE_EMBEDDED {
public:
ClustersCoarser();
// Processes a given retainer graph.
void Process(JSObjectsClusterTree* tree);
// Returns an equivalent cluster (can be the cluster itself).
// If the given cluster doesn't have an equivalent, returns null cluster.
JSObjectsCluster GetCoarseEquivalent(const JSObjectsCluster& cluster);
// Returns whether a cluster can be substitued with an equivalent and thus,
// skipped in some cases.
bool HasAnEquivalent(const JSObjectsCluster& cluster);
// Used by ZoneSplayTree::ForEach.
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
private:
// Stores a list of back references for a cluster.
struct ClusterBackRefs {
explicit ClusterBackRefs(const JSObjectsCluster& cluster_);
ClusterBackRefs(const ClusterBackRefs& src);
ClusterBackRefs& operator=(const ClusterBackRefs& src);
static int Compare(const ClusterBackRefs& a, const ClusterBackRefs& b);
JSObjectsCluster cluster;
ZoneList<JSObjectsCluster> refs;
};
typedef ZoneList<ClusterBackRefs> SimilarityList;
// A tree for storing a list of equivalents for a cluster.
struct ClusterEqualityConfig {
typedef JSObjectsCluster Key;
typedef JSObjectsCluster Value;
static const Key kNoKey;
static const Value kNoValue;
static int Compare(const Key& a, const Key& b) {
return Key::Compare(a, b);
}
};
typedef ZoneSplayTree<ClusterEqualityConfig> EqualityTree;
static int ClusterBackRefsCmp(
const ClusterBackRefs* a, const ClusterBackRefs* b) {
return ClusterBackRefs::Compare(*a, *b);
}
int DoProcess(JSObjectsClusterTree* tree);
int FillEqualityTree();
static const int INITIAL_BACKREFS_LIST_CAPACITY = 2;
static const int INITIAL_SIMILARITY_LIST_CAPACITY = 2000;
// Number of passes for finding equivalents. Limits the length of paths
// that can be considered equivalent.
static const int MAX_PASSES_COUNT = 10;
ZoneScope zscope_;
SimilarityList simList_;
EqualityTree eqTree_;
ClusterBackRefs* currentPair_;
JSObjectsClusterTree* currentSet_;
};
// RetainerHeapProfile is responsible for gathering and logging
// "retainer profile" of JS objects allocated on heap.
// It is run during garbage collection cycle, thus it doesn't need
// to use handles.
class RetainerHeapProfile BASE_EMBEDDED {
public:
class Printer {
public:
virtual ~Printer() {}
virtual void PrintRetainers(const StringStream& retainers) = 0;
};
RetainerHeapProfile();
void CollectStats(HeapObject* obj);
void PrintStats();
void DebugPrintStats(Printer* printer);
void StoreReference(const JSObjectsCluster& cluster, Object* ref);
private:
JSObjectsCluster Clusterize(Object* obj);
// Limit on the number of retainers to be printed per cluster.
static const int MAX_RETAINERS_TO_PRINT = 50;
ZoneScope zscope_;
JSObjectsClusterTree retainers_tree_;
ClustersCoarser coarser_;
// TODO(mnaganov): Use some helper class to hold these state variables.
JSObjectsClusterTree* coarse_cluster_tree_;
int retainers_printed_;
Printer* current_printer_;
StringStream* current_stream_;
public:
// Used by JSObjectsClusterTree::ForEach.
void Call(const JSObjectsCluster& cluster, JSObjectsClusterTree* tree);
};
#endif // ENABLE_LOGGING_AND_PROFILING
} } // namespace v8::internal
#endif // V8_HEAP_PROFILER_H_
......@@ -33,6 +33,7 @@
#include "codegen-inl.h"
#include "compilation-cache.h"
#include "debug.h"
#include "heap-profiler.h"
#include "global-handles.h"
#include "mark-compact.h"
#include "natives.h"
......@@ -3544,164 +3545,6 @@ void HeapIterator::reset() {
}
#ifdef ENABLE_LOGGING_AND_PROFILING
namespace {
// JSConstructorProfile is responsible for gathering and logging
// "constructor profile" of JS object allocated on heap.
// It is run during garbage collection cycle, thus it doesn't need
// to use handles.
class JSConstructorProfile BASE_EMBEDDED {
public:
JSConstructorProfile() : zscope_(DELETE_ON_EXIT) {}
void CollectStats(HeapObject* obj);
void PrintStats();
// Used by ZoneSplayTree::ForEach.
void Call(String* name, const NumberAndSizeInfo& number_and_size);
private:
struct TreeConfig {
typedef String* Key;
typedef NumberAndSizeInfo Value;
static const Key kNoKey;
static const Value kNoValue;
// Strings are unique, so it is sufficient to compare their pointers.
static int Compare(const Key& a, const Key& b) {
return a == b ? 0 : (a < b ? -1 : 1);
}
};
typedef ZoneSplayTree<TreeConfig> JSObjectsInfoTree;
static int CalculateJSObjectNetworkSize(JSObject* obj);
ZoneScope zscope_;
JSObjectsInfoTree js_objects_info_tree_;
};
const JSConstructorProfile::TreeConfig::Key
JSConstructorProfile::TreeConfig::kNoKey = NULL;
const JSConstructorProfile::TreeConfig::Value
JSConstructorProfile::TreeConfig::kNoValue;
int JSConstructorProfile::CalculateJSObjectNetworkSize(JSObject* obj) {
int size = obj->Size();
// If 'properties' and 'elements' are non-empty (thus, non-shared),
// take their size into account.
if (FixedArray::cast(obj->properties())->length() != 0) {
size += obj->properties()->Size();
}
if (FixedArray::cast(obj->elements())->length() != 0) {
size += obj->elements()->Size();
}
return size;
}
void JSConstructorProfile::Call(String* name,
const NumberAndSizeInfo& number_and_size) {
ASSERT(name != NULL);
SmartPointer<char> s_name(
name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL));
LOG(HeapSampleJSConstructorEvent(*s_name,
number_and_size.number(),
number_and_size.bytes()));
}
void JSConstructorProfile::CollectStats(HeapObject* obj) {
String* constructor = NULL;
int size;
if (obj->IsString()) {
constructor = Heap::String_symbol();
size = obj->Size();
} else if (obj->IsJSObject()) {
JSObject* js_obj = JSObject::cast(obj);
constructor = js_obj->constructor_name();
size = CalculateJSObjectNetworkSize(js_obj);
} else {
return;
}
JSObjectsInfoTree::Locator loc;
if (!js_objects_info_tree_.Find(constructor, &loc)) {
js_objects_info_tree_.Insert(constructor, &loc);
}
NumberAndSizeInfo number_and_size = loc.value();
number_and_size.increment_number(1);
number_and_size.increment_bytes(size);
loc.set_value(number_and_size);
}
void JSConstructorProfile::PrintStats() {
js_objects_info_tree_.ForEach(this);
}
} // namespace
#endif
//
// HeapProfiler class implementation.
//
#ifdef ENABLE_LOGGING_AND_PROFILING
void HeapProfiler::CollectStats(HeapObject* obj, HistogramInfo* info) {
InstanceType type = obj->map()->instance_type();
ASSERT(0 <= type && type <= LAST_TYPE);
info[type].increment_number(1);
info[type].increment_bytes(obj->Size());
}
#endif
#ifdef ENABLE_LOGGING_AND_PROFILING
void HeapProfiler::WriteSample() {
LOG(HeapSampleBeginEvent("Heap", "allocated"));
LOG(HeapSampleStats(
"Heap", "allocated", Heap::Capacity(), Heap::SizeOfObjects()));
HistogramInfo info[LAST_TYPE+1];
#define DEF_TYPE_NAME(name) info[name].set_name(#name);
INSTANCE_TYPE_LIST(DEF_TYPE_NAME)
#undef DEF_TYPE_NAME
JSConstructorProfile js_cons_profile;
HeapIterator iterator;
while (iterator.has_next()) {
HeapObject* obj = iterator.next();
CollectStats(obj, info);
js_cons_profile.CollectStats(obj);
}
// Lump all the string types together.
int string_number = 0;
int string_bytes = 0;
#define INCREMENT_SIZE(type, size, name, camel_name) \
string_number += info[type].number(); \
string_bytes += info[type].bytes();
STRING_TYPE_LIST(INCREMENT_SIZE)
#undef INCREMENT_SIZE
if (string_bytes > 0) {
LOG(HeapSampleItemEvent("STRING_TYPE", string_number, string_bytes));
}
for (int i = FIRST_NONSTRING_TYPE; i <= LAST_TYPE; ++i) {
if (info[i].bytes() > 0) {
LOG(HeapSampleItemEvent(info[i].name(), info[i].number(),
info[i].bytes()));
}
}
js_cons_profile.PrintStats();
LOG(HeapSampleEndEvent("Heap", "allocated"));
}
#endif
#ifdef DEBUG
static bool search_for_any_global;
......
......@@ -1443,20 +1443,6 @@ class DisableAssertNoAllocation {
#endif
#ifdef ENABLE_LOGGING_AND_PROFILING
// The HeapProfiler writes data to the log files, which can be postprocessed
// to generate .hp files for use by the GHC/Valgrind tool hp2ps.
class HeapProfiler {
public:
// Write a single heap sample to the log file.
static void WriteSample();
private:
// Update the array info with stats from obj.
static void CollectStats(HeapObject* obj, HistogramInfo* info);
};
#endif
// GCTracer collects and prints ONE line after each garbage collector
// invocation IFF --trace_gc is used.
......
......@@ -897,6 +897,16 @@ void Logger::HeapSampleJSConstructorEvent(const char* constructor,
}
void Logger::HeapSampleJSRetainersEvent(const char* event) {
#ifdef ENABLE_LOGGING_AND_PROFILING
if (!Log::IsEnabled() || !FLAG_log_gc) return;
LogMessageBuilder msg;
msg.Append("heap-js-ret-item,%s\n", event);
msg.WriteToLogFile();
#endif
}
void Logger::DebugTag(const char* call_site_tag) {
#ifdef ENABLE_LOGGING_AND_PROFILING
if (!Log::IsEnabled() || !FLAG_log) return;
......
......@@ -221,6 +221,7 @@ class Logger {
static void HeapSampleItemEvent(const char* type, int number, int bytes);
static void HeapSampleJSConstructorEvent(const char* constructor,
int number, int bytes);
static void HeapSampleJSRetainersEvent(const char* event);
static void HeapSampleStats(const char* space, const char* kind,
int capacity, int used);
......
......@@ -251,7 +251,7 @@ void StringStream::Add(const char* format, FmtElm arg0, FmtElm arg1,
}
SmartPointer<const char> StringStream::ToCString() {
SmartPointer<const char> StringStream::ToCString() const {
char* str = NewArray<char>(length_ + 1);
memcpy(str, buffer_, length_);
str[length_] = '\0';
......
......@@ -141,7 +141,7 @@ class StringStream {
void OutputToStdOut();
void Log();
Handle<String> ToString();
SmartPointer<const char> ToCString();
SmartPointer<const char> ToCString() const;
// Object printing support.
void PrintName(Object* o);
......
......@@ -45,6 +45,7 @@ SOURCES = {
'test-func-name-inference.cc',
'test-hashmap.cc',
'test-heap.cc',
'test-heap-profiler.cc',
'test-list.cc',
'test-lock.cc',
'test-log.cc',
......
// Copyright 2009 the V8 project authors. All rights reserved.
//
// Tests for heap profiler
#ifdef ENABLE_LOGGING_AND_PROFILING
#include "v8.h"
#include "heap-profiler.h"
#include "string-stream.h"
#include "cctest.h"
namespace i = v8::internal;
using i::ClustersCoarser;
using i::JSObjectsCluster;
using i::JSObjectsClusterTree;
using i::RetainerHeapProfile;
static void CompileAndRunScript(const char *src) {
v8::Script::Compile(v8::String::New(src))->Run();
}
namespace {
class ConstructorHeapProfileTestHelper : public i::ConstructorHeapProfile {
public:
ConstructorHeapProfileTestHelper()
: i::ConstructorHeapProfile(),
f_name_(i::Factory::NewStringFromAscii(i::CStrVector("F"))),
f_count_(0) {
}
void Call(i::String* name, const i::NumberAndSizeInfo& number_and_size) {
CHECK(name != NULL);
if (f_name_->Equals(name)) {
CHECK_EQ(f_count_, 0);
f_count_ = number_and_size.number();
CHECK_GT(f_count_, 0);
}
}
int f_count() { return f_count_; }
private:
i::Handle<i::String> f_name_;
int f_count_;
};
} // namespace
TEST(ConstructorProfile) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
CompileAndRunScript(
"function F() {} // A constructor\n"
"var f1 = new F();\n"
"var f2 = new F();\n");
ConstructorHeapProfileTestHelper cons_profile;
i::AssertNoAllocation no_alloc;
i::HeapIterator iterator;
while (iterator.has_next()) {
i::HeapObject* obj = iterator.next();
cons_profile.CollectStats(obj);
}
CHECK_EQ(0, cons_profile.f_count());
cons_profile.PrintStats();
CHECK_EQ(2, cons_profile.f_count());
}
static JSObjectsCluster AddHeapObjectToTree(
JSObjectsClusterTree* tree,
i::String* constructor,
int instance,
JSObjectsCluster* ref1 = NULL,
JSObjectsCluster* ref2 = NULL,
JSObjectsCluster* ref3 = NULL) {
JSObjectsCluster o(constructor, reinterpret_cast<i::Object*>(instance));
JSObjectsClusterTree* o_tree = new JSObjectsClusterTree();
JSObjectsClusterTree::Locator loc;
if (ref1 != NULL) o_tree->Insert(*ref1, &loc);
if (ref2 != NULL) o_tree->Insert(*ref2, &loc);
if (ref3 != NULL) o_tree->Insert(*ref3, &loc);
tree->Insert(o, &loc);
loc.set_value(o_tree);
return o;
}
static inline void CheckEqualsHelper(const char* file, int line,
const char* expected_source,
const JSObjectsCluster& expected,
const char* value_source,
const JSObjectsCluster& value) {
if (JSObjectsCluster::Compare(expected, value) != 0) {
i::HeapStringAllocator allocator;
i::StringStream stream(&allocator);
stream.Add("# Expected: ");
expected.DebugPrint(&stream);
stream.Add("\n# Found: ");
value.DebugPrint(&stream);
V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n%s",
expected_source, value_source,
*stream.ToCString());
}
}
static inline void CheckNonEqualsHelper(const char* file, int line,
const char* expected_source,
const JSObjectsCluster& expected,
const char* value_source,
const JSObjectsCluster& value) {
if (JSObjectsCluster::Compare(expected, value) == 0) {
i::HeapStringAllocator allocator;
i::StringStream stream(&allocator);
stream.Add("# Expected: ");
expected.DebugPrint(&stream);
stream.Add("\n# Found: ");
value.DebugPrint(&stream);
V8_Fatal(file, line, "CHECK_NE(%s, %s) failed\n%s",
expected_source, value_source,
*stream.ToCString());
}
}
TEST(ClustersCoarserSimple) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
JSObjectsClusterTree tree;
JSObjectsCluster function(i::Heap::function_class_symbol());
JSObjectsCluster a(*i::Factory::NewStringFromAscii(i::CStrVector("A")));
JSObjectsCluster b(*i::Factory::NewStringFromAscii(i::CStrVector("B")));
// o1 <- Function
JSObjectsCluster o1 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100, &function);
// o2 <- Function
JSObjectsCluster o2 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x200, &function);
// o3 <- A, B
JSObjectsCluster o3 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &a, &b);
// o4 <- B, A
JSObjectsCluster o4 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x400, &b, &a);
// o5 <- A, B, Function
JSObjectsCluster o5 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x500,
&a, &b, &function);
ClustersCoarser coarser;
coarser.Process(&tree);
CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
CHECK_EQ(coarser.GetCoarseEquivalent(o3), coarser.GetCoarseEquivalent(o4));
CHECK_NE(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o3));
CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o5));
}
TEST(ClustersCoarserMultipleConstructors) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
JSObjectsClusterTree tree;
JSObjectsCluster function(i::Heap::function_class_symbol());
// o1 <- Function
JSObjectsCluster o1 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100, &function);
// a1 <- Function
JSObjectsCluster a1 =
AddHeapObjectToTree(&tree, i::Heap::Array_symbol(), 0x1000, &function);
// o2 <- Function
JSObjectsCluster o2 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x200, &function);
// a2 <- Function
JSObjectsCluster a2 =
AddHeapObjectToTree(&tree, i::Heap::Array_symbol(), 0x2000, &function);
ClustersCoarser coarser;
coarser.Process(&tree);
CHECK_EQ(coarser.GetCoarseEquivalent(o1), coarser.GetCoarseEquivalent(o2));
CHECK_EQ(coarser.GetCoarseEquivalent(a1), coarser.GetCoarseEquivalent(a2));
}
TEST(ClustersCoarserPathsTraversal) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
i::ZoneScope zn_scope(i::DELETE_ON_EXIT);
JSObjectsClusterTree tree;
// On the following graph:
//
// p
// <- o21 <- o11 <-
// q o
// <- o22 <- o12 <-
// r
//
// we expect that coarser will deduce equivalences: p ~ q ~ r,
// o21 ~ o22, and o11 ~ o12.
JSObjectsCluster o =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x100);
JSObjectsCluster o11 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x110, &o);
JSObjectsCluster o12 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x120, &o);
JSObjectsCluster o21 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x210, &o11);
JSObjectsCluster o22 =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x220, &o12);
JSObjectsCluster p =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x300, &o21);
JSObjectsCluster q =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x310, &o21, &o22);
JSObjectsCluster r =
AddHeapObjectToTree(&tree, i::Heap::Object_symbol(), 0x320, &o22);
ClustersCoarser coarser;
coarser.Process(&tree);
CHECK_EQ(JSObjectsCluster(), coarser.GetCoarseEquivalent(o));
CHECK_EQ(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o12));
CHECK_EQ(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(o22));
CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(o21));
CHECK_EQ(coarser.GetCoarseEquivalent(p), coarser.GetCoarseEquivalent(q));
CHECK_EQ(coarser.GetCoarseEquivalent(q), coarser.GetCoarseEquivalent(r));
CHECK_NE(coarser.GetCoarseEquivalent(o11), coarser.GetCoarseEquivalent(p));
CHECK_NE(coarser.GetCoarseEquivalent(o21), coarser.GetCoarseEquivalent(p));
}
namespace {
class RetainerProfilePrinter : public RetainerHeapProfile::Printer {
public:
RetainerProfilePrinter() : stream_(&allocator_), lines_(100) {}
void PrintRetainers(const i::StringStream& retainers) {
stream_.Add("%s", *(retainers.ToCString()));
stream_.Put('\0');
}
const char* GetRetainers(const char* constructor) {
FillLines();
const size_t cons_len = strlen(constructor);
for (int i = 0; i < lines_.length(); ++i) {
if (strncmp(constructor, lines_[i], cons_len) == 0 &&
lines_[i][cons_len] == ',') {
return lines_[i] + cons_len + 1;
}
}
return NULL;
}
private:
void FillLines() {
if (lines_.length() > 0) return;
stream_.Put('\0');
stream_str_ = stream_.ToCString();
const char* pos = *stream_str_;
while (pos != NULL && *pos != '\0') {
lines_.Add(pos);
pos = strchr(pos, '\0');
if (pos != NULL) ++pos;
}
}
i::HeapStringAllocator allocator_;
i::StringStream stream_;
i::SmartPointer<const char> stream_str_;
i::List<const char*> lines_;
};
} // namespace
TEST(RetainerProfile) {
v8::HandleScope scope;
v8::Handle<v8::Context> env = v8::Context::New();
env->Enter();
CompileAndRunScript(
"function A() {}\n"
"function B(x) { this.x = x; }\n"
"var a = new A();\n"
"var b = new B(a);\n");
RetainerHeapProfile ret_profile;
i::AssertNoAllocation no_alloc;
i::HeapIterator iterator;
while (iterator.has_next()) {
i::HeapObject* obj = iterator.next();
ret_profile.CollectStats(obj);
}
RetainerProfilePrinter printer;
ret_profile.DebugPrintStats(&printer);
CHECK_EQ("(global property),B", printer.GetRetainers("A"));
CHECK_EQ("(global property)", printer.GetRetainers("B"));
}
#endif // ENABLE_LOGGING_AND_PROFILING
......@@ -279,6 +279,8 @@
'../../src/heap-inl.h',
'../../src/heap.cc',
'../../src/heap.h',
'../../src/heap-profiler.cc',
'../../src/heap-profiler.h',
'../../src/ic-inl.h',
'../../src/ic.cc',
'../../src/ic.h',
......
......@@ -207,6 +207,8 @@
89F23C9F0E78D604006B2466 /* simulator-arm.cc in Sources */ = {isa = PBXBuildFile; fileRef = 897FF17D0E719B8F00D62E90 /* simulator-arm.cc */; };
89F23CA00E78D609006B2466 /* stub-cache-arm.cc in Sources */ = {isa = PBXBuildFile; fileRef = 897FF18A0E719B8F00D62E90 /* stub-cache-arm.cc */; };
89FB0E3A0F8E533F00B04B3C /* d8-posix.cc in Sources */ = {isa = PBXBuildFile; fileRef = 89FB0E360F8E531900B04B3C /* d8-posix.cc */; };
9F11D9A0105AF0A300EBE5B2 /* heap-profiler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9F11D99E105AF0A300EBE5B2 /* heap-profiler.cc */; };
9F11D9A1105AF0A300EBE5B2 /* heap-profiler.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9F11D99E105AF0A300EBE5B2 /* heap-profiler.cc */; };
9F4B7B890FCC877A00DC4117 /* log-utils.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9F4B7B870FCC877A00DC4117 /* log-utils.cc */; };
9F4B7B8A0FCC877A00DC4117 /* log-utils.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9F4B7B870FCC877A00DC4117 /* log-utils.cc */; };
9F92FAA90F8F28AD0089F02C /* func-name-inferrer.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9F92FAA70F8F28AD0089F02C /* func-name-inferrer.cc */; };
......@@ -533,6 +535,8 @@
89F23C950E78D5B6006B2466 /* v8_shell-arm */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "v8_shell-arm"; sourceTree = BUILT_PRODUCTS_DIR; };
89FB0E360F8E531900B04B3C /* d8-posix.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "d8-posix.cc"; path = "../src/d8-posix.cc"; sourceTree = "<group>"; };
89FB0E370F8E531900B04B3C /* d8-windows.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = "d8-windows.cc"; path = "../src/d8-windows.cc"; sourceTree = "<group>"; };
9F11D99E105AF0A300EBE5B2 /* heap-profiler.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "heap-profiler.cc"; sourceTree = "<group>"; };
9F11D99F105AF0A300EBE5B2 /* heap-profiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "heap-profiler.h"; sourceTree = "<group>"; };
9F4B7B870FCC877A00DC4117 /* log-utils.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "log-utils.cc"; sourceTree = "<group>"; };
9F4B7B880FCC877A00DC4117 /* log-utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "log-utils.h"; sourceTree = "<group>"; };
9F92FAA70F8F28AD0089F02C /* func-name-inferrer.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "func-name-inferrer.cc"; sourceTree = "<group>"; };
......@@ -626,7 +630,6 @@
897FF0D70E719AB300D62E90 /* C++ */ = {
isa = PBXGroup;
children = (
22A76C900FF259E600FDC694 /* log-inl.h */,
897FF0F60E719B8F00D62E90 /* accessors.cc */,
897FF0F70E719B8F00D62E90 /* accessors.h */,
897FF0F80E719B8F00D62E90 /* allocation.cc */,
......@@ -725,6 +728,8 @@
897FF1460E719B8F00D62E90 /* heap-inl.h */,
897FF1470E719B8F00D62E90 /* heap.cc */,
897FF1480E719B8F00D62E90 /* heap.h */,
9F11D99E105AF0A300EBE5B2 /* heap-profiler.cc */,
9F11D99F105AF0A300EBE5B2 /* heap-profiler.h */,
897FF1490E719B8F00D62E90 /* ic-arm.cc */,
897FF14A0E719B8F00D62E90 /* ic-ia32.cc */,
897FF14B0E719B8F00D62E90 /* ic-inl.h */,
......@@ -742,6 +747,7 @@
897FF1510E719B8F00D62E90 /* list.h */,
897FF1520E719B8F00D62E90 /* log.cc */,
897FF1530E719B8F00D62E90 /* log.h */,
22A76C900FF259E600FDC694 /* log-inl.h */,
9F4B7B870FCC877A00DC4117 /* log-utils.cc */,
9F4B7B880FCC877A00DC4117 /* log-utils.h */,
897FF1540E719B8F00D62E90 /* macro-assembler-arm.cc */,
......@@ -1201,6 +1207,7 @@
89A88E2E0E71A6D60043BA31 /* zone.cc in Sources */,
9F4B7B890FCC877A00DC4117 /* log-utils.cc in Sources */,
8981F6001010501900D1520E /* frame-element.cc in Sources */,
9F11D9A0105AF0A300EBE5B2 /* heap-profiler.cc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -1306,6 +1313,7 @@
89F23C820E78D5B2006B2466 /* zone.cc in Sources */,
9F4B7B8A0FCC877A00DC4117 /* log-utils.cc in Sources */,
8981F6011010502800D1520E /* frame-element.cc in Sources */,
9F11D9A1105AF0A300EBE5B2 /* heap-profiler.cc in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......
......@@ -488,6 +488,14 @@
RelativePath="..\..\src\heap.h"
>
</File>
<File
RelativePath="..\..\src\heap-profiler.cc"
>
</File>
<File
RelativePath="..\..\src\heap-profiler.h"
>
</File>
<File
RelativePath="..\..\src\ia32\ic-ia32.cc"
>
......
......@@ -496,6 +496,14 @@
RelativePath="..\..\src\heap.h"
>
</File>
<File
RelativePath="..\..\src\heap-profiler.cc"
>
</File>
<File
RelativePath="..\..\src\heap-profiler.h"
>
</File>
<File
RelativePath="..\..\src\arm\ic-arm.cc"
>
......
......@@ -488,6 +488,14 @@
RelativePath="..\..\src\heap.h"
>
</File>
<File
RelativePath="..\..\src\heap-profiler.cc"
>
</File>
<File
RelativePath="..\..\src\heap-profiler.h"
>
</File>
<File
RelativePath="..\..\src\x64\ic-x64.cc"
>
......
......@@ -197,6 +197,10 @@
RelativePath="..\..\test\cctest\test-heap.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-heap-profiler.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-lock.cc"
>
......
......@@ -193,6 +193,10 @@
RelativePath="..\..\test\cctest\test-heap.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-heap-profiler.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-lock.cc"
>
......
......@@ -199,6 +199,10 @@
RelativePath="..\..\test\cctest\test-heap.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-heap-profiler.cc"
>
</File>
<File
RelativePath="..\..\test\cctest\test-lock.cc"
>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment