| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #ifdef JS_GC_TRACE |
| |
| #include "gc/GCTrace.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include "gc/GCTraceFormat.h" |
| |
| #include "js/HashTable.h" |
| |
| using namespace js; |
| using namespace js::gc; |
| |
| JS_STATIC_ASSERT(AllocKinds == unsigned(AllocKind::LIMIT)); |
| JS_STATIC_ASSERT(LastObjectAllocKind == unsigned(AllocKind::OBJECT_LAST)); |
| |
| static FILE* gcTraceFile = nullptr; |
| |
| static HashSet<const Class*, DefaultHasher<const Class*>, SystemAllocPolicy> tracedClasses; |
| static HashSet<const ObjectGroup*, DefaultHasher<const ObjectGroup*>, SystemAllocPolicy> tracedGroups; |
| |
| static inline void |
| WriteWord(uint64_t data) |
| { |
| if (gcTraceFile) |
| fwrite(&data, sizeof(data), 1, gcTraceFile); |
| } |
| |
| static inline void |
| TraceEvent(GCTraceEvent event, uint64_t payload = 0, uint8_t extra = 0) |
| { |
| MOZ_ASSERT(event < GCTraceEventCount); |
| MOZ_ASSERT((payload >> TracePayloadBits) == 0); |
| WriteWord((uint64_t(event) << TraceEventShift) | |
| (uint64_t(extra) << TraceExtraShift) | payload); |
| } |
| |
| static inline void |
| TraceAddress(const void* p) |
| { |
| TraceEvent(TraceDataAddress, uint64_t(p)); |
| } |
| |
| static inline void |
| TraceInt(uint32_t data) |
| { |
| TraceEvent(TraceDataInt, data); |
| } |
| |
| static void |
| TraceString(const char* string) |
| { |
| JS_STATIC_ASSERT(sizeof(char) == 1); |
| |
| size_t length = strlen(string); |
| const unsigned charsPerWord = sizeof(uint64_t); |
| unsigned wordCount = (length + charsPerWord - 1) / charsPerWord; |
| |
| TraceEvent(TraceDataString, length); |
| for (unsigned i = 0; i < wordCount; ++i) { |
| union |
| { |
| uint64_t word; |
| char chars[charsPerWord]; |
| } data; |
| strncpy(data.chars, string + (i * charsPerWord), charsPerWord); |
| WriteWord(data.word); |
| } |
| } |
| |
| bool |
| js::gc::InitTrace(GCRuntime& gc) |
| { |
| /* This currently does not support multiple runtimes. */ |
| MOZ_ALWAYS_TRUE(!gcTraceFile); |
| |
| char* filename = js_sb_getenv("JS_GC_TRACE"); |
| if (!filename) |
| return true; |
| |
| if (!tracedClasses.init() || !tracedTypes.init()) { |
| FinishTrace(); |
| return false; |
| } |
| |
| gcTraceFile = fopen(filename, "w"); |
| if (!gcTraceFile) { |
| FinishTrace(); |
| return false; |
| } |
| |
| TraceEvent(TraceEventInit, 0, TraceFormatVersion); |
| |
| /* Trace information about thing sizes. */ |
| for (auto kind : AllAllocKinds()) |
| TraceEvent(TraceEventThingSize, Arena::thingSize(kind)); |
| |
| return true; |
| } |
| |
| void |
| js::gc::FinishTrace() |
| { |
| if (gcTraceFile) { |
| fclose(gcTraceFile); |
| gcTraceFile = nullptr; |
| } |
| tracedClasses.finish(); |
| tracedTypes.finish(); |
| } |
| |
| bool |
| js::gc::TraceEnabled() |
| { |
| return gcTraceFile != nullptr; |
| } |
| |
| void |
| js::gc::TraceNurseryAlloc(Cell* thing, size_t size) |
| { |
| if (thing) { |
| /* We don't have AllocKind here, but we can work it out from size. */ |
| unsigned slots = (size - sizeof(JSObject)) / sizeof(JS::Value); |
| AllocKind kind = GetBackgroundAllocKind(GetGCObjectKind(slots)); |
| TraceEvent(TraceEventNurseryAlloc, uint64_t(thing), kind); |
| } |
| } |
| |
| void |
| js::gc::TraceTenuredAlloc(Cell* thing, AllocKind kind) |
| { |
| if (thing) |
| TraceEvent(TraceEventTenuredAlloc, uint64_t(thing), kind); |
| } |
| |
| static void |
| MaybeTraceClass(const Class* clasp) |
| { |
| if (tracedClasses.has(clasp)) |
| return; |
| |
| TraceEvent(TraceEventClassInfo, uint64_t(clasp)); |
| TraceString(clasp->name); |
| TraceInt(clasp->flags); |
| TraceInt(clasp->finalize != nullptr); |
| |
| MOZ_ALWAYS_TRUE(tracedClasses.put(clasp)); |
| } |
| |
| static void |
| MaybeTraceGroup(ObjectGroup* group) |
| { |
| if (tracedGroups.has(group)) |
| return; |
| |
| MaybeTraceClass(group->clasp()); |
| TraceEvent(TraceEventGroupInfo, uint64_t(group)); |
| TraceAddress(group->clasp()); |
| TraceInt(group->flags()); |
| |
| MOZ_ALWAYS_TRUE(tracedGroups.put(group)); |
| } |
| |
| void |
| js::gc::TraceTypeNewScript(ObjectGroup* group) |
| { |
| const size_t bufLength = 128; |
| static char buffer[bufLength]; |
| MOZ_ASSERT(group->hasNewScript()); |
| JSAtom* funName = group->newScript()->fun->displayAtom(); |
| if (!funName) |
| return; |
| |
| size_t length = funName->length(); |
| MOZ_ALWAYS_TRUE(length < bufLength); |
| CopyChars(reinterpret_cast<Latin1Char*>(buffer), *funName); |
| buffer[length] = 0; |
| |
| TraceEvent(TraceEventTypeNewScript, uint64_t(group)); |
| TraceString(buffer); |
| } |
| |
| void |
| js::gc::TraceCreateObject(JSObject* object) |
| { |
| if (!gcTraceFile) |
| return; |
| |
| ObjectGroup* group = object->group(); |
| MaybeTraceGroup(group); |
| TraceEvent(TraceEventCreateObject, uint64_t(object)); |
| TraceAddress(group); |
| } |
| |
| void |
| js::gc::TraceMinorGCStart() |
| { |
| TraceEvent(TraceEventMinorGCStart); |
| } |
| |
| void |
| js::gc::TracePromoteToTenured(Cell* src, Cell* dst) |
| { |
| TraceEvent(TraceEventPromoteToTenured, uint64_t(src)); |
| TraceAddress(dst); |
| } |
| |
| void |
| js::gc::TraceMinorGCEnd() |
| { |
| TraceEvent(TraceEventMinorGCEnd); |
| } |
| |
| void |
| js::gc::TraceMajorGCStart() |
| { |
| TraceEvent(TraceEventMajorGCStart); |
| } |
| |
| void |
| js::gc::TraceTenuredFinalize(Cell* thing) |
| { |
| if (!gcTraceFile) |
| return; |
| if (thing->tenuredGetAllocKind() == AllocKind::OBJECT_GROUP) |
| tracedGroups.remove(static_cast<const ObjectGroup*>(thing)); |
| TraceEvent(TraceEventTenuredFinalize, uint64_t(thing)); |
| } |
| |
| void |
| js::gc::TraceMajorGCEnd() |
| { |
| TraceEvent(TraceEventMajorGCEnd); |
| } |
| |
| #endif |