Revision: 7012
Author: [email protected]
Date: Wed Mar 2 01:16:05 2011
Log: Adding files for LiveObjectList implementation.
Patch by Mark Lam from Hewlett-Packard Development Company, LP
Review URL: http://codereview.chromium.org/6357005
http://code.google.com/p/v8/source/detail?r=7012
Modified:
/branches/bleeding_edge/src/flag-definitions.h
/branches/bleeding_edge/src/liveobjectlist-inl.h
/branches/bleeding_edge/src/liveobjectlist.cc
/branches/bleeding_edge/src/liveobjectlist.h
=======================================
--- /branches/bleeding_edge/src/flag-definitions.h Tue Mar 1 04:26:19 2011
+++ /branches/bleeding_edge/src/flag-definitions.h Wed Mar 2 01:16:05 2011
@@ -267,6 +267,12 @@
// ic.cc
DEFINE_bool(use_ic, true, "use inline caching")
+#ifdef LIVE_OBJECT_LIST
+// liveobjectlist.cc
+DEFINE_string(lol_workdir, NULL, "path for lol temp files")
+DEFINE_bool(verify_lol, false, "perform debugging verification for lol")
+#endif
+
// macro-assembler-ia32.cc
DEFINE_bool(native_code_counters, false,
"generate extra code for manipulating stats counters")
=======================================
--- /branches/bleeding_edge/src/liveobjectlist-inl.h Thu Jan 20 00:11:53
2011
+++ /branches/bleeding_edge/src/liveobjectlist-inl.h Wed Mar 2 01:16:05
2011
@@ -32,5 +32,95 @@
#include "liveobjectlist.h"
+namespace v8 {
+namespace internal {
+
+#ifdef LIVE_OBJECT_LIST
+
+void LiveObjectList::GCEpilogue() {
+ if (!NeedLOLProcessing()) return;
+ GCEpiloguePrivate();
+}
+
+
+void LiveObjectList::GCPrologue() {
+ if (!NeedLOLProcessing()) return;
+#ifdef VERIFY_LOL
+ if (FLAG_verify_lol) {
+ Verify();
+ }
+#endif
+}
+
+
+void LiveObjectList::IterateElements(ObjectVisitor* v) {
+ if (!NeedLOLProcessing()) return;
+ IterateElementsPrivate(v);
+}
+
+
+void LiveObjectList::ProcessNonLive(HeapObject *obj) {
+ // Only do work if we have at least one list to process.
+ if (last()) DoProcessNonLive(obj);
+}
+
+
+void LiveObjectList::UpdateReferencesForScavengeGC() {
+ if (LiveObjectList::NeedLOLProcessing()) {
+ UpdateLiveObjectListVisitor update_visitor;
+ LiveObjectList::IterateElements(&update_visitor);
+ }
+}
+
+
+LiveObjectList* LiveObjectList::FindLolForId(int id,
+ LiveObjectList* start_lol) {
+ if (id != 0) {
+ LiveObjectList* lol = start_lol;
+ while (lol != NULL) {
+ if (lol->id() == id) {
+ return lol;
+ }
+ lol = lol->prev_;
+ }
+ }
+ return NULL;
+}
+
+
+// Iterates the elements in every lol and returns the one that matches the
+// specified key. If no matching element is found, then it returns NULL.
+template <typename T>
+inline LiveObjectList::Element*
+LiveObjectList::FindElementFor(T (*GetValue)(LiveObjectList::Element*), T
key) {
+ LiveObjectList *lol = last();
+ while (lol != NULL) {
+ Element* elements = lol->elements_;
+ for (int i = 0; i < lol->obj_count_; i++) {
+ Element* element = &elements[i];
+ if (GetValue(element) == key) {
+ return element;
+ }
+ }
+ lol = lol->prev_;
+ }
+ return NULL;
+}
+
+
+inline int LiveObjectList::GetElementId(LiveObjectList::Element* element) {
+ return element->id_;
+}
+
+
+inline HeapObject*
+LiveObjectList::GetElementObj(LiveObjectList::Element* element) {
+ return element->obj_;
+}
+
+#endif // LIVE_OBJECT_LIST
+
+} } // namespace v8::internal
+
#endif // V8_LIVEOBJECTLIST_INL_H_
=======================================
--- /branches/bleeding_edge/src/liveobjectlist.cc Thu Jan 20 00:11:53 2011
+++ /branches/bleeding_edge/src/liveobjectlist.cc Wed Mar 2 01:16:05 2011
@@ -37,7 +37,7 @@
#include "heap.h"
#include "inspector.h"
#include "list-inl.h"
-#include "liveobjectlist.h"
+#include "liveobjectlist-inl.h"
#include "string-stream.h"
#include "top.h"
#include "v8utils.h"
@@ -46,6 +46,2480 @@
namespace internal {
+typedef int (*RawComparer)(const void*, const void*);
+
+
+#ifdef CHECK_ALL_OBJECT_TYPES
+
+#define DEBUG_LIVE_OBJECT_TYPES(v) \
+ v(Smi, "unexpected: Smi") \
+ \
+ v(CodeCache, "unexpected: CodeCache") \
+ v(BreakPointInfo, "unexpected: BreakPointInfo") \
+ v(DebugInfo, "unexpected: DebugInfo") \
+ v(TypeSwitchInfo, "unexpected: TypeSwitchInfo") \
+ v(SignatureInfo, "unexpected: SignatureInfo") \
+ v(Script, "unexpected: Script") \
+ v(ObjectTemplateInfo, "unexpected: ObjectTemplateInfo") \
+ v(FunctionTemplateInfo, "unexpected: FunctionTemplateInfo") \
+ v(CallHandlerInfo, "unexpected: CallHandlerInfo") \
+ v(InterceptorInfo, "unexpected: InterceptorInfo") \
+ v(AccessCheckInfo, "unexpected: AccessCheckInfo") \
+ v(AccessorInfo, "unexpected: AccessorInfo") \
+ v(ExternalTwoByteString, "unexpected: ExternalTwoByteString") \
+ v(ExternalAsciiString, "unexpected: ExternalAsciiString") \
+ v(ExternalString, "unexpected: ExternalString") \
+ v(SeqTwoByteString, "unexpected: SeqTwoByteString") \
+ v(SeqAsciiString, "unexpected: SeqAsciiString") \
+ v(SeqString, "unexpected: SeqString") \
+ v(JSFunctionResultCache, "unexpected: JSFunctionResultCache") \
+ v(GlobalContext, "unexpected: GlobalContext") \
+ v(MapCache, "unexpected: MapCache") \
+ v(CodeCacheHashTable, "unexpected: CodeCacheHashTable") \
+ v(CompilationCacheTable, "unexpected: CompilationCacheTable") \
+ v(SymbolTable, "unexpected: SymbolTable") \
+ v(Dictionary, "unexpected: Dictionary") \
+ v(HashTable, "unexpected: HashTable") \
+ v(DescriptorArray, "unexpected: DescriptorArray") \
+ v(ExternalFloatArray, "unexpected: ExternalFloatArray") \
+ v(ExternalUnsignedIntArray, "unexpected: ExternalUnsignedIntArray") \
+ v(ExternalIntArray, "unexpected: ExternalIntArray") \
+ v(ExternalUnsignedShortArray, "unexpected: ExternalUnsignedShortArray") \
+ v(ExternalShortArray, "unexpected: ExternalShortArray") \
+ v(ExternalUnsignedByteArray, "unexpected: ExternalUnsignedByteArray") \
+ v(ExternalByteArray, "unexpected: ExternalByteArray") \
+ v(JSValue, "unexpected: JSValue")
+
+#else
+#define DEBUG_LIVE_OBJECT_TYPES(v)
+#endif
+
+
+#define FOR_EACH_LIVE_OBJECT_TYPE(v) \
+ DEBUG_LIVE_OBJECT_TYPES(v) \
+ \
+ v(JSArray, "JSArray") \
+ v(JSRegExp, "JSRegExp") \
+ v(JSFunction, "JSFunction") \
+ v(JSGlobalObject, "JSGlobal") \
+ v(JSBuiltinsObject, "JSBuiltins") \
+ v(GlobalObject, "Global") \
+ v(JSGlobalProxy, "JSGlobalProxy") \
+ v(JSObject, "JSObject") \
+ \
+ v(Context, "meta: Context") \
+ v(ByteArray, "meta: ByteArray") \
+ v(PixelArray, "meta: PixelArray") \
+ v(ExternalArray, "meta: ExternalArray") \
+ v(FixedArray, "meta: FixedArray") \
+ v(String, "String") \
+ v(HeapNumber, "HeapNumber") \
+ \
+ v(Code, "meta: Code") \
+ v(Map, "meta: Map") \
+ v(Oddball, "Oddball") \
+ v(Proxy, "meta: Proxy") \
+ v(SharedFunctionInfo, "meta: SharedFunctionInfo") \
+ v(Struct, "meta: Struct") \
+ \
+ v(HeapObject, "HeapObject")
+
+
+enum /* LiveObjectType */ {
+#define DECLARE_OBJECT_TYPE_ENUM(type, name) kType##type,
+ FOR_EACH_LIVE_OBJECT_TYPE(DECLARE_OBJECT_TYPE_ENUM)
+ kInvalidLiveObjType,
+ kNumberOfTypes
+#undef DECLARE_OBJECT_TYPE_ENUM
+};
+
+
+LiveObjectType GetObjectType(HeapObject* heap_obj) {
+ // TODO(mlam): investigate usint Map::instance_type() instead.
+#define CHECK_FOR_OBJECT_TYPE(type, name) \
+ if (heap_obj->Is##type()) return kType##type;
+ FOR_EACH_LIVE_OBJECT_TYPE(CHECK_FOR_OBJECT_TYPE)
+#undef CHECK_FOR_OBJECT_TYPE
+
+ UNREACHABLE();
+ return kInvalidLiveObjType;
+}
+
+
+inline const char* GetObjectTypeDesc(LiveObjectType type) {
+ static const char* const name[kNumberOfTypes] = {
+ #define DEFINE_OBJECT_TYPE_NAME(type, name) name,
+ FOR_EACH_LIVE_OBJECT_TYPE(DEFINE_OBJECT_TYPE_NAME)
+ "invalid"
+ #undef DEFINE_OBJECT_TYPE_NAME
+ };
+ ASSERT(type < kNumberOfTypes);
+ return name[type];
+}
+
+
+const char* GetObjectTypeDesc(HeapObject* heap_obj) {
+ LiveObjectType type = GetObjectType(heap_obj);
+ return GetObjectTypeDesc(type);
+}
+
+
+bool IsOfType(LiveObjectType type, HeapObject *obj) {
+ // Note: there are types that are more general (e.g. JSObject) that would
+ // have passed the Is##type_() test for more specialized types (e.g.
+ // JSFunction). If we find a more specialized match but we're looking
for
+ // the general type, then we should reject the ones that matches the
+ // specialized type.
+#define CHECK_OBJECT_TYPE(type_, name) \
+ if (obj->Is##type_()) return (type == kType##type_);
+
+ FOR_EACH_LIVE_OBJECT_TYPE(CHECK_OBJECT_TYPE)
+#undef CHECK_OBJECT_TYPE
+
+ return false;
+}
+
+
+const AllocationSpace kInvalidSpace = static_cast<AllocationSpace>(-1);
+
+static AllocationSpace FindSpaceFor(String* space_str) {
+ SmartPointer<char> s =
+ space_str->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
+
+ const char* key_str = *s;
+ switch (key_str[0]) {
+ case 'c':
+ if (strcmp(key_str, "cell") == 0) return CELL_SPACE;
+ if (strcmp(key_str, "code") == 0) return CODE_SPACE;
+ break;
+ case 'l':
+ if (strcmp(key_str, "lo") == 0) return LO_SPACE;
+ break;
+ case 'm':
+ if (strcmp(key_str, "map") == 0) return MAP_SPACE;
+ break;
+ case 'n':
+ if (strcmp(key_str, "new") == 0) return NEW_SPACE;
+ break;
+ case 'o':
+ if (strcmp(key_str, "old-pointer") == 0) return OLD_POINTER_SPACE;
+ if (strcmp(key_str, "old-data") == 0) return OLD_DATA_SPACE;
+ break;
+ }
+ return kInvalidSpace;
+}
+
+
+static bool InSpace(AllocationSpace space, HeapObject *heap_obj) {
+ if (space != LO_SPACE) {
+ return Heap::InSpace(heap_obj, space);
+ }
+
+ // This is an optimization to speed up the check for an object in the LO
+ // space by exclusion because we know that all object pointers passed in
+ // here are guaranteed to be in the heap. Hence, it is safe to infer
+ // using an exclusion test.
+ // Note: calling Heap::InSpace(heap_obj, LO_SPACE) is too slow for our
+ // filters.
+ int first_space = static_cast<int>(FIRST_SPACE);
+ int last_space = static_cast<int>(LO_SPACE);
+ for (int sp = first_space; sp < last_space; sp++) {
+ if (Heap::InSpace(heap_obj, static_cast<AllocationSpace>(sp))) {
+ return false;
+ }
+ }
+ SLOW_ASSERT(Heap::InSpace(heap_obj, LO_SPACE));
+ return true;
+}
+
+
+static LiveObjectType FindTypeFor(String* type_str) {
+ SmartPointer<char> s =
+ type_str->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
+
+#define CHECK_OBJECT_TYPE(type_, name) { \
+ const char* type_desc = GetObjectTypeDesc(kType##type_); \
+ const char* key_str = *s; \
+ if (strstr(type_desc, key_str) != NULL) return kType##type_; \
+ }
+ FOR_EACH_LIVE_OBJECT_TYPE(CHECK_OBJECT_TYPE)
+#undef CHECK_OBJECT_TYPE
+
+ return kInvalidLiveObjType;
+}
+
+
+class LolFilter {
+ public:
+ explicit LolFilter(Handle<JSObject> filter_obj);
+
+ inline bool is_active() const { return is_active_; }
+ inline bool Matches(HeapObject* obj) {
+ return !is_active() || MatchesSlow(obj);
+ }
+
+ private:
+ void InitTypeFilter(Handle<JSObject> filter_obj);
+ void InitSpaceFilter(Handle<JSObject> filter_obj);
+ void InitPropertyFilter(Handle<JSObject> filter_obj);
+ bool MatchesSlow(HeapObject* obj);
+
+ bool is_active_;
+ LiveObjectType type_;
+ AllocationSpace space_;
+ Handle<String> prop_;
+};
+
+
+LolFilter::LolFilter(Handle<JSObject> filter_obj)
+ : is_active_(false),
+ type_(kInvalidLiveObjType),
+ space_(kInvalidSpace),
+ prop_() {
+ if (filter_obj.is_null()) return;
+
+ InitTypeFilter(filter_obj);
+ InitSpaceFilter(filter_obj);
+ InitPropertyFilter(filter_obj);
+}
+
+
+void LolFilter::InitTypeFilter(Handle<JSObject> filter_obj) {
+ Handle<String> type_sym = Factory::LookupAsciiSymbol("type");
+ MaybeObject* maybe_result = filter_obj->GetProperty(*type_sym);
+ Object* type_obj;
+ if (maybe_result->ToObject(&type_obj)) {
+ if (type_obj->IsString()) {
+ String* type_str = String::cast(type_obj);
+ type_ = FindTypeFor(type_str);
+ if (type_ != kInvalidLiveObjType) {
+ is_active_ = true;
+ }
+ }
+ }
+}
+
+
+void LolFilter::InitSpaceFilter(Handle<JSObject> filter_obj) {
+ Handle<String> space_sym = Factory::LookupAsciiSymbol("space");
+ MaybeObject* maybe_result = filter_obj->GetProperty(*space_sym);
+ Object* space_obj;
+ if (maybe_result->ToObject(&space_obj)) {
+ if (space_obj->IsString()) {
+ String* space_str = String::cast(space_obj);
+ space_ = FindSpaceFor(space_str);
+ if (space_ != kInvalidSpace) {
+ is_active_ = true;
+ }
+ }
+ }
+}
+
+
+void LolFilter::InitPropertyFilter(Handle<JSObject> filter_obj) {
+ Handle<String> prop_sym = Factory::LookupAsciiSymbol("prop");
+ MaybeObject* maybe_result = filter_obj->GetProperty(*prop_sym);
+ Object* prop_obj;
+ if (maybe_result->ToObject(&prop_obj)) {
+ if (prop_obj->IsString()) {
+ prop_ = Handle<String>(String::cast(prop_obj));
+ is_active_ = true;
+ }
+ }
+}
+
+
+bool LolFilter::MatchesSlow(HeapObject* obj) {
+ if ((type_ != kInvalidLiveObjType) && !IsOfType(type_, obj)) {
+ return false; // Fail because obj is not of the type of interest.
+ }
+ if ((space_ != kInvalidSpace) && !InSpace(space_, obj)) {
+ return false; // Fail because obj is not in the space of interest.
+ }
+ if (!prop_.is_null() && obj->IsJSObject()) {
+ LookupResult result;
+ obj->Lookup(*prop_, &result);
+ if (!result.IsProperty()) {
+ return false; // Fail because obj does not have the property of
interest.
+ }
+ }
+ return true;
+}
+
+
+class LolIterator {
+ public:
+ LolIterator(LiveObjectList* older, LiveObjectList* newer)
+ : older_(older),
+ newer_(newer),
+ curr_(0),
+ elements_(0),
+ count_(0),
+ index_(0) { }
+
+ inline void Init() {
+ SetCurrent(newer_);
+ // If the elements_ list is empty, then move on to the next list as
long
+ // as we're not at the last list (indicated by done()).
+ while ((elements_ == NULL) && !Done()) {
+ SetCurrent(curr_->prev_);
+ }
+ }
+
+ inline bool Done() const {
+ return (curr_ == older_);
+ }
+
+ // Object level iteration.
+ inline void Next() {
+ index_++;
+ if (index_ >= count_) {
+ // Iterate backwards until we get to the oldest list.
+ while (!Done()) {
+ SetCurrent(curr_->prev_);
+ // If we have elements to process, we're good to go.
+ if (elements_ != NULL) break;
+
+ // Else, we should advance to the next older list.
+ }
+ }
+ }
+
+ inline int Id() const {
+ return elements_[index_].id_;
+ }
+ inline HeapObject* Obj() const {
+ return elements_[index_].obj_;
+ }
+
+ inline int LolObjCount() const {
+ if (curr_ != NULL) return curr_->obj_count_;
+ return 0;
+ }
+
+ protected:
+ inline void SetCurrent(LiveObjectList* new_curr) {
+ curr_ = new_curr;
+ if (curr_ != NULL) {
+ elements_ = curr_->elements_;
+ count_ = curr_->obj_count_;
+ index_ = 0;
+ }
+ }
+
+ LiveObjectList* older_;
+ LiveObjectList* newer_;
+ LiveObjectList* curr_;
+ LiveObjectList::Element* elements_;
+ int count_;
+ int index_;
+};
+
+
+class LolForwardIterator : public LolIterator {
+ public:
+ LolForwardIterator(LiveObjectList* first, LiveObjectList* last)
+ : LolIterator(first, last) {
+ }
+
+ inline void Init() {
+ SetCurrent(older_);
+ // If the elements_ list is empty, then move on to the next list as
long
+ // as we're not at the last list (indicated by Done()).
+ while ((elements_ == NULL) && !Done()) {
+ SetCurrent(curr_->next_);
+ }
+ }
+
+ inline bool Done() const {
+ return (curr_ == newer_);
+ }
+
+ // Object level iteration.
+ inline void Next() {
+ index_++;
+ if (index_ >= count_) {
+ // Done with current list. Move on to the next.
+ while (!Done()) { // If not at the last list already, ...
+ SetCurrent(curr_->next_);
+ // If we have elements to process, we're good to go.
+ if (elements_ != NULL) break;
+
+ // Else, we should advance to the next list.
+ }
+ }
+ }
+};
+
+
+// Minimizes the white space in a string. Tabs and newlines are replaced
+// with a space where appropriate.
+static int CompactString(char* str) {
+ char* src = str;
+ char* dst = str;
+ char prev_ch = 0;
+ while (*dst != '\0') {
+ char ch = *src++;
+ // We will treat non-ascii chars as '?'.
+ if ((ch & 0x80) != 0) {
+ ch = '?';
+ }
+ // Compact contiguous whitespace chars into a single ' '.
+ if (isspace(ch)) {
+ if (prev_ch != ' ') *dst++ = ' ';
+ prev_ch = ' ';
+ continue;
+ }
+ *dst++ = ch;
+ prev_ch = ch;
+ }
+ return (dst - str);
+}
+
+
+// Generates a custom description based on the specific type of
+// object we're looking at. We only generate specialized
+// descriptions where we can. In all other cases, we emit the
+// generic info.
+static void GenerateObjectDesc(HeapObject* obj,
+ char* buffer,
+ int buffer_size) {
+ Vector<char> buffer_v(buffer, buffer_size);
+ ASSERT(obj != NULL);
+ if (obj->IsJSArray()) {
+ JSArray* jsarray = JSArray::cast(obj);
+ double length = jsarray->length()->Number();
+ OS::SNPrintF(buffer_v,
+ "%p <%s> len %g",
+ reinterpret_cast<void*>(obj),
+ GetObjectTypeDesc(obj),
+ length);
+
+ } else if (obj->IsString()) {
+ String *str = String::cast(obj);
+ // Only grab up to 160 chars in case they are double byte.
+ // We'll only dump 80 of them after we compact them.
+ const int kMaxCharToDump = 80;
+ const int kMaxBufferSize = kMaxCharToDump * 2;
+ SmartPointer<char> str_sp = str->ToCString(DISALLOW_NULLS,
+ ROBUST_STRING_TRAVERSAL,
+ 0,
+ kMaxBufferSize);
+ char* str_cstr = *str_sp;
+ int length = CompactString(str_cstr);
+ OS::SNPrintF(buffer_v,
+ "%p <%s> '%.80s%s'",
+ reinterpret_cast<void*>(obj),
+ GetObjectTypeDesc(obj),
+ str_cstr,
+ (length > kMaxCharToDump) ? "..." : "");
+
+ } else if (obj->IsJSFunction() || obj->IsSharedFunctionInfo()) {
+ SharedFunctionInfo* sinfo;
+ if (obj->IsJSFunction()) {
+ JSFunction* func = JSFunction::cast(obj);
+ sinfo = func->shared();
+ } else {
+ sinfo = SharedFunctionInfo::cast(obj);
+ }
+
+ String* name = sinfo->DebugName();
+ SmartPointer<char> name_sp =
+ name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
+ char* name_cstr = *name_sp;
+
+ HeapStringAllocator string_allocator;
+ StringStream stream(&string_allocator);
+ sinfo->SourceCodePrint(&stream, 50);
+ SmartPointer<const char> source_sp = stream.ToCString();
+ const char* source_cstr = *source_sp;
+
+ OS::SNPrintF(buffer_v,
+ "%p <%s> '%s' %s",
+ reinterpret_cast<void*>(obj),
+ GetObjectTypeDesc(obj),
+ name_cstr,
+ source_cstr);
+
+ } else if (obj->IsFixedArray()) {
+ FixedArray* fixed = FixedArray::cast(obj);
+
+ OS::SNPrintF(buffer_v,
+ "%p <%s> len %d",
+ reinterpret_cast<void*>(obj),
+ GetObjectTypeDesc(obj),
+ fixed->length());
+
+ } else {
+ OS::SNPrintF(buffer_v,
+ "%p <%s>",
+ reinterpret_cast<void*>(obj),
+ GetObjectTypeDesc(obj));
+ }
+}
+
+
+// Utility function for filling in a line of detail in a verbose dump.
+static bool AddObjDetail(Handle<FixedArray> arr,
+ int index,
+ int obj_id,
+ Handle<HeapObject> target,
+ const char* desc_str,
+ Handle<String> id_sym,
+ Handle<String> desc_sym,
+ Handle<String> size_sym,
+ Handle<JSObject> detail,
+ Handle<String> desc,
+ Handle<Object> error) {
+ detail = Factory::NewJSObject(Top::object_function());
+ if (detail->IsFailure()) {
+ error = detail;
+ return false;
+ }
+
+ int size = 0;
+ char buffer[512];
+ if (desc_str == NULL) {
+ ASSERT(!target.is_null());
+ HeapObject* obj = *target;
+ GenerateObjectDesc(obj, buffer, sizeof(buffer));
+ desc_str = buffer;
+ size = obj->Size();
+ }
+ desc = Factory::NewStringFromAscii(CStrVector(desc_str));
+ if (desc->IsFailure()) {
+ error = desc;
+ return false;
+ }
+
+ { MaybeObject* maybe_result =
+ detail->SetProperty(*id_sym, Smi::FromInt(obj_id), NONE);
+ if (maybe_result->IsFailure()) return false;
+ }
+ { MaybeObject* maybe_result =
+ detail->SetProperty(*desc_sym, *desc, NONE);
+ if (maybe_result->IsFailure()) return false;
+ }
+ { MaybeObject* maybe_result =
+ detail->SetProperty(*size_sym, Smi::FromInt(size), NONE);
+ if (maybe_result->IsFailure()) return false;
+ }
+
+ arr->set(index, *detail);
+ return true;
+}
+
+
+class DumpWriter {
+ public:
+ virtual ~DumpWriter() {}
+
+ virtual void ComputeTotalCountAndSize(LolFilter* filter,
+ int* count,
+ int* size) = 0;
+ virtual bool Write(Handle<FixedArray> elements_arr,
+ int start,
+ int dump_limit,
+ LolFilter* filter,
+ Handle<Object> error) = 0;
+};
+
+
+class LolDumpWriter: public DumpWriter {
+ public:
+ LolDumpWriter(LiveObjectList* older, LiveObjectList* newer)
+ : older_(older), newer_(newer) {
+ }
+
+ void ComputeTotalCountAndSize(LolFilter* filter, int* count, int* size) {
+ *count = 0;
+ *size = 0;
+
+ LolIterator it(older_, newer_);
+ for (it.Init(); !it.Done(); it.Next()) {
+ HeapObject* heap_obj = it.Obj();
+ if (!filter->Matches(heap_obj)) {
+ continue;
+ }
+
+ *size += heap_obj->Size();
+ (*count)++;
+ }
+ }
+
+ bool Write(Handle<FixedArray> elements_arr,
+ int start,
+ int dump_limit,
+ LolFilter* filter,
+ Handle<Object> error) {
+ // The lols are listed in latest to earliest. We want to dump from
+ // earliest to latest. So, compute the last element to start with.
+ int index = 0;
+ int count = 0;
+
+ // Prefetch some needed symbols.
+ Handle<String> id_sym = Factory::LookupAsciiSymbol("id");
+ Handle<String> desc_sym = Factory::LookupAsciiSymbol("desc");
+ Handle<String> size_sym = Factory::LookupAsciiSymbol("size");
+
+ // Fill the array with the lol object details.
+ Handle<JSObject> detail;
+ Handle<String> desc;
+ Handle<HeapObject> target;
+
+ LiveObjectList* first_lol = (older_ != NULL) ?
+ older_->next_ : LiveObjectList::first_;
+ LiveObjectList* last_lol = (newer_ != NULL) ? newer_->next_ : NULL;
+
+ LolForwardIterator it(first_lol, last_lol);
+ for (it.Init(); !it.Done() && (index < dump_limit); it.Next()) {
+ HeapObject* heap_obj = it.Obj();
+
+ // Skip objects that have been filtered out.
+ if (!filter->Matches(heap_obj)) {
+ continue;
+ }
+
+ // Only report objects that are in the section of interest.
+ if (count >= start) {
+ target = Handle<HeapObject>(heap_obj);
+ bool success = AddObjDetail(elements_arr,
+ index++,
+ it.Id(),
+ target,
+ NULL,
+ id_sym,
+ desc_sym,
+ size_sym,
+ detail,
+ desc,
+ error);
+ if (!success) return false;
+ }
+ count++;
+ }
+ return true;
+ }
+
+ private:
+ LiveObjectList* older_;
+ LiveObjectList* newer_;
+};
+
+
+class RetainersDumpWriter: public DumpWriter {
+ public:
+ RetainersDumpWriter(Handle<HeapObject> target,
+ Handle<JSObject> instance_filter,
+ Handle<JSFunction> args_function)
+ : target_(target),
+ instance_filter_(instance_filter),
+ args_function_(args_function) {
+ }
+
+ void ComputeTotalCountAndSize(LolFilter* filter, int* count, int* size) {
+ Handle<FixedArray> retainers_arr;
+ Handle<Object> error;
+
+ *size = -1;
+ LiveObjectList::GetRetainers(target_,
+ instance_filter_,
+ retainers_arr,
+ 0,
+ Smi::kMaxValue,
+ count,
+ filter,
+ NULL,
+ *args_function_,
+ error);
+ }
+
+ bool Write(Handle<FixedArray> elements_arr,
+ int start,
+ int dump_limit,
+ LolFilter* filter,
+ Handle<Object> error) {
+ int dummy;
+ int count;
+
+ // Fill the retainer objects.
+ count = LiveObjectList::GetRetainers(target_,
+ instance_filter_,
+ elements_arr,
+ start,
+ dump_limit,
+ &dummy,
+ filter,
+ NULL,
+ *args_function_,
+ error);
+ if (count < 0) {
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ Handle<HeapObject> target_;
+ Handle<JSObject> instance_filter_;
+ Handle<JSFunction> args_function_;
+};
+
+
+class LiveObjectSummary {
+ public:
+ explicit LiveObjectSummary(LolFilter* filter)
+ : total_count_(0),
+ total_size_(0),
+ found_root_(false),
+ found_weak_root_(false),
+ filter_(filter) {
+ memset(counts_, 0, sizeof(counts_[0]) * kNumberOfEntries);
+ memset(sizes_, 0, sizeof(sizes_[0]) * kNumberOfEntries);
+ }
+
+ void Add(HeapObject* heap_obj) {
+ int size = heap_obj->Size();
+ LiveObjectType type = GetObjectType(heap_obj);
+ ASSERT(type != kInvalidLiveObjType);
+ counts_[type]++;
+ sizes_[type] += size;
+ total_count_++;
+ total_size_ += size;
+ }
+
+ void set_found_root() { found_root_ = true; }
+ void set_found_weak_root() { found_weak_root_ = true; }
+
+ inline int Count(LiveObjectType type) {
+ return counts_[type];
+ }
+ inline int Size(LiveObjectType type) {
+ return sizes_[type];
+ }
+ inline int total_count() {
+ return total_count_;
+ }
+ inline int total_size() {
+ return total_size_;
+ }
+ inline bool found_root() {
+ return found_root_;
+ }
+ inline bool found_weak_root() {
+ return found_weak_root_;
+ }
+ int GetNumberOfEntries() {
+ int entries = 0;
+ for (int i = 0; i < kNumberOfEntries; i++) {
+ if (counts_[i]) entries++;
+ }
+ return entries;
+ }
+
+ inline LolFilter* filter() { return filter_; }
+
+ static const int kNumberOfEntries = kNumberOfTypes;
+
+ private:
+ int counts_[kNumberOfEntries];
+ int sizes_[kNumberOfEntries];
+ int total_count_;
+ int total_size_;
+ bool found_root_;
+ bool found_weak_root_;
+
+ LolFilter *filter_;
+};
+
+
+// Abstraction for a summary writer.
+class SummaryWriter {
+ public:
+ virtual ~SummaryWriter() {}
+ virtual void Write(LiveObjectSummary* summary) = 0;
+};
+
+
+// A summary writer for filling in a summary of lol lists and diffs.
+class LolSummaryWriter: public SummaryWriter {
+ public:
+ LolSummaryWriter(LiveObjectList *older_lol,
+ LiveObjectList *newer_lol)
+ : older_(older_lol), newer_(newer_lol) {
+ }
+
+ void Write(LiveObjectSummary* summary) {
+ LolFilter* filter = summary->filter();
+
+ // Fill the summary with the lol object details.
+ LolIterator it(older_, newer_);
+ for (it.Init(); !it.Done(); it.Next()) {
+ HeapObject* heap_obj = it.Obj();
+ if (!filter->Matches(heap_obj)) {
+ continue;
+ }
+ summary->Add(heap_obj);
+ }
+ }
+
+ private:
+ LiveObjectList* older_;
+ LiveObjectList* newer_;
+};
+
+
+// A summary writer for filling in a retainers list.
+class RetainersSummaryWriter: public SummaryWriter {
+ public:
+ RetainersSummaryWriter(Handle<HeapObject> target,
+ Handle<JSObject> instance_filter,
+ Handle<JSFunction> args_function)
+ : target_(target),
+ instance_filter_(instance_filter),
+ args_function_(args_function) {
+ }
+
+ void Write(LiveObjectSummary* summary) {
+ Handle<FixedArray> retainers_arr;
+ Handle<Object> error;
+ int dummy_total_count;
+ LiveObjectList::GetRetainers(target_,
+ instance_filter_,
+ retainers_arr,
+ 0,
+ Smi::kMaxValue,
+ &dummy_total_count,
+ summary->filter(),
+ summary,
+ *args_function_,
+ error);
+ }
+
+ private:
+ Handle<HeapObject> target_;
+ Handle<JSObject> instance_filter_;
+ Handle<JSFunction> args_function_;
+};
+
+
+uint32_t LiveObjectList::next_element_id_ = 1;
+int LiveObjectList::list_count_ = 0;
+int LiveObjectList::last_id_ = 0;
+LiveObjectList* LiveObjectList::first_ = NULL;
+LiveObjectList* LiveObjectList::last_ = NULL;
+
+
+LiveObjectList::LiveObjectList(LiveObjectList* prev, int capacity)
+ : prev_(prev),
+ next_(NULL),
+ capacity_(capacity),
+ obj_count_(0) {
+ elements_ = NewArray<Element>(capacity);
+ id_ = ++last_id_;
+
+ list_count_++;
+}
+
+
+LiveObjectList::~LiveObjectList() {
+ DeleteArray<Element>(elements_);
+ delete prev_;
+}
+
+
+int LiveObjectList::GetTotalObjCountAndSize(int* size_p) {
+ int size = 0;
+ int count = 0;
+ LiveObjectList *lol = this;
+ do {
+ // Only compute total size if requested i.e. when size_p is not null.
+ if (size_p != NULL) {
+ Element* elements = lol->elements_;
+ for (int i = 0; i < lol->obj_count_; i++) {
+ HeapObject* heap_obj = elements[i].obj_;
+ size += heap_obj->Size();
+ }
+ }
+ count += lol->obj_count_;
+ lol = lol->prev_;
+ } while (lol != NULL);
+
+ if (size_p != NULL) {
+ *size_p = size;
+ }
+ return count;
+}
+
+
+// Adds an object to the lol.
+// Returns true if successful, else returns false.
+bool LiveObjectList::Add(HeapObject* obj) {
+ // If the object is already accounted for in the prev list which we
inherit
+ // from, then no need to add it to this list.
+ if ((prev() != NULL) && (prev()->Find(obj) != NULL)) {
+ return true;
+ }
+ ASSERT(obj_count_ <= capacity_);
+ if (obj_count_ == capacity_) {
+ // The heap must have grown and we have more objects than capacity to
store
+ // them.
+ return false; // Fail this addition.
+ }
+ Element& element = elements_[obj_count_++];
+ element.id_ = next_element_id_++;
+ element.obj_ = obj;
+ return true;
+}
+
+
+// Comparator used for sorting and searching the lol.
+int LiveObjectList::CompareElement(const Element* a, const Element* b) {
+ const HeapObject* obj1 = a->obj_;
+ const HeapObject* obj2 = b->obj_;
+ // For lol elements, it doesn't matter which comes first if 2 elements
point
+ // to the same object (which gets culled later). Hence, we only care
about
+ // the the greater than / less than relationships.
+ return (obj1 > obj2) ? 1 : (obj1 == obj2) ? 0 : -1;
+}
+
+
+// Looks for the specified object in the lol, and returns its element if
found.
+LiveObjectList::Element* LiveObjectList::Find(HeapObject* obj) {
+ LiveObjectList* lol = this;
+ Element key;
+ Element* result = NULL;
+
+ key.obj_ = obj;
+ // Iterate through the chain of lol's to look for the object.
+ while ((result == NULL) && (lol != NULL)) {
+ result = reinterpret_cast<Element*>(
+ bsearch(&key, lol->elements_, lol->obj_count_,
+ sizeof(Element),
+ reinterpret_cast<RawComparer>(CompareElement)));
+ lol = lol->prev_;
+ }
+ return result;
+}
+
+
+// "Nullifies" (convert the HeapObject* into an SMI) so that it will get
cleaned
+// up in the GCEpilogue, while preserving the sort order of the lol.
+// NOTE: the lols need to be already sorted before NullifyMostRecent() is
+// called.
+void LiveObjectList::NullifyMostRecent(HeapObject* obj) {
+ LiveObjectList* lol = last();
+ Element key;
+ Element* result = NULL;
+
+ key.obj_ = obj;
+ // Iterate through the chain of lol's to look for the object.
+ while (lol != NULL) {
+ result = reinterpret_cast<Element*>(
+ bsearch(&key, lol->elements_, lol->obj_count_,
+ sizeof(Element),
+ reinterpret_cast<RawComparer>(CompareElement)));
+ if (result != NULL) {
+ // Since there may be more than one (we are nullifying dup's after
all),
+ // find the first in the current lol, and nullify that. The lol
should
+ // be sorted already to make this easy (see the use of SortAll()).
+ int i = result - lol->elements_;
+
+ // NOTE: we sort the lol in increasing order. So, if an object has
been
+ // "nullified" (its lowest bit will be cleared to make it look like
an
+ // SMI), it would/should show up before the equivalent dups that
have not
+ // yet been "nullified". Hence, we should be searching backwards
for the
+ // first occurence of a matching object and nullify that instance.
This
+ // will ensure that we preserve the expected sorting order.
+ for (i--; i > 0; i--) {
***The diff for this file has been truncated for email.***
=======================================
--- /branches/bleeding_edge/src/liveobjectlist.h Thu Jan 20 00:11:53 2011
+++ /branches/bleeding_edge/src/liveobjectlist.h Wed Mar 2 01:16:05 2011
@@ -40,67 +40,277 @@
#ifdef LIVE_OBJECT_LIST
-
-// Temporary stubbed out LiveObjectList implementation.
+#ifdef DEBUG
+// The following symbol when defined enables thorough verification of lol
data.
+// FLAG_verify_lol will also need to set to true to enable the
verification.
+#define VERIFY_LOL
+#endif
+
+
+typedef int LiveObjectType;
+class LolFilter;
+class LiveObjectSummary;
+class DumpWriter;
+class SummaryWriter;
+
+
+// The LiveObjectList is both a mechanism for tracking a live capture of
+// objects in the JS heap, as well as is the data structure which
represents
+// each of those captures. Unlike a snapshot, the lol is live. For
example,
+// if an object in a captured lol dies and is collected by the GC, the lol
+// will reflect that the object is no longer available. The term
+// LiveObjectList (and lol) is used to describe both the mechanism and the
+// data structure depending on context of use.
+//
+// In captured lols, objects are tracked using their address and an object
id.
+// The object id is unique. Once assigned to an object, the object id can
never
+// be assigned to another object. That is unless all captured lols are
deleted
+// which allows the user to start over with a fresh set of lols and object
ids.
+// The uniqueness of the object ids allows the user to track specific
objects
+// and inspect its longevity while debugging JS code in execution.
+//
+// The lol comes with utility functions to capture, dump, summarize, and
diff
+// captured lols amongst other functionality. These functionality are
+// accessible via the v8 debugger interface.
class LiveObjectList {
public:
- inline static void GCEpilogue() {}
- inline static void GCPrologue() {}
- inline static void IterateElements(ObjectVisitor* v) {}
- inline static void ProcessNonLive(HeapObject *obj) {}
- inline static void UpdateReferencesForScavengeGC() {}
-
- static MaybeObject* Capture() { return Heap::undefined_value(); }
- static bool Delete(int id) { return false; }
+ inline static void GCEpilogue();
+ inline static void GCPrologue();
+ inline static void IterateElements(ObjectVisitor* v);
+ inline static void ProcessNonLive(HeapObject *obj);
+ inline static void UpdateReferencesForScavengeGC();
+
+ // Note: LOLs can be listed by calling Dump(0, <lol id>), and 2 LOLs can
be
+ // compared/diff'ed using Dump(<lol id1>, <lol id2>, ...). This will
yield
+ // a verbose dump of all the objects in the resultant lists.
+ // Similarly, a summarized result of a LOL listing or a diff can be
+ // attained using the Summarize(0, <lol id>) and Summarize(<lol id1,
+ // <lol id2>, ...) respectively.
+
+ static MaybeObject* Capture();
+ static bool Delete(int id);
static MaybeObject* Dump(int id1,
int id2,
int start_idx,
int dump_limit,
- Handle<JSObject> filter_obj) {
- return Heap::undefined_value();
- }
- static MaybeObject* Info(int start_idx, int dump_limit) {
- return Heap::undefined_value();
- }
- static MaybeObject* Summarize(int id1,
- int id2,
- Handle<JSObject> filter_obj) {
- return Heap::undefined_value();
- }
-
- static void Reset() {}
- static Object* GetObj(int obj_id) { return Heap::undefined_value(); }
- static Object* GetObjId(Handle<String> address) {
- return Heap::undefined_value();
- }
+ Handle<JSObject> filter_obj);
+ static MaybeObject* Info(int start_idx, int dump_limit);
+ static MaybeObject* Summarize(int id1, int id2, Handle<JSObject>
filter_obj);
+
+ static void Reset();
+ static Object* GetObj(int obj_id);
+ static int GetObjId(Object* obj);
+ static Object* GetObjId(Handle<String> address);
static MaybeObject* GetObjRetainers(int obj_id,
Handle<JSObject> instance_filter,
bool verbose,
int start,
int count,
- Handle<JSObject> filter_obj) {
- return Heap::undefined_value();
- }
+ Handle<JSObject> filter_obj);
static Object* GetPath(int obj_id1,
int obj_id2,
- Handle<JSObject> instance_filter) {
- return Heap::undefined_value();
- }
- static Object* PrintObj(int obj_id) { return Heap::undefined_value(); }
+ Handle<JSObject> instance_filter);
+ static Object* PrintObj(int obj_id);
+
+ private:
+
+ struct Element {
+ int id_;
+ HeapObject* obj_;
+ };
+
+ explicit LiveObjectList(LiveObjectList* prev, int capacity);
+ ~LiveObjectList();
+
+ static void GCEpiloguePrivate();
+ static void IterateElementsPrivate(ObjectVisitor* v);
+
+ static void DoProcessNonLive(HeapObject *obj);
+
+ static int CompareElement(const Element* a, const Element* b);
+
+ static Object* GetPathPrivate(HeapObject* obj1, HeapObject* obj2);
+
+ static int GetRetainers(Handle<HeapObject> target,
+ Handle<JSObject> instance_filter,
+ Handle<FixedArray> retainers_arr,
+ int start,
+ int dump_limit,
+ int* total_count,
+ LolFilter* filter,
+ LiveObjectSummary *summary,
+ JSFunction* arguments_function,
+ Handle<Object> error);
+
+ static MaybeObject* DumpPrivate(DumpWriter* writer,
+ int start,
+ int dump_limit,
+ LolFilter* filter);
+ static MaybeObject* SummarizePrivate(SummaryWriter* writer,
+ LolFilter* filter,
+ bool is_tracking_roots);
+
+ static bool NeedLOLProcessing() { return (last() != NULL); }
+ static void NullifyNonLivePointer(HeapObject **p) {
+ // Mask out the low bit that marks this as a heap object. We'll use
this
+ // cleared bit as an indicator that this pointer needs to be collected.
+ //
+ // Meanwhile, we still preserve its approximate value so that we don't
+ // have to resort the elements list all the time.
+ //
+ // Note: Doing so also makes this HeapObject* look like an SMI. Hence,
+ // GC pointer updater will ignore it when it gets scanned.
+ *p = reinterpret_cast<HeapObject*>((*p)->address());
+ }
+
+ LiveObjectList* prev() { return prev_; }
+ LiveObjectList* next() { return next_; }
+ int id() { return id_; }
+
+ static int list_count() { return list_count_; }
+ static LiveObjectList* last() { return last_; }
+
+ inline static LiveObjectList* FindLolForId(int id, LiveObjectList*
start_lol);
+ int TotalObjCount() { return GetTotalObjCountAndSize(NULL); }
+ int GetTotalObjCountAndSize(int* size_p);
+
+ bool Add(HeapObject* obj);
+ Element* Find(HeapObject* obj);
+ static void NullifyMostRecent(HeapObject* obj);
+ void Sort();
+ static void SortAll();
+
+ static void PurgeDuplicates(); // Only to be called by GCEpilogue.
+
+#ifdef VERIFY_LOL
+ static void Verify(bool match_heap_exactly = false);
+ static void VerifyNotInFromSpace();
+#endif
+
+ // Iterates the elements in every lol and returns the one that matches
the
+ // specified key. If no matching element is found, then it returns NULL.
+ template <typename T>
+ inline static LiveObjectList::Element*
+ FindElementFor(T (*GetValue)(LiveObjectList::Element*), T key);
+
+ inline static int GetElementId(Element* element);
+ inline static HeapObject* GetElementObj(Element* element);
+
+ // Instance fields.
+ LiveObjectList* prev_;
+ LiveObjectList* next_;
+ int id_;
+ int capacity_;
+ int obj_count_;
+ Element *elements_;
+
+ // Statics for managing all the lists.
+ static uint32_t next_element_id_;
+ static int list_count_;
+ static int last_id_;
+ static LiveObjectList* first_;
+ static LiveObjectList* last_;
+
+ friend class LolIterator;
+ friend class LolForwardIterator;
+ friend class LolDumpWriter;
+ friend class RetainersDumpWriter;
+ friend class RetainersSummaryWriter;
+ friend class UpdateLiveObjectListVisitor;
};
+// Helper class for updating the LiveObjectList HeapObject pointers.
+class UpdateLiveObjectListVisitor: public ObjectVisitor {
+ public:
+
+ void VisitPointer(Object** p) { UpdatePointer(p); }
+
+ void VisitPointers(Object** start, Object** end) {
+ // Copy all HeapObject pointers in [start, end).
+ for (Object** p = start; p < end; p++) UpdatePointer(p);
+ }
+
+ private:
+ // Based on Heap::ScavengeObject() but only does forwarding of pointers
+ // to live new space objects, and not actually keep them alive.
+ void UpdatePointer(Object** p) {
+ Object* object = *p;
+ if (!Heap::InNewSpace(object)) return;
+
+ HeapObject* heap_obj = HeapObject::cast(object);
+ ASSERT(Heap::InFromSpace(heap_obj));
+
+ // We use the first word (where the map pointer usually is) of a heap
+ // object to record the forwarding pointer. A forwarding pointer can
+ // point to an old space, the code space, or the to space of the new
+ // generation.
+ MapWord first_word = heap_obj->map_word();
+
+ // If the first word is a forwarding address, the object has already
been
+ // copied.
+ if (first_word.IsForwardingAddress()) {
+ *p = first_word.ToForwardingAddress();
+ return;
+
+ // Else, it's a dead object.
+ } else {
+
LiveObjectList::NullifyNonLivePointer(reinterpret_cast<HeapObject**>(p));
+ }
+ }
+};
+
+
#else // !LIVE_OBJECT_LIST
class LiveObjectList {
public:
- static void GCEpilogue() {}
- static void GCPrologue() {}
- static void IterateElements(ObjectVisitor* v) {}
- static void ProcessNonLive(HeapObject *obj) {}
- static void UpdateReferencesForScavengeGC() {}
+ inline static void GCEpilogue() {}
+ inline static void GCPrologue() {}
+ inline static void IterateElements(ObjectVisitor* v) {}
+ inline static void ProcessNonLive(HeapObject* obj) {}
+ inline static void UpdateReferencesForScavengeGC() {}
+
+ inline static MaybeObject* Capture() { return Heap::undefined_value(); }
+ inline static bool Delete(int id) { return false; }
+ inline static MaybeObject* Dump(int id1,
+ int id2,
+ int start_idx,
+ int dump_limit,
+ Handle<JSObject> filter_obj) {
+ return Heap::undefined_value();
+ }
+ inline static MaybeObject* Info(int start_idx, int dump_limit) {
+ return Heap::undefined_value();
+ }
+ inline static MaybeObject* Summarize(int id1,
+ int id2,
+ Handle<JSObject> filter_obj) {
+ return Heap::undefined_value();
+ }
+
+ inline static void Reset() {}
+ inline static Object* GetObj(int obj_id) { return
Heap::undefined_value(); }
+ inline static Object* GetObjId(Handle<String> address) {
+ return Heap::undefined_value();
+ }
+ inline static MaybeObject* GetObjRetainers(int obj_id,
+ Handle<JSObject>
instance_filter,
+ bool verbose,
+ int start,
+ int count,
+ Handle<JSObject> filter_obj) {
+ return Heap::undefined_value();
+ }
+
+ inline static Object* GetPath(int obj_id1,
+ int obj_id2,
+ Handle<JSObject> instance_filter) {
+ return Heap::undefined_value();
+ }
+ inline static Object* PrintObj(int obj_id) { return
Heap::undefined_value(); }
};
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev