basic legahy heap dumper
diff --git a/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.cpp b/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.cpp
index fecd919..8297b19 100644
--- a/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.cpp
+++ b/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.cpp
@@ -6,6 +6,7 @@
#include "AllocatorImpl.hpp"
#include "ThreadData.hpp"
+#include "ObjectTraversal.hpp"
using namespace kotlin;
@@ -72,3 +73,19 @@
auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData();
threadData->allocator().impl().extraObjectDataFactoryThreadQueue().DestroyExtraObjectData(extraObject);
}
+
+void alloc::Allocator::Impl::dumpHeap() noexcept {
+ graphviz::LogPrinter<kTagGC, logging::Level::kDebug> printer;
+ HeapDump heapDump(printer);
+
+ for (auto node : objectFactory_.LockForIter()) {
+ auto obj = node.GetObjHeader();
+
+ heapDump.object(obj);
+
+ traverseReferredObjects(obj, [&] (ObjHeader* referred){
+ heapDump.reference(obj, referred);
+ });
+ }
+}
+
diff --git a/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.hpp b/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.hpp
index b1e40ba..809f20b 100644
--- a/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.hpp
+++ b/kotlin-native/runtime/src/alloc/legacy/cpp/AllocatorImpl.hpp
@@ -16,6 +16,8 @@
#include "ObjectFactorySweep.hpp"
#include "Logging.hpp"
+#include "HeapDumper.hpp"
+
namespace kotlin::alloc {
struct ObjectFactoryTraits {
@@ -44,6 +46,8 @@
ObjectFactoryImpl& objectFactory() noexcept { return objectFactory_; }
ExtraObjectDataFactory& extraObjectDataFactory() noexcept { return extraObjectDataFactory_; }
+ void dumpHeap() noexcept;
+
private:
ObjectFactoryImpl objectFactory_;
ExtraObjectDataFactory extraObjectDataFactory_;
diff --git a/kotlin-native/runtime/src/alloc/legacy/cpp/HeapDumper.hpp b/kotlin-native/runtime/src/alloc/legacy/cpp/HeapDumper.hpp
new file mode 100644
index 0000000..7911597
--- /dev/null
+++ b/kotlin-native/runtime/src/alloc/legacy/cpp/HeapDumper.hpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
+ * that can be found in the LICENSE file.
+ */
+
+#pragma once
+
+#include <sstream>
+
+#include "Graphviz.hpp"
+#include "KString.h"
+
+namespace kotlin::alloc {
+
+template <typename Printer>
+class HeapDump {
+public:
+ explicit HeapDump(const Printer& printer) : printer_(printer) {}
+
+ void object(const ObjHeader* obj) {
+ auto objId = id(obj);
+ auto typeName = name(obj->type_info());
+
+ std::stringstream label;
+ label << objId << "[" << typeName << "]";
+
+ graph_.node(id(obj), label.str());
+
+ if (auto extraObj = mm::ExtraObjectData::Get(obj)) {
+ std::stringstream flags;
+ flags << "{";
+ if (extraObj->getFlag(mm::ExtraObjectData::FLAGS_FROZEN)) flags << "FROZEN,";
+ if (extraObj->getFlag(mm::ExtraObjectData::FLAGS_NEVER_FROZEN)) flags << "NEVER_FROZEN,";
+ if (extraObj->getFlag(mm::ExtraObjectData::FLAGS_IN_FINALIZER_QUEUE)) flags << "IN_FIN_Q,";
+ if (extraObj->getFlag(mm::ExtraObjectData::FLAGS_SWEEPABLE)) flags << "SWEEPABLE,";
+ if (extraObj->getFlag(mm::ExtraObjectData::FLAGS_RELEASE_ON_MAIN_QUEUE)) flags << "RELEASE_ON_MAIN,";
+ if (extraObj->getFlag(mm::ExtraObjectData::FLAGS_FINALIZED)) flags << "FINALIZED,";
+ flags << "}";
+
+
+ std::stringstream extraLabel;
+ extraLabel << id(extraObj) << "[EXTRA(" << id(obj) << ")\\nflags: " << flags.str() << "]";
+
+ graph_.node(id(extraObj), extraLabel.str());
+ };
+ }
+
+ void reference(const ObjHeader* from, const ObjHeader* to) {
+ graph_.edge(id(from), id(to));
+ }
+
+private:
+ template <typename T>
+ static std::string id(T* obj) {
+ std::stringstream stream;
+ stream << obj; // TOOD std::hex?
+ return stream.str();
+ }
+
+ static std::string name(const TypeInfo* type) {
+ if (type == nullptr) return "<unknown>";
+ char* cstr = CreateCStringFromString(type->relativeName_);
+ std::string str = cstr;
+ std::free(cstr);
+ return str;
+ }
+
+ Printer printer_;
+ graphviz::Graph<Printer> graph_{printer_, "Heap", true};
+};
+
+}
diff --git a/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp b/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp
index 34b02a4..f66b7e5 100644
--- a/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp
+++ b/kotlin-native/runtime/src/gc/pmcs/cpp/ParallelMarkConcurrentSweep.cpp
@@ -184,6 +184,8 @@
allocator_.prepareForGC();
#ifndef CUSTOM_ALLOCATOR
+ allocator_.impl().dumpHeap();
+
// Taking the locks before the pause is completed. So that any destroying thread
// would not publish into the global state at an unexpected time.
std::optional objectFactoryIterable = allocator_.impl().objectFactory().LockForIter();
diff --git a/kotlin-native/runtime/src/mm/cpp/Graphviz.hpp b/kotlin-native/runtime/src/mm/cpp/Graphviz.hpp
new file mode 100644
index 0000000..1e5b75b
--- /dev/null
+++ b/kotlin-native/runtime/src/mm/cpp/Graphviz.hpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
+ * that can be found in the LICENSE file.
+ */
+
+#pragma once
+
+#include <sstream>
+
+#include "Logging.hpp"
+#include "Utils.hpp"
+
+namespace kotlin::graphviz {
+
+template <typename Printer>
+class Graph : private Pinned {
+public:
+ Graph(const Printer& printer, std::string_view name, bool directed = true) : printer_(printer), directed_(directed) {
+ auto kind = directed_ ? "digraph" : "graph";
+ std::stringstream s;
+ s << kind << " " << name << " {";
+ printer_.println(s.str().data());
+ incIdent();
+ }
+
+ ~Graph() {
+ printer_.println("}");
+ }
+
+ void node(std::string_view id, std::string_view label) {
+ std::stringstream s;
+ s << ident() << id << "[label=\"" << label << "\"];";
+ printer_.println(s.str().data());
+ }
+
+ void edge(std::string_view fromId, std::string_view toId) {
+ std::stringstream s;
+ auto edgeOp = directed_ ? "->" : "--";
+ s << ident() << fromId << edgeOp << toId << ";";
+ printer_.println(s.str().data());
+ }
+private:
+ static constexpr int kIdentStep = 4;
+ void incIdent() {
+ ident_ += kIdentStep;
+ }
+ void decIdent() {
+ ident_ -= kIdentStep;
+ }
+ [[nodiscard]] std::string ident() const {
+ return std::string(ident_, ' ');
+ }
+
+ Printer printer_;
+ const bool directed_;
+ int ident_ = 0;
+};
+
+// FIXME move out of graphviz namespace
+template <logging::Tag kTag, logging::Level kLevel>
+struct LogPrinter {
+ void println(const char* str) {
+ RuntimeLog(kLevel, {kTag}, "%s", str);
+ }
+};
+
+}