From 4eccfdca18da858b6983971f75e42e3bf5b85d80 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 17 Oct 2024 15:28:17 +0200 Subject: [PATCH 1/5] Delay Global object creation --- src/rt/core.h | 53 ++++++++++++++++++++++++++----------- src/rt/objects/dyn_object.h | 8 +++--- src/rt/rt.cc | 10 +++---- tests/global_region.vpy | 3 +++ 4 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 tests/global_region.vpy diff --git a/src/rt/core.h b/src/rt/core.h index d3c86e5..076a476 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -1,16 +1,17 @@ #include "objects/dyn_object.h" #include "rt.h" + + namespace rt::core { - class PrototypeObject : public objects::DynObject { std::string name; public: PrototypeObject(std::string name_, objects::DynObject* prototype = nullptr) - : objects::DynObject(prototype, true), name(name_) + : objects::DynObject(prototype), name(name_) {} std::string get_name() @@ -21,15 +22,18 @@ namespace rt::core } }; - PrototypeObject framePrototypeObject{"Frame"}; + PrototypeObject* framePrototypeObject() { + static PrototypeObject* proto = new PrototypeObject("Frame"); + return proto; + } class FrameObject : public objects::DynObject { - FrameObject() : objects::DynObject(&framePrototypeObject, true) {} + FrameObject() : objects::DynObject(framePrototypeObject(), true) {} public: FrameObject(objects::DynObject* parent_frame) - : objects::DynObject(&framePrototypeObject) + : objects::DynObject(framePrototypeObject()) { if (parent_frame) { @@ -45,9 +49,14 @@ namespace rt::core } }; - PrototypeObject funcPrototypeObject{"Function"}; - PrototypeObject bytecodeFuncPrototypeObject{ - "BytecodeFunction", &funcPrototypeObject}; + PrototypeObject* funcPrototypeObject() { + static PrototypeObject* proto = new PrototypeObject("Function"); + return proto; + } + PrototypeObject* bytecodeFuncPrototypeObject() { + static PrototypeObject* proto = new PrototypeObject("BytecodeFunction", funcPrototypeObject()); + return proto; + } class FuncObject : public objects::DynObject { @@ -63,7 +72,7 @@ namespace rt::core public: BytecodeFuncObject(verona::interpreter::Bytecode* body_) - : FuncObject(&bytecodeFuncPrototypeObject), body(body_) + : FuncObject(bytecodeFuncPrototypeObject()), body(body_) {} ~BytecodeFuncObject() { @@ -77,7 +86,11 @@ namespace rt::core } }; - PrototypeObject stringPrototypeObject{"String"}; + PrototypeObject* stringPrototypeObject() { + static PrototypeObject* proto = new PrototypeObject("String"); + return proto; + } + class StringObject : public objects::DynObject { @@ -85,7 +98,7 @@ namespace rt::core public: StringObject(std::string value_, bool global = false) - : objects::DynObject(&stringPrototypeObject, global), value(value_) + : objects::DynObject(stringPrototypeObject(), global), value(value_) {} std::string get_name() @@ -106,12 +119,20 @@ namespace rt::core } }; - StringObject TrueObject{"True", true}; - StringObject FalseObject{"False", true}; + StringObject* trueObject() { + static StringObject* val = new StringObject("True", true); + return val; + } + StringObject* falseObject() { + static StringObject* val = new StringObject("False", true); + return val; + } // The prototype object for iterators - // TODO put some stuff in here? - PrototypeObject keyIterPrototypeObject{"KeyIterator"}; + PrototypeObject* keyIterPrototypeObject() { + static PrototypeObject* proto = new PrototypeObject("KeyIterator"); + return proto; + } class KeyIterObject : public objects::DynObject { @@ -120,7 +141,7 @@ namespace rt::core public: KeyIterObject(std::map& fields) - : objects::DynObject(&keyIterPrototypeObject), + : objects::DynObject(keyIterPrototypeObject()), iter(fields.begin()), iter_end(fields.end()) {} diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 5306e53..5f8ee8e 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -170,20 +170,20 @@ namespace rt::objects public: // prototype is borrowed, the caller does not need to provide an RC. - DynObject(DynObject* prototype_ = nullptr, bool global = false) + DynObject(DynObject* prototype_ = nullptr, bool first_frame = false) : prototype(prototype_) { - if (!global) - { + if (!first_frame) { count++; all_objects.insert(this); auto local_region = get_local_region(); region = local_region; local_region->objects.insert(this); } + if (prototype != nullptr) prototype->change_rc(1); - std::cout << "Allocate: " << get_name() << std::endl; + std::cout << "Allocate: " << this << std::endl; } // TODO This should use prototype lookup for the destructor. diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 439e87a..6259d70 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -54,7 +54,7 @@ namespace rt { // TODO Add some checking. This is need to lookup the correct function in // the prototype chain. - if (key->get_prototype() != &core::stringPrototypeObject) + if (key->get_prototype() != core::stringPrototypeObject()) { ui::error("Key must be a string."); } @@ -96,11 +96,11 @@ namespace rt objects::DynObject* get_true() { - return &core::TrueObject; + return core::trueObject(); } objects::DynObject* get_false() { - return &core::FalseObject; + return core::falseObject(); } void add_reference(objects::DynObject* src, objects::DynObject* target) @@ -168,7 +168,7 @@ namespace rt objects::DynObject* iter_next(objects::DynObject* iter) { assert(!iter->is_immutable()); - if (iter->get_prototype() != &core::keyIterPrototypeObject) + if (iter->get_prototype() != core::keyIterPrototypeObject()) { ui::error("Object is not an iterator."); } @@ -178,7 +178,7 @@ namespace rt verona::interpreter::Bytecode* get_bytecode(objects::DynObject* func) { - if (func->get_prototype() == &core::bytecodeFuncPrototypeObject) + if (func->get_prototype() == core::bytecodeFuncPrototypeObject()) { return reinterpret_cast(func)->get_bytecode(); } diff --git a/tests/global_region.vpy b/tests/global_region.vpy new file mode 100644 index 0000000..74d6d14 --- /dev/null +++ b/tests/global_region.vpy @@ -0,0 +1,3 @@ +str = "example" + +str["__proto__"].dummy = {} From 244bbba7b51a1621d3edc9888ba6f5a18dace467 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 17 Oct 2024 15:45:00 +0200 Subject: [PATCH 2/5] Mermaid: Draw global objects as reachable --- src/lang/interpreter.cc | 8 +++--- src/rt/core.h | 50 ++++++++++++++++++++++++++----------- src/rt/objects/dyn_object.h | 2 +- src/rt/rt.cc | 18 ++++++------- src/rt/ui.h | 4 +-- src/rt/ui/mermaid.cc | 12 ++++++--- 6 files changed, 59 insertions(+), 35 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 631f3c7..e30992b 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -125,8 +125,8 @@ namespace verona::interpreter std::cout << node->location().view() << std::endl << std::endl; // Mermaid output - std::vector edges{{nullptr, "?", frame()}}; - ui->output(edges, std::string(node->location().view())); + std::vector roots{frame()}; + ui->output(roots, std::string(node->location().view())); // Continue return ExecNext{}; @@ -464,12 +464,12 @@ namespace verona::interpreter out.open(path); } - void output(std::vector& edges, std::string message) + void output(std::vector& roots, std::string message) { out << "```" << std::endl; out << message << std::endl; out << "```" << std::endl; - rt::ui::mermaid(edges, out); + rt::ui::mermaid(roots, out); if (interactive) { out.close(); diff --git a/src/rt/core.h b/src/rt/core.h index 076a476..8d8959f 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -1,8 +1,6 @@ #include "objects/dyn_object.h" #include "rt.h" - - namespace rt::core { class PrototypeObject : public objects::DynObject @@ -22,7 +20,8 @@ namespace rt::core } }; - PrototypeObject* framePrototypeObject() { + inline PrototypeObject* framePrototypeObject() + { static PrototypeObject* proto = new PrototypeObject("Frame"); return proto; } @@ -49,12 +48,15 @@ namespace rt::core } }; - PrototypeObject* funcPrototypeObject() { + inline PrototypeObject* funcPrototypeObject() + { static PrototypeObject* proto = new PrototypeObject("Function"); return proto; } - PrototypeObject* bytecodeFuncPrototypeObject() { - static PrototypeObject* proto = new PrototypeObject("BytecodeFunction", funcPrototypeObject()); + inline PrototypeObject* bytecodeFuncPrototypeObject() + { + static PrototypeObject* proto = + new PrototypeObject("BytecodeFunction", funcPrototypeObject()); return proto; } @@ -86,19 +88,19 @@ namespace rt::core } }; - PrototypeObject* stringPrototypeObject() { + inline PrototypeObject* stringPrototypeObject() + { static PrototypeObject* proto = new PrototypeObject("String"); return proto; } - class StringObject : public objects::DynObject { std::string value; public: - StringObject(std::string value_, bool global = false) - : objects::DynObject(stringPrototypeObject(), global), value(value_) + StringObject(std::string value_) + : objects::DynObject(stringPrototypeObject()), value(value_) {} std::string get_name() @@ -119,17 +121,20 @@ namespace rt::core } }; - StringObject* trueObject() { - static StringObject* val = new StringObject("True", true); + inline StringObject* trueObject() + { + static StringObject* val = new StringObject("True"); return val; } - StringObject* falseObject() { - static StringObject* val = new StringObject("False", true); + inline StringObject* falseObject() + { + static StringObject* val = new StringObject("False"); return val; } // The prototype object for iterators - PrototypeObject* keyIterPrototypeObject() { + inline PrototypeObject* keyIterPrototypeObject() + { static PrototypeObject* proto = new PrototypeObject("KeyIterator"); return proto; } @@ -168,4 +173,19 @@ namespace rt::core return this; } }; + + inline std::vector* globals() + { + static std::vector* globals = + new std::vector{ + framePrototypeObject(), + funcPrototypeObject(), + bytecodeFuncPrototypeObject(), + stringPrototypeObject(), + keyIterPrototypeObject(), + trueObject(), + falseObject(), + }; + return globals; + } } // namespace rt::core diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 5f8ee8e..f56f048 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -28,7 +28,7 @@ namespace rt::objects friend class Reference; friend objects::DynObject* rt::make_iter(objects::DynObject* obj); friend void - rt::ui::mermaid(std::vector& roots, std::ostream& out); + rt::ui::mermaid(std::vector& roots, std::ostream& out); friend void destruct(DynObject* obj); friend void dealloc(DynObject* obj); template diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 6259d70..b1b4b5a 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -123,6 +123,9 @@ namespace rt size_t pre_run() { + std::cout << "Initilizing global objects" << std::endl; + core::globals(); + std::cout << "Running test..." << std::endl; return objects::DynObject::get_count(); } @@ -134,13 +137,8 @@ namespace rt if (objects::DynObject::get_count() != initial_count) { std::cout << "Cycles detected in local region." << std::endl; - auto objs = objects::DynObject::get_local_region()->get_objects(); - std::vector edges; - for (auto obj : objs) - { - edges.push_back({nullptr, "?", obj}); - } - ui.output(edges, "Cycles detected in local region."); + auto roots = objects::DynObject::get_local_region()->get_objects(); + ui.output(roots, "Cycles detected in local region."); } objects::DynObject::get_local_region()->terminate_region(); if (objects::DynObject::get_count() != initial_count) @@ -150,12 +148,12 @@ namespace rt std::cout << "Final count: " << objects::DynObject::get_count() << std::endl; - std::vector edges; + std::vector roots; for (auto obj : objects::DynObject::get_objects()) { - edges.push_back({nullptr, "?", obj}); + roots.push_back(obj); } - ui.output(edges, "Memory leak detected!"); + ui.output(roots, "Memory leak detected!"); std::exit(1); } diff --git a/src/rt/ui.h b/src/rt/ui.h index cb81967..18a824e 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -12,10 +12,10 @@ namespace rt::ui class UI { public: - virtual void output(std::vector&, std::string) {} + virtual void output(std::vector&, std::string) {} }; - void mermaid(std::vector& roots, std::ostream& out); + void mermaid(std::vector& roots, std::ostream& out); [[noreturn]] inline void error(const std::string& msg) { diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 809b20c..b9a4dff 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -1,5 +1,6 @@ #include "../objects/dyn_object.h" #include "../ui.h" +#include "../core.h" #include #include @@ -30,7 +31,7 @@ namespace rt::ui return text; } - void mermaid(std::vector& roots, std::ostream& out) + void mermaid(std::vector& roots, std::ostream& out) { // Give a nice id to each object. std::map visited; @@ -85,10 +86,15 @@ namespace rt::ui } return true; }; - // Output all reachable edges + // Output all reachable nodes for (auto& root : roots) { - objects::visit({root.src, root.key, root.target}, explore); + objects::visit(root, explore); + } + auto globals = core::globals(); + for (auto& root : *globals) + { + objects::visit(root, explore); } // Output the unreachable parts of the graph From 44dcc92452a36965bd4dfacfc98a546d7a6c6823 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 17 Oct 2024 15:53:01 +0200 Subject: [PATCH 3/5] Mermaid: Only draw global objects when they are used --- src/rt/core.h | 6 +++--- src/rt/objects/dyn_object.h | 3 ++- src/rt/ui/mermaid.cc | 11 +++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/rt/core.h b/src/rt/core.h index 8d8959f..e4acfc0 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -174,10 +174,10 @@ namespace rt::core } }; - inline std::vector* globals() + inline std::set* globals() { - static std::vector* globals = - new std::vector{ + static std::set* globals = + new std::set{ framePrototypeObject(), funcPrototypeObject(), bytecodeFuncPrototypeObject(), diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index f56f048..73e0fff 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -173,7 +173,8 @@ namespace rt::objects DynObject(DynObject* prototype_ = nullptr, bool first_frame = false) : prototype(prototype_) { - if (!first_frame) { + if (!first_frame) + { count++; all_objects.insert(this); auto local_region = get_local_region(); diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index b9a4dff..29a432b 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -1,6 +1,6 @@ +#include "../core.h" #include "../objects/dyn_object.h" #include "../ui.h" -#include "../core.h" #include #include @@ -57,6 +57,10 @@ namespace rt::ui objects::DynObject* dst = e.target; std::string key = e.key; objects::DynObject* src = e.src; + if (unreachable && core::globals()->contains(dst)) + { + return false; + } if (src != nullptr) { out << " id" << visited[src] << " -->|" << escape(key) << "| "; @@ -91,11 +95,6 @@ namespace rt::ui { objects::visit(root, explore); } - auto globals = core::globals(); - for (auto& root : *globals) - { - objects::visit(root, explore); - } // Output the unreachable parts of the graph unreachable = true; From 00f3249bb2953bd55b81a1fc7ff02eef9dbce679 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 17 Oct 2024 17:14:17 +0200 Subject: [PATCH 4/5] Memory: Freeze global objects to check memory usage --- src/lang/interpreter.cc | 3 ++- src/rt/objects/dyn_object.h | 5 ++++- src/rt/rt.cc | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index e30992b..894cbd4 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -464,7 +464,8 @@ namespace verona::interpreter out.open(path); } - void output(std::vector& roots, std::string message) + void + output(std::vector& roots, std::string message) { out << "```" << std::endl; out << message << std::endl; diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 73e0fff..ac6e2b5 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -199,7 +199,10 @@ namespace rt::objects // RC is zero. if (change_rc(0) != 0 && matched != 0) { - ui::error("Object still has references"); + std::stringstream stream; + stream << this; + stream << " still has references"; + ui::error(stream.str()); } auto r = get_region(this); diff --git a/src/rt/rt.cc b/src/rt/rt.cc index b1b4b5a..6c46386 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -3,7 +3,9 @@ #include "core.h" #include "objects/dyn_object.h" +#include #include +#include #include #include @@ -134,12 +136,27 @@ namespace rt { std::cout << "Test complete - checking for cycles in local region..." << std::endl; + auto globals = core::globals(); if (objects::DynObject::get_count() != initial_count) { std::cout << "Cycles detected in local region." << std::endl; auto roots = objects::DynObject::get_local_region()->get_objects(); + roots.erase( + std::remove_if( + roots.begin(), + roots.end(), + [&globals](auto x) { return globals->contains(x); }), + roots.end()); ui.output(roots, "Cycles detected in local region."); } + + // Freeze global objects, to low the termination of the local region + std::cout << "Freezing global objects" << std::endl; + for (auto obj : *globals) + { + obj->freeze(); + } + objects::DynObject::get_local_region()->terminate_region(); if (objects::DynObject::get_count() != initial_count) { From e47135153ba851e9b5ad72c649b603e02bd4c301 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 17 Oct 2024 17:17:56 +0200 Subject: [PATCH 5/5] Leek memory test --- CMakeLists.txt | 1 + tests/{global_region.vpy => leak_with_global.vpy} | 0 2 files changed, 1 insertion(+) rename tests/{global_region.vpy => leak_with_global.vpy} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3910a28..c2d269c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,3 +61,4 @@ foreach(FILE ${ALL_FILES}) endforeach() set_property(TEST three_regions.vpy PROPERTY WILL_FAIL true) +set_property(TEST leak_with_global.vpy PROPERTY WILL_FAIL true) diff --git a/tests/global_region.vpy b/tests/leak_with_global.vpy similarity index 100% rename from tests/global_region.vpy rename to tests/leak_with_global.vpy