From 5ddaa48eeca9c9e401ba63ef040c926bdd33199a Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 3 Mar 2025 16:07:32 +0100 Subject: [PATCH 01/23] Trieste: Desugar `when:` --- src/lang/lang.h | 1 + src/lang/passes/call_stmts.cc | 15 +++++++++++-- src/lang/passes/flatten.cc | 6 ++++- src/lang/passes/grouping.cc | 41 ++++++++++++++++++++++++++++++++++- src/lang/passes/parse.cc | 11 ++++++++-- src/rt/core/builtin.cc | 18 +++++++++++++++ tests/exprs/when.frank | 17 +++++++++++++++ 7 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 tests/exprs/when.frank diff --git a/src/lang/lang.h b/src/lang/lang.h index b475c22..8c2b65c 100644 --- a/src/lang/lang.h +++ b/src/lang/lang.h @@ -20,6 +20,7 @@ inline const TokenDef Move{"move"}; inline const TokenDef Lookup{"lookup"}; inline const TokenDef Parens{"parens"}; inline const TokenDef Method{"method"}; +inline const TokenDef When{"when"}; inline const TokenDef Op{"op"}; inline const TokenDef Rhs{"rhs"}; diff --git a/src/lang/passes/call_stmts.cc b/src/lang/passes/call_stmts.cc index 2d53166..fbbd3a8 100644 --- a/src/lang/passes/call_stmts.cc +++ b/src/lang/passes/call_stmts.cc @@ -17,9 +17,20 @@ PassDef call_stmts() verona::wf::call_stmts, dir::bottomup | dir::once, { - In(Block) * T(Call, Method)[Call] >> + In(Block) * (T(Call)[Call] << T(Ident)[Ident]) >> [](auto& _) { - return Seq << _(Call) << ClearStack << create_print(_(Call)); + if (_(Ident)->location().view() == "spawn_behavior") + { + return Seq << _(Call) << create_print(_(Call), "Spawning Behavior"); + } + else + { + return Seq << _(Call) << ClearStack << create_print(_(Call)); + } + }, + In(Block) * T(Method)[Method] >> + [](auto& _) { + return Seq << _(Method) << ClearStack << create_print(_(Method)); }, }}; } diff --git a/src/lang/passes/flatten.cc b/src/lang/passes/flatten.cc index 04b83b6..17e4e38 100644 --- a/src/lang/passes/flatten.cc +++ b/src/lang/passes/flatten.cc @@ -171,10 +171,14 @@ PassDef flatten() } body << create_print(_(Func), func_head + " (Exit)"); + auto def_text = func_head; + if (_(Ident)->location().view().starts_with("__when_")) { + def_text = std::string("Creating behavior from: ") + func_head; + } // Function cleanup return Seq << (CreateObject << (Func << (Compile << body))) << (StoreFrame ^ _(Ident)) - << create_print(_(Func), func_head); + << create_print(_(Func), def_text); }, }}; } diff --git a/src/lang/passes/grouping.cc b/src/lang/passes/grouping.cc index e96810b..c6700a6 100644 --- a/src/lang/passes/grouping.cc +++ b/src/lang/passes/grouping.cc @@ -2,6 +2,14 @@ inline const TokenDef Rest{"rest"}; +int g_when_counter = 0; + +std::string new_when_ident() +{ + g_when_counter += 1; + return "__when_" + std::to_string(g_when_counter); +} + PassDef grouping() { PassDef p{ @@ -57,6 +65,37 @@ PassDef grouping() return create_from(Method, _(Group)) << _(Lookup) << list; }, + T(When)[When] << (T(Group)[Empty] * T(Group)[Block] * End) >> + [](auto& _) { + return create_from(When, _(When)) + << _(Empty) << (Group << Parens) << _(Block); + }, + T(When)[When] + << ((T(Group) << End) * + (T(Group) + << (T(Parens)[Parens] << ((~(T(List) << T(Ident)++[List]))))) * + (T(Group) << T(Block)[Block])) >> + [](auto& _) { + auto when_name = new_when_ident(); + + // ===================================== + // Define `__when_X()` function + auto when_def = create_from(Func, _(When)) + << (Ident ^ when_name) + << (create_from(Params, _(Parens)) << clone(_[List])) + << (Body << _(Block)); + + // ===================================== + // Call `spawn_behavior()` + auto list = create_from(List, _(Parens)) + << (Ident ^ when_name) << clone(_[List]); + auto call = create_from(Call, _(When)) + << (Ident ^ "spawn_behavior") << list; + + // Put it all together + return Seq << when_def << call; + }, + T(Assign) << ((T(Group) << LV[Lhs] * End) * ((T(Group) << (RV[Rhs] * End)) / (RV[Rhs] * End)) * End) >> @@ -109,7 +148,7 @@ PassDef grouping() << ((T(Group) << End) * (T(Group) << ((T(Ident)[Ident]) * - (T(Parens)[Parens] << (~(T(List) << T(Ident)++[List]))) * + (T(Parens)[Parens] << ((~T(List) << T(Ident)++[List]))) * End)) * (T(Group) << T(Block)[Block]) * End) >> [](auto& _) { diff --git a/src/lang/passes/parse.cc b/src/lang/passes/parse.cc index 0d6475a..ab357fa 100644 --- a/src/lang/passes/parse.cc +++ b/src/lang/passes/parse.cc @@ -7,14 +7,14 @@ namespace verona::wf inline const auto parse_tokens = Ident | Lookup | Empty | Drop | Move | Null | String | Parens; inline const auto parse_groups = - Group | Assign | If | Else | Block | For | Func | List | Return | While; + Group | Assign | If | Else | Block | For | Func | List | Return | While | When; inline const auto parser = (Top <<= File) | (File <<= parse_groups++) | (Assign <<= Group * (Lhs >>= (Group | cond))) | (If <<= Group * (Op >>= (cond | Group)) * Group) | (Else <<= Group * Group) | (Group <<= (parse_tokens | Block | List)++) | (Block <<= (parse_tokens | parse_groups)++) | (Eq <<= Group * Group) | - (Neq <<= Group * Group) | (Lookup <<= Group) | + (Neq <<= Group * Group) | (Lookup <<= Group) | (When <<= Group++) | (For <<= Group * List * Group * Group) | (While <<= Group * (Op >>= (cond | Group)) * Group) | (List <<= Group++) | (Parens <<= (Group | List)++) | (Func <<= Group * Group * Group) | @@ -96,6 +96,9 @@ trieste::Parse parser() "(?:#[^\\n\\r]*)" >> [](auto&) {}, "def\\b" >> [](auto& m) { m.seq(Func); }, + "when\\b" >> [](auto& m) { + m.seq(When); + }, "\\(" >> [](auto& m) { m.push(Parens); }, "\\)" >> [](auto& m) { @@ -149,6 +152,10 @@ trieste::Parse parser() { toc = While; } + else if (m.in(When)) + { + toc = When; + } else { m.error("unexpected colon"); diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index fb28da2..c1bc6f8 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -390,11 +390,29 @@ namespace rt::core }); } + void concurrency_builtins() + { + add_builtin("spawn_behavior", [](auto frame, auto args) { + std::cout << "Yay, what a day to live :D" << std::endl; + std::cout << "Arguments: " << args << std::endl; + + for (int i = 0; i < args; i++) + { + auto value = frame->stack_pop("argument"); + std::cout << " - " << value << std::endl; + rt::remove_reference(frame->object(), value); + } + + return std::nullopt; + }); + } + void init_builtins(ui::UI* ui) { mermaid_builtins(ui); ctor_builtins(); action_builtins(); pragma_builtins(); + concurrency_builtins(); } } diff --git a/tests/exprs/when.frank b/tests/exprs/when.frank new file mode 100644 index 0000000..a8721d4 --- /dev/null +++ b/tests/exprs/when.frank @@ -0,0 +1,17 @@ +# For debugging: +# mermaid_show_functions() + +c1 = Cown(Region()) +c2 = Cown(Region()) + +when (c1): + r = c1 + +when (c1, c2): + r = c1 + +when (): + r = c1 + +when: + r = None \ No newline at end of file From a085bfa1d193f591cc4683251e3c94ebf2454c53 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 3 Mar 2025 17:08:46 +0100 Subject: [PATCH 02/23] `spawn_behavior()` impl plan --- src/rt/core/builtin.cc | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index c1bc6f8..1d8f542 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -394,14 +394,37 @@ namespace rt::core { add_builtin("spawn_behavior", [](auto frame, auto args) { std::cout << "Yay, what a day to live :D" << std::endl; - std::cout << "Arguments: " << args << std::endl; - - for (int i = 0; i < args; i++) - { - auto value = frame->stack_pop("argument"); - std::cout << " - " << value << std::endl; - rt::remove_reference(frame->object(), value); - } + + // cowns (Stored on the stack in reverse order) + // -1 since the first argument is the actual behavior + std::vector cowns = {}; + for (int i = 0; i < args - 1; i++) + { + auto value = frame->stack_pop("cown"); + cowns.push_back(value); + } + // when + auto behavior = frame->stack_pop("behavior"); + + // 1. Create `Behavior` (ByteCodeFunc, [Cowns]) object + // 2. Inform Sceduler about `Behavior` + // + // In sceduler: + // 3. Sceduler waits until all cowns are available (dependency graph) + // - The "draw the rest of the owl step" + // 4. Create a new interpreter from `bytecode.value()->body` + // - `rt::move_reference(NULL, new_interpreter->frame(), body)` + // 5. Aquire the cowns (Set them to aquired with this interpreter id) + // 6. Push the cowns on to the interpreter frame + // - `rt::move_reference(NULL, new_interpreter->frame(), cown);` + // 7. Start interpreter + // + // In interpreter (Already done by the lowering pass): + // 8. Cowns from the stack are assigned to the defined names on the frame + // + // After completion in sceduler + // 9. Release cowns + // 10. `rt::remove_reference` the `behavior.func` return std::nullopt; }); From d161ac254df84db98623fcb7a18c73064dab7500 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 21 Mar 2025 18:11:27 +0100 Subject: [PATCH 03/23] C++ sucks --- src/lang/interpreter.cc | 144 +++++++++++++++++++++++++++++++++++- src/lang/interpreter.h | 56 ++++++++++++++ src/lang/passes/grouping.cc | 2 +- src/lang/passes/parse.cc | 8 +- src/rt/core.h | 14 ++++ src/rt/core/builtin.cc | 10 +-- src/rt/objects/region.cc | 8 +- src/rt/rt.cc | 29 +++++++- src/rt/rt.h | 7 ++ tests/example_1.frank | 20 ++++- 10 files changed, 276 insertions(+), 22 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 375e09c..f39dc57 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -1,3 +1,5 @@ +#include "interpreter.h" + #include "../rt/rt.h" #include "bytecode.h" #include "trieste/trieste.h" @@ -560,10 +562,148 @@ namespace verona::interpreter size_t initial = rt::pre_run(ui); - Interpreter inter(ui); - inter.run(main_body); + // Interpreter inter(ui); + // inter.run(main_body); + Scheduler s; + s.start(new Bytecode{main_body}); rt::post_run(initial, ui); } + int Behavior::s_behavior_counter = 0; + + std::string Behavior::name() + { + std::stringstream ss; + ss << "Behavior_" << this->id; + return ss.str(); + } + + Bytecode* Behavior::spawn() + { + assert(!this->is_complete); + + for (auto c : this->cowns) + { + rt::aquire_cown(c); + } + + return rt::try_get_bytecode(this->code).value(); + } + + void Behavior::complete() + { + this->is_complete = true; + rt::remove_reference(nullptr, this->code); + this->code = nullptr; + + for (auto c : this->cowns) + { + rt::release_cown(c); + rt::remove_reference(nullptr, c); + } + this->cowns.clear(); + } + + void Scheduler::add(std::shared_ptr behavior) + { + bool is_ready = true; + + for (auto cown : behavior->cowns) + { + // Get the last behavior that is waiting on the cown + auto pending = cowns[cown]; + // If a behavior is pending, set the successor + if (pending && !pending->is_complete) + { + is_ready = false; + pending->succ = behavior; + } + // Update pointer to the last pending behavior + cowns[cown] = behavior; + } + + if (is_ready) + { + this->ready.push_back(behavior); + std::cout << "New behavior `" << behavior->name() << "` is ready" + << std::endl; + } + else + { + std::cout << "New behavior `" << behavior->name() << "` is pending" + << std::endl; + } + } + + void Scheduler::start(Bytecode* main_block) + { + auto main_function = rt::make_func(main_block); + // Hack: Needed to keep the main function alive. Otherwise, it'll be freed thereby + // also deleting the trieste nodes. + rt::hack_inc_rc(main_function); + // :notes: I imagine a world without ugly c++ :notes: + auto behavior = std::make_shared( + main_function, std::vector{}); + // Seriously, why do we use this language? The memory problems I currently + // have could easly be avoided. + while (behavior) + { + auto block = behavior->spawn(); + + Interpreter inter(rt::ui::globalUI()); + inter.run(block->body); + + behavior->complete(); + + behavior = this->get_next(); + } + + rt::remove_reference(nullptr, main_function); + } + + std::shared_ptr Scheduler::get_next() + { + if (this->ready.empty()) + { + return nullptr; + } + + // I hate c and c++ `unsigned` soo much... At least I'm getting paid to deal + // with this s... *suboptimal* language + unsigned int selected = 0; + while (true) + { + // Promt the user: + std::cout << "Available behaviors:" << std::endl; + for (unsigned int idx = 0; idx < this->ready.size(); idx += 1) + { + std::cout << "- " << idx << ": " << this->ready[idx]->name() + << std::endl; + } + + // Get user input + std::cout << "> "; + std::string line; + std::getline(std::cin, line); + std::istringstream iss(line); + + int n = 0; + if (iss >> n) + { + selected = n; + + // Sanity checks and preventing undefined behavior. + if (selected < this->ready.size()) + { + break; + } + } + } + + auto removed = this->ready[selected]; + this->ready.erase(this->ready.begin() + selected); + return removed; + } + } // namespace verona::interpreter diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 8edd2ab..199340e 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -1,6 +1,9 @@ #pragma once #include +#include +#include +#include namespace rt::objects { @@ -33,4 +36,57 @@ namespace verona::interpreter return this->get_stack_size() == 0; } }; + + class Behavior + { + // Static member for naming + static int s_behavior_counter; + + int id; + + public: + // Instance members to describe the behavior + std::vector cowns; + // This uses a function object opposed to a Bytecode* to not leak memory + rt::objects::DynObject* code; + std::shared_ptr succ = nullptr; + bool is_complete = false; + + Behavior( + rt::objects::DynObject* code_, + std::vector cowns_) + : cowns(cowns_), code(code_) + { + id = s_behavior_counter; + s_behavior_counter += 1; + } + + std::string name(); + + Bytecode* spawn(); + // This completes the behavior by releasing all cowns + // decreffing all held objects and informing its successor. + void complete(); + }; + + class Scheduler + { + // All behaviors that are ready to run + std::vector> ready; + // A map from cowns to the last behavior that is waiting on them. + // + // The cowns in the key are weak pointers, they should never be + // dereferenced. + std::unordered_map> + cowns; + + public: + void add(std::shared_ptr behavior); + + void start(Bytecode* main); + + private: + std::shared_ptr get_next(); + + }; } diff --git a/src/lang/passes/grouping.cc b/src/lang/passes/grouping.cc index c6700a6..5cb3712 100644 --- a/src/lang/passes/grouping.cc +++ b/src/lang/passes/grouping.cc @@ -148,7 +148,7 @@ PassDef grouping() << ((T(Group) << End) * (T(Group) << ((T(Ident)[Ident]) * - (T(Parens)[Parens] << ((~T(List) << T(Ident)++[List]))) * + (T(Parens)[Parens] << (~(T(List) << T(Ident)++[List]))) * End)) * (T(Group) << T(Block)[Block]) * End) >> [](auto& _) { diff --git a/src/lang/passes/parse.cc b/src/lang/passes/parse.cc index ab357fa..5922516 100644 --- a/src/lang/passes/parse.cc +++ b/src/lang/passes/parse.cc @@ -6,8 +6,8 @@ namespace verona::wf inline const auto parse_tokens = Ident | Lookup | Empty | Drop | Move | Null | String | Parens; - inline const auto parse_groups = - Group | Assign | If | Else | Block | For | Func | List | Return | While | When; + inline const auto parse_groups = Group | Assign | If | Else | Block | For | + Func | List | Return | While | When; inline const auto parser = (Top <<= File) | (File <<= parse_groups++) | (Assign <<= Group * (Lhs >>= (Group | cond))) | @@ -96,9 +96,7 @@ trieste::Parse parser() "(?:#[^\\n\\r]*)" >> [](auto&) {}, "def\\b" >> [](auto& m) { m.seq(Func); }, - "when\\b" >> [](auto& m) { - m.seq(When); - }, + "when\\b" >> [](auto& m) { m.seq(When); }, "\\(" >> [](auto& m) { m.push(Parens); }, "\\)" >> [](auto& m) { diff --git a/src/rt/core.h b/src/rt/core.h index 58c10b5..e0766bb 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -388,6 +388,20 @@ namespace rt::core status = Status::Released; } } + + void aquire() { + // Who needs other safety checks than this? + // This is so gonna bite me... + assert(this->status == Status::Released); + + this->status = Status::Acquired; + } + + void release() { + assert(this->status == Status::Acquired); + + this->status = Status::Released; + } }; inline std::set* globals() diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 1d8f542..d6741f3 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -394,7 +394,7 @@ namespace rt::core { add_builtin("spawn_behavior", [](auto frame, auto args) { std::cout << "Yay, what a day to live :D" << std::endl; - + // cowns (Stored on the stack in reverse order) // -1 since the first argument is the actual behavior std::vector cowns = {}; @@ -407,10 +407,10 @@ namespace rt::core auto behavior = frame->stack_pop("behavior"); // 1. Create `Behavior` (ByteCodeFunc, [Cowns]) object - // 2. Inform Sceduler about `Behavior` + // 2. Inform Scheduler about `Behavior` // - // In sceduler: - // 3. Sceduler waits until all cowns are available (dependency graph) + // In Scheduler: + // 3. Scheduler waits until all cowns are available (dependency graph) // - The "draw the rest of the owl step" // 4. Create a new interpreter from `bytecode.value()->body` // - `rt::move_reference(NULL, new_interpreter->frame(), body)` @@ -422,7 +422,7 @@ namespace rt::core // In interpreter (Already done by the lowering pass): // 8. Cowns from the stack are assigned to the defined names on the frame // - // After completion in sceduler + // After completion in Scheduler // 9. Release cowns // 10. `rt::remove_reference` the `behavior.func` diff --git a/src/rt/objects/region.cc b/src/rt/objects/region.cc index 8b13475..cc445f7 100644 --- a/src/rt/objects/region.cc +++ b/src/rt/objects/region.cc @@ -226,11 +226,11 @@ namespace rt::objects if (e.target == nullptr) return false; - std::cout << "Remove reference from: " << e.src->get_name() << " to " - << e.target->get_name() << std::endl; bool result = e.target->change_rc(-1) == 0; - - remove_region_reference(get_region(e.src), get_region(e.target)); + if (e.src) + { + remove_region_reference(get_region(e.src), get_region(e.target)); + } return result; }, [&](DynObject* obj) { delete obj; }); diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 46f8b62..db1377b 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -313,6 +313,16 @@ namespace rt objects::dissolve_region(bridge); } + void cown_update_state(objects::DynObject* cown) + { + if (cown->get_prototype() != core::cownPrototypeObject()) + { + ui::error("The given object is not a cown", cown); + } + + reinterpret_cast(cown)->update_status(); + } + bool is_cown_released(objects::DynObject* cown) { if (cown->get_prototype() != core::cownPrototypeObject()) @@ -323,13 +333,28 @@ namespace rt return reinterpret_cast(cown)->is_released(); } - void cown_update_state(objects::DynObject* cown) + void aquire_cown(objects::DynObject* cown) { if (cown->get_prototype() != core::cownPrototypeObject()) { ui::error("The given object is not a cown", cown); } - reinterpret_cast(cown)->update_status(); + reinterpret_cast(cown)->aquire(); } + + void release_cown(objects::DynObject* cown) + { + if (cown->get_prototype() != core::cownPrototypeObject()) + { + ui::error("The given object is not a cown", cown); + } + + reinterpret_cast(cown)->release(); + } + + void hack_inc_rc(objects::DynObject* obj) { + obj->change_rc(+1); + } + } // namespace rt diff --git a/src/rt/rt.h b/src/rt/rt.h index 6870c04..7aaa2c8 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -69,4 +69,11 @@ namespace rt /// released. void cown_update_state(objects::DynObject* cown); bool is_cown_released(objects::DynObject* cown); + + void aquire_cown(objects::DynObject* cown); + void release_cown(objects::DynObject* cown); + + // This increases the rc without asking questions. Very much a + // hack but I don't care anymore. + void hack_inc_rc(objects::DynObject* obj); } // namespace rt diff --git a/tests/example_1.frank b/tests/example_1.frank index c5ff04b..a8721d4 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -1,3 +1,17 @@ -x = {} -y = {} -x.y = {} +# For debugging: +# mermaid_show_functions() + +c1 = Cown(Region()) +c2 = Cown(Region()) + +when (c1): + r = c1 + +when (c1, c2): + r = c1 + +when (): + r = c1 + +when: + r = None \ No newline at end of file From b895dce03169240383427c9fb709649b35b545ee Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 21 Mar 2025 18:40:13 +0100 Subject: [PATCH 04/23] It seems like only one more thing is broken --- src/lang/interpreter.cc | 28 ++++++++++++++++------------ src/lang/interpreter.h | 6 ++---- src/rt/core.h | 8 +++++--- src/rt/core/builtin.cc | 10 ++++++---- src/rt/objects/dyn_object.h | 2 -- src/rt/objects/region.h | 2 -- src/rt/rt.cc | 4 ++-- src/rt/rt.h | 2 +- tests/example_1.frank | 2 +- 9 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index f39dc57..d4d4a2f 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -560,11 +560,10 @@ namespace verona::interpreter reinterpret_cast(ui)->set_step_counter(step_counter); } - size_t initial = rt::pre_run(ui); - - // Interpreter inter(ui); - // inter.run(main_body); Scheduler s; + + size_t initial = rt::pre_run(ui, &s); + s.start(new Bytecode{main_body}); rt::post_run(initial, ui); @@ -611,16 +610,21 @@ namespace verona::interpreter for (auto cown : behavior->cowns) { + std::cout << "Fuck c++ " << cown << std::endl; // Get the last behavior that is waiting on the cown - auto pending = cowns[cown]; - // If a behavior is pending, set the successor - if (pending && !pending->is_complete) + auto cown_info = cowns.find((uintptr_t)cown); + if (cown_info != cowns.end()) { - is_ready = false; - pending->succ = behavior; + auto pending = cown_info->second; + // If a behavior is pending, set the successor + if (!pending->is_complete) + { + is_ready = false; + pending->succ = behavior; + } } // Update pointer to the last pending behavior - cowns[cown] = behavior; + this->cowns.insert({(uintptr_t)cown, behavior}); } if (is_ready) @@ -639,8 +643,8 @@ namespace verona::interpreter void Scheduler::start(Bytecode* main_block) { auto main_function = rt::make_func(main_block); - // Hack: Needed to keep the main function alive. Otherwise, it'll be freed thereby - // also deleting the trieste nodes. + // Hack: Needed to keep the main function alive. Otherwise, it'll be freed + // thereby also deleting the trieste nodes. rt::hack_inc_rc(main_function); // :notes: I imagine a world without ugly c++ :notes: auto behavior = std::make_shared( diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 199340e..1c17c0e 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -72,13 +72,12 @@ namespace verona::interpreter class Scheduler { // All behaviors that are ready to run - std::vector> ready; + std::vector> ready = {}; // A map from cowns to the last behavior that is waiting on them. // // The cowns in the key are weak pointers, they should never be // dereferenced. - std::unordered_map> - cowns; + std::unordered_map> cowns = {}; public: void add(std::shared_ptr behavior); @@ -87,6 +86,5 @@ namespace verona::interpreter private: std::shared_ptr get_next(); - }; } diff --git a/src/rt/core.h b/src/rt/core.h index e0766bb..652bcf6 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -389,7 +389,8 @@ namespace rt::core } } - void aquire() { + void aquire() + { // Who needs other safety checks than this? // This is so gonna bite me... assert(this->status == Status::Released); @@ -397,7 +398,8 @@ namespace rt::core this->status = Status::Acquired; } - void release() { + void release() + { assert(this->status == Status::Acquired); this->status = Status::Released; @@ -436,5 +438,5 @@ namespace rt::core /// /// @param ui The UI to allow builtin functions to create output, when they're /// called. - void init_builtins(ui::UI* ui); + void init_builtins(ui::UI* ui, verona::interpreter::Scheduler* scheduler); } // namespace rt::core diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index d6741f3..9df99c2 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -390,9 +390,9 @@ namespace rt::core }); } - void concurrency_builtins() + void concurrency_builtins(verona::interpreter::Scheduler* scheduler) { - add_builtin("spawn_behavior", [](auto frame, auto args) { + add_builtin("spawn_behavior", [=](auto frame, auto args) { std::cout << "Yay, what a day to live :D" << std::endl; // cowns (Stored on the stack in reverse order) @@ -405,6 +405,8 @@ namespace rt::core } // when auto behavior = frame->stack_pop("behavior"); + scheduler->add( + std::make_shared(behavior, cowns)); // 1. Create `Behavior` (ByteCodeFunc, [Cowns]) object // 2. Inform Scheduler about `Behavior` @@ -430,12 +432,12 @@ namespace rt::core }); } - void init_builtins(ui::UI* ui) + void init_builtins(ui::UI* ui, verona::interpreter::Scheduler* scheduler) { mermaid_builtins(ui); ctor_builtins(); action_builtins(); pragma_builtins(); - concurrency_builtins(); + concurrency_builtins(scheduler); } } diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 41853e4..018f297 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -63,8 +63,6 @@ namespace rt::objects public: size_t change_rc(signed delta) { - std::cout << "Change RC: " << get_name() << " " << rc << " + " << delta - << std::endl; if (!(is_immutable() || is_cown())) { assert(delta == 0 || rc != 0); diff --git a/src/rt/objects/region.h b/src/rt/objects/region.h index c6f4f82..25cecef 100644 --- a/src/rt/objects/region.h +++ b/src/rt/objects/region.h @@ -233,7 +233,6 @@ namespace rt::objects collecting = true; - std::cout << "Starting collection" << std::endl; while (!to_collect.empty()) { auto r = *to_collect.begin(); @@ -249,7 +248,6 @@ namespace rt::objects delete r; } - std::cout << "Finished collection" << std::endl; collecting = false; } }; diff --git a/src/rt/rt.cc b/src/rt/rt.cc index db1377b..d51dfa6 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -190,11 +190,11 @@ namespace rt objects::move_reference(src, dst, target); } - size_t pre_run(ui::UI* ui) + size_t pre_run(ui::UI* ui, verona::interpreter::Scheduler *scheduler) { std::cout << "Initilizing global objects" << std::endl; core::globals(); - core::init_builtins(ui); + core::init_builtins(ui, scheduler); if (ui->is_mermaid()) { diff --git a/src/rt/rt.h b/src/rt/rt.h index 7aaa2c8..c956d35 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -53,7 +53,7 @@ namespace rt objects::DynObject* dst, objects::DynObject* target); - size_t pre_run(rt::ui::UI* ui); + size_t pre_run(rt::ui::UI* ui, verona::interpreter::Scheduler* scheduler); void post_run(size_t count, rt::ui::UI* ui); objects::DynObject* iter_next(objects::DynObject* iter); diff --git a/tests/example_1.frank b/tests/example_1.frank index a8721d4..6252bba 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -11,7 +11,7 @@ when (c1, c2): r = c1 when (): - r = c1 + r = None when: r = None \ No newline at end of file From 37041c917d5d17fdf374ca3fe604a445cfee8da4 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 21 Mar 2025 19:28:42 +0100 Subject: [PATCH 05/23] Behaviors seem to be working --- src/lang/interpreter.cc | 39 +++++++++++++++++++++++++++++++-------- src/lang/interpreter.h | 2 ++ tests/example_1.frank | 2 +- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index d4d4a2f..70a2379 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -481,10 +482,16 @@ namespace verona::interpreter public: Interpreter(rt::ui::UI* ui_) : ui(ui_) {} - void run(trieste::Node main) + void + run(trieste::Node main, std::vector start_stack) { auto frame = push_stack_frame(main); + for (auto elem : std::views::reverse(start_stack)) + { + frame->frame->stack_push(elem, "staring stack"); + } + while (frame) { const auto action = run_stmt(*frame->ip); @@ -606,11 +613,9 @@ namespace verona::interpreter void Scheduler::add(std::shared_ptr behavior) { - bool is_ready = true; - + // TODO add a testing mode that selects based on a seed for (auto cown : behavior->cowns) { - std::cout << "Fuck c++ " << cown << std::endl; // Get the last behavior that is waiting on the cown auto cown_info = cowns.find((uintptr_t)cown); if (cown_info != cowns.end()) @@ -619,15 +624,22 @@ namespace verona::interpreter // If a behavior is pending, set the successor if (!pending->is_complete) { - is_ready = false; - pending->succ = behavior; + if (pending->succ == nullptr) + { + behavior->pred_ctn += 1; + pending->succ = behavior; + } + else + { + assert(pending->succ == behavior); + } } } // Update pointer to the last pending behavior this->cowns.insert({(uintptr_t)cown, behavior}); } - if (is_ready) + if (behavior->pred_ctn == 0) { this->ready.push_back(behavior); std::cout << "New behavior `" << behavior->name() << "` is ready" @@ -656,9 +668,20 @@ namespace verona::interpreter auto block = behavior->spawn(); Interpreter inter(rt::ui::globalUI()); - inter.run(block->body); + inter.run(block->body, behavior->cowns); + // This is ugly and should be in a method behavior->complete(); + if (behavior->succ) + { + behavior->succ->pred_ctn -= 1; + if (behavior->succ->pred_ctn == 0) + { + this->ready.push_back(behavior->succ); + } + + behavior->succ = nullptr; + } behavior = this->get_next(); } diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 1c17c0e..4db226c 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -49,6 +49,8 @@ namespace verona::interpreter std::vector cowns; // This uses a function object opposed to a Bytecode* to not leak memory rt::objects::DynObject* code; + // The number of behaviors that this behavior is waiting on + int pred_ctn = 0; std::shared_ptr succ = nullptr; bool is_complete = false; diff --git a/tests/example_1.frank b/tests/example_1.frank index 6252bba..130f88f 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -14,4 +14,4 @@ when (): r = None when: - r = None \ No newline at end of file + r = None From 209648feac6235f3f02c8ed11a75aa4c79cbbeb1 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 21 Mar 2025 21:45:21 +0100 Subject: [PATCH 06/23] Working on diagrams --- src/lang/interpreter.cc | 47 +++++++++++------ src/lang/interpreter.h | 3 ++ src/rt/objects/dyn_object.h | 2 +- src/rt/ui.h | 10 ++++ src/rt/ui/mermaid.cc | 102 ++++++++++++++++++++++++++++-------- 5 files changed, 124 insertions(+), 40 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 70a2379..ba693ff 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -639,17 +639,18 @@ namespace verona::interpreter this->cowns.insert({(uintptr_t)cown, behavior}); } + std::stringstream ss; if (behavior->pred_ctn == 0) { this->ready.push_back(behavior); - std::cout << "New behavior `" << behavior->name() << "` is ready" - << std::endl; + ss << "New behavior `" << behavior->name() << "` is ready"; } else { - std::cout << "New behavior `" << behavior->name() << "` is pending" - << std::endl; + ss << "New behavior `" << behavior->name() << "` is pending"; } + + this->draw_scedule(ss.str()); } void Scheduler::start(Bytecode* main_block) @@ -670,18 +671,7 @@ namespace verona::interpreter Interpreter inter(rt::ui::globalUI()); inter.run(block->body, behavior->cowns); - // This is ugly and should be in a method - behavior->complete(); - if (behavior->succ) - { - behavior->succ->pred_ctn -= 1; - if (behavior->succ->pred_ctn == 0) - { - this->ready.push_back(behavior->succ); - } - - behavior->succ = nullptr; - } + this->complete(behavior); behavior = this->get_next(); } @@ -689,6 +679,29 @@ namespace verona::interpreter rt::remove_reference(nullptr, main_function); } + void Scheduler::complete(std::shared_ptr behavior) + { + behavior->complete(); + if (behavior->succ) + { + behavior->succ->pred_ctn -= 1; + if (behavior->succ->pred_ctn == 0) + { + this->ready.push_back(behavior->succ); + } + + behavior->succ = nullptr; + } + } + + void Scheduler::draw_scedule(std::string message) + { + auto ui = rt::ui::globalUI(); + assert(ui->is_mermaid()); + auto mermaid = reinterpret_cast(ui); + mermaid->draw_schedule(this->ready, message); + } + std::shared_ptr Scheduler::get_next() { if (this->ready.empty()) @@ -696,6 +709,8 @@ namespace verona::interpreter return nullptr; } + this->draw_scedule("Current Schedule:"); + // I hate c and c++ `unsigned` soo much... At least I'm getting paid to deal // with this s... *suboptimal* language unsigned int selected = 0; diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 4db226c..388f97c 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -86,7 +86,10 @@ namespace verona::interpreter void start(Bytecode* main); + private: + void complete(std::shared_ptr behavior); + void draw_scedule(std::string message); std::shared_ptr get_next(); }; } diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 018f297..0c3822c 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -38,7 +38,7 @@ namespace rt::objects friend class Reference; friend objects::DynObject* rt::make_iter(objects::DynObject* obj); friend class ui::MermaidUI; - friend class ui::MermaidDiagram; + friend class ui::ObjectGraphDiagram; friend class core::CownObject; friend void destruct(DynObject* obj); friend void dealloc(DynObject* obj); diff --git a/src/rt/ui.h b/src/rt/ui.h index 2bb7d6c..2ea3775 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -1,5 +1,6 @@ #pragma once +#include "../lang/interpreter.h" #include "objects/visit.h" #include @@ -38,6 +39,7 @@ namespace rt::core namespace rt::ui { class MermaidDiagram; + class ObjectGraphDiagram; class MermaidUI : public UI { @@ -46,6 +48,8 @@ namespace rt::ui static inline bool highlight_unreachable = false; private: + friend class ObjectGraphDiagram; + friend class ScheduleDiagram; friend class MermaidDiagram; friend void core::mermaid_builtins(ui::UI* ui); @@ -86,9 +90,15 @@ namespace rt::ui this->path = path_; } + void prep_output(); + void output( std::vector& roots, std::string message) override; + void draw_schedule( + std::vector> behaviors, + std::string message); + void highlight( std::string message, std::vector& highlight) override; diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index e6ed274..00518c9 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -9,6 +9,8 @@ #include #include +typedef std::shared_ptr behavior_ptr; + namespace rt::ui { namespace fs = std::filesystem; @@ -91,39 +93,75 @@ namespace rt::ui class MermaidDiagram { + protected: MermaidUI* info; std::ofstream& out; + size_t edge_counter = 0; + + MermaidDiagram(MermaidUI* info_) : info(info_), out(info->out) {} + + void draw_header() + { + // Header + out << "
" << std::endl; + out << std::endl; + out << "```mermaid" << std::endl; + out << "%%{init: {'theme': 'neutral', 'themeVariables': { 'fontSize': '" + << FONT_SIZE << "' }}}%%" << std::endl; + out << "graph TD" << std::endl; + } + + void draw_footer() + { + // Footer (end of mermaid graph) + out << "```" << std::endl; + out << "
" << std::endl; + out << "
" << std::endl; + out << std::endl; + } + + void color_edge(size_t edge_id, const char* color, int width = EDGE_WIDTH) + { + out << " linkStyle " << edge_id << " stroke:" << color + << ",stroke-width:" << width << "px" << std::endl; + } + }; + + class ScheduleDiagram : protected MermaidDiagram + { + public: + ScheduleDiagram(MermaidUI* info_) : MermaidDiagram(info_) {} + + void draw(std::vector& roots) + { + // Header + this->draw_header(); + out << " id0(Scheduler):::immutable" << std::endl; + // Footer + this->draw_footer(); + } + }; + class ObjectGraphDiagram : protected MermaidDiagram + { size_t id_counter = 1; - size_t edge_counter = 0; // Give a nice id to each object. std::map nodes; std::map regions; public: - MermaidDiagram(MermaidUI* info_) : info(info_), out(info->out) + ObjectGraphDiagram(MermaidUI* info_) : MermaidDiagram(info_) { // Add nullptr nodes[nullptr] = {0}; regions[objects::immutable_region].nodes.push_back(0); } - void color_edge(size_t edge_id, const char* color, int width = EDGE_WIDTH) - { - out << " linkStyle " << edge_id << " stroke:" << color - << ",stroke-width:" << width << "px" << std::endl; - } - void draw(std::vector& roots) { - // Header - out << "
" << std::endl; - out << std::endl; - out << "```mermaid" << std::endl; - out << "%%{init: {'theme': 'neutral', 'themeVariables': { 'fontSize': '" - << FONT_SIZE << "' }}}%%" << std::endl; - out << "graph TD" << std::endl; + // header + this->draw_header(); out << " id0(None):::immutable" << std::endl; draw_nodes(roots); @@ -132,6 +170,7 @@ namespace rt::ui draw_highlight(); draw_error(); + // Classes out << "classDef unreachable stroke-width:2px,stroke:" << UNREACHABLE_NODE_COLOR << std::endl; out << "classDef highlight stroke-width:4px,stroke:" @@ -142,11 +181,9 @@ namespace rt::ui out << "classDef tainted_immutable stroke-width:4px,stroke:" << TAINT_NODE_COLOR << std::endl; out << "classDef immutable fill:" << IMMUTABLE_NODE_COLOR << std::endl; - // Footer (end of mermaid graph) - out << "```" << std::endl; - out << "
" << std::endl; - out << "
" << std::endl; - out << std::endl; + + // Footer + this->draw_footer(); } private: @@ -497,8 +534,7 @@ namespace rt::ui hide_cown_region(); } - void MermaidUI::output( - std::vector& roots, std::string message) + void MermaidUI::prep_output() { // Reset the file if this is a breakpoint if (should_break() && out.is_open()) @@ -525,10 +561,16 @@ namespace rt::ui std::abort(); } } + } + + void MermaidUI::output( + std::vector& roots, std::string message) + { + this->prep_output(); out << "
" << message << "
" << std::endl; - MermaidDiagram diag(this); + ObjectGraphDiagram diag(this); diag.draw(roots); if (should_break()) @@ -542,6 +584,20 @@ namespace rt::ui } } + void MermaidUI::draw_schedule( + std::vector behaviors, std::string message) + { + this->prep_output(); + + out << "### " << message << std::endl; + + ScheduleDiagram diag(this); + diag.draw(behaviors); + + // Make sure the output is available. + out.flush(); + } + void MermaidUI::highlight( std::string message, std::vector& highlight) { From bfdc79dbce89cb3b8cb93ab898b32e2f2f1892c5 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Mon, 24 Mar 2025 10:57:14 +0100 Subject: [PATCH 07/23] Better drawing --- CMakeLists.txt | 1 + src/lang/interpreter.cc | 39 +++++---- src/lang/interpreter.h | 38 ++++++--- src/rt/core.cc | 5 ++ src/rt/core.h | 13 ++- src/rt/objects/dyn_object.h | 11 +-- src/rt/rt.cc | 15 +++- src/rt/rt.h | 3 +- src/rt/ui.h | 1 + src/rt/ui/mermaid.cc | 152 ++++++++++++++++++++++++++++++------ tests/example_1.frank | 15 +++- 11 files changed, 233 insertions(+), 60 deletions(-) create mode 100644 src/rt/core.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 34a8a27..4627e24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ set(CMAKE_CXX_STANDARD 20) add_library( rt OBJECT src/rt/rt.cc + src/rt/core.cc src/rt/objects/region.cc src/rt/ui/mermaid.cc src/rt/core/builtin.cc diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index ba693ff..5a3c5dd 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -578,6 +578,16 @@ namespace verona::interpreter int Behavior::s_behavior_counter = 0; + Behavior::Behavior( + rt::objects::DynObject* code_, std::vector cowns_) + : id(s_behavior_counter++), cowns(cowns_), code(code_) + { + for (auto c : cowns) + { + this->ordered_cown[rt::get_cown_id(c)] = c; + } + } + std::string Behavior::name() { std::stringstream ss; @@ -585,6 +595,13 @@ namespace verona::interpreter return ss.str(); } + std::string Behavior::id_str() + { + std::stringstream ss; + ss << "B" << this->id; + return ss.str(); + } + Bytecode* Behavior::spawn() { assert(!this->is_complete); @@ -617,26 +634,21 @@ namespace verona::interpreter for (auto cown : behavior->cowns) { // Get the last behavior that is waiting on the cown - auto cown_info = cowns.find((uintptr_t)cown); + auto cown_info = cowns.find(cown); if (cown_info != cowns.end()) { auto pending = cown_info->second; // If a behavior is pending, set the successor if (!pending->is_complete) { - if (pending->succ == nullptr) + if (pending->succ.insert(behavior).second) { behavior->pred_ctn += 1; - pending->succ = behavior; - } - else - { - assert(pending->succ == behavior); } } } // Update pointer to the last pending behavior - this->cowns.insert({(uintptr_t)cown, behavior}); + this->cowns[cown] = behavior; } std::stringstream ss; @@ -682,16 +694,15 @@ namespace verona::interpreter void Scheduler::complete(std::shared_ptr behavior) { behavior->complete(); - if (behavior->succ) + for (auto succ : behavior->succ) { - behavior->succ->pred_ctn -= 1; - if (behavior->succ->pred_ctn == 0) + succ->pred_ctn -= 1; + if (succ->pred_ctn == 0) { - this->ready.push_back(behavior->succ); + this->ready.push_back(succ); } - - behavior->succ = nullptr; } + behavior->succ.clear(); } void Scheduler::draw_scedule(std::string message) diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 388f97c..587035d 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -2,14 +2,21 @@ #include #include +#include #include #include +#include namespace rt::objects { class DynObject; } // namespace rt::objects +namespace rt::ui +{ + class ScheduleDiagram; +} + namespace verona::interpreter { struct Bytecode; @@ -39,35 +46,44 @@ namespace verona::interpreter class Behavior { + friend class rt::ui::ScheduleDiagram; + // Static member for naming static int s_behavior_counter; + // A unique ID, this is used for drawing and naming, it isn't needed for + // scheduling. int id; + // The IDs of the cowns this behavior is waiting on. This is used to create + // a better mermaid diagram, it isn't needed for scheduling. + std::map ordered_cown; + public: - // Instance members to describe the behavior + // The cowns as they were passed in to the cown. These have to be provided + // to the new Interpreter to populate the frame std::vector cowns; // This uses a function object opposed to a Bytecode* to not leak memory rt::objects::DynObject* code; // The number of behaviors that this behavior is waiting on int pred_ctn = 0; - std::shared_ptr succ = nullptr; + // Behaviors which are waiting on this behavior. These will be notified once + // this behavior completes + std::set> succ; + + // TODO: Make enum bool is_complete = false; Behavior( rt::objects::DynObject* code_, - std::vector cowns_) - : cowns(cowns_), code(code_) - { - id = s_behavior_counter; - s_behavior_counter += 1; - } + std::vector cowns_); std::string name(); + std::string id_str(); Bytecode* spawn(); // This completes the behavior by releasing all cowns - // decreffing all held objects and informing its successor. + // decreffing all held objects and informing its successors. void complete(); }; @@ -79,14 +95,14 @@ namespace verona::interpreter // // The cowns in the key are weak pointers, they should never be // dereferenced. - std::unordered_map> cowns = {}; + std::unordered_map> + cowns = {}; public: void add(std::shared_ptr behavior); void start(Bytecode* main); - private: void complete(std::shared_ptr behavior); void draw_scedule(std::string message); diff --git a/src/rt/core.cc b/src/rt/core.cc new file mode 100644 index 0000000..8df5c71 --- /dev/null +++ b/src/rt/core.cc @@ -0,0 +1,5 @@ +#include "core.h" + +namespace rt::core { + int CownObject::s_id_counter = 0; +} \ No newline at end of file diff --git a/src/rt/core.h b/src/rt/core.h index 652bcf6..070ae6c 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -256,6 +256,8 @@ namespace rt::core class CownObject : public objects::DynObject { private: + static int s_id_counter; + enum class Status { Pending, @@ -279,6 +281,7 @@ namespace rt::core } Status status; + int id; public: CownObject(objects::DynObject* obj) @@ -287,6 +290,8 @@ namespace rt::core status = Status::Pending; auto old = set("value", obj); assert(!old); + + id = s_id_counter++; } [[nodiscard]] DynObject* set(std::string name, DynObject* obj) override @@ -333,10 +338,16 @@ namespace rt::core return old; } + // A unique cown ID + int get_id() + { + return this->id; + } + std::string get_name() override { std::stringstream ss; - ss << "" << std::endl; + ss << "id << ">" << std::endl; ss << "status=" << to_string(status); return ss.str(); } diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 0c3822c..dbcaf6f 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -57,6 +57,7 @@ namespace rt::objects size_t rc{1}; RegionPointer region{nullptr}; DynObject* prototype{nullptr}; + std::string name; std::map fields{}; @@ -92,10 +93,12 @@ namespace rt::objects if (prototype != nullptr) { - // prototype->change_rc(1); objects::add_reference(this, prototype); } - std::cout << "Allocate: " << this << std::endl; + + std::stringstream stream; + stream << this; + name = stream.str(); } // TODO This should use prototype lookup for the destructor. @@ -132,9 +135,7 @@ namespace rt::objects /// TODO remove virtual once we have primitive functions. virtual std::string get_name() { - std::stringstream stream; - stream << this; - return stream.str(); + return name; } /// TODO remove virtual once we have primitive functions. diff --git a/src/rt/rt.cc b/src/rt/rt.cc index d51dfa6..215e856 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -190,7 +190,7 @@ namespace rt objects::move_reference(src, dst, target); } - size_t pre_run(ui::UI* ui, verona::interpreter::Scheduler *scheduler) + size_t pre_run(ui::UI* ui, verona::interpreter::Scheduler* scheduler) { std::cout << "Initilizing global objects" << std::endl; core::globals(); @@ -353,7 +353,18 @@ namespace rt reinterpret_cast(cown)->release(); } - void hack_inc_rc(objects::DynObject* obj) { + int get_cown_id(objects::DynObject* cown) + { + if (cown->get_prototype() != core::cownPrototypeObject()) + { + ui::error("The given object is not a cown", cown); + } + + return reinterpret_cast(cown)->get_id(); + } + + void hack_inc_rc(objects::DynObject* obj) + { obj->change_rc(+1); } diff --git a/src/rt/rt.h b/src/rt/rt.h index c956d35..929b118 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -69,9 +69,10 @@ namespace rt /// released. void cown_update_state(objects::DynObject* cown); bool is_cown_released(objects::DynObject* cown); - + void aquire_cown(objects::DynObject* cown); void release_cown(objects::DynObject* cown); + int get_cown_id(objects::DynObject* cown); // This increases the rc without asking questions. Very much a // hack but I don't care anymore. diff --git a/src/rt/ui.h b/src/rt/ui.h index 2ea3775..fc87ddf 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -39,6 +39,7 @@ namespace rt::core namespace rt::ui { class MermaidDiagram; + class ScheduleDiagram; class ObjectGraphDiagram; class MermaidUI : public UI diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 00518c9..273b091 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -125,6 +125,27 @@ namespace rt::ui out << " linkStyle " << edge_id << " stroke:" << color << ",stroke-width:" << width << "px" << std::endl; } + + std::pair get_node_style(objects::DynObject* obj) + { + if (obj->get_prototype() == core::cownPrototypeObject()) + { + return {"[[", "]]"}; + } + + if (obj->get_prototype() == objects::regionPrototypeObject()) + { + return {"[\\", "/]"}; + } + + if (obj->is_immutable()) + { + // Make sure to also update the None node, when editing these + return {"(", ")"}; + } + + return {"[", "]"}; + } }; class ScheduleDiagram : protected MermaidDiagram @@ -132,11 +153,117 @@ namespace rt::ui public: ScheduleDiagram(MermaidUI* info_) : MermaidDiagram(info_) {} - void draw(std::vector& roots) + private: + std::string cown_node_id(rt::objects::DynObject* cown, int behavior_id) + { + std::stringstream ss; + ss << cown << "_" << behavior_id; + return ss.str(); + } + + void draw_cown(rt::objects::DynObject* cown_obj, behavior_ptr behavior) { + assert(cown_obj->get_prototype() == core::cownPrototypeObject()); + core::CownObject* cown = reinterpret_cast(cown_obj); + auto cown_id = cown->get_id(); + + auto markers = get_node_style(cown); + + // Header + out << " "; + out << cown_node_id(cown, behavior->id); + out << markers.first; + + // Content + out << "cown " << cown_id; + + // Footer + out << markers.second; + out << std::endl; + } + + void draw_behavior(behavior_ptr behavior) + { + out << "subgraph " << behavior->id_str() << "[\"" << behavior->name() + << "\"]" << std::endl; + + for (auto [_, c] : behavior->ordered_cown) + { + this->draw_cown(c, behavior); + } + out << "end" << std::endl; + } + + void draw_dependencies(behavior_ptr behavior) + { + for (auto [_, cown_obj] : behavior->ordered_cown) + { + assert(cown_obj->get_prototype() == core::cownPrototypeObject()); + core::CownObject* cown = reinterpret_cast(cown_obj); + auto cown_id = cown->get_id(); + + // Draw dependencies + for (auto succ : behavior->succ) + { + if (succ->ordered_cown.contains(cown_id)) + { + out << " "; + out << cown_node_id(cown, succ->id); + out << " --> "; + out << cown_node_id(cown, behavior->id); + out << std::endl; + edge_counter += 1; + } + } + } + } + + std::map + aggregate_behaviors(std::vector pending) + { + std::map behaviors; + + while (!pending.empty()) + { + auto b = pending.back(); + pending.pop_back(); + + auto [_, inserted] = behaviors.insert({b->id, b}); + if (inserted) + { + for (auto succ : b->succ) + { + pending.push_back(succ); + } + } + } + + return behaviors; + } + + public: + void draw(std::vector pending) + { + auto behaviors = aggregate_behaviors(pending); + // Header this->draw_header(); - out << " id0(Scheduler):::immutable" << std::endl; + + for (auto& [bid, behavior] : behaviors) + { + this->draw_behavior(behavior); + } + + for (auto& [bid, behavior] : behaviors) + { + this->draw_dependencies(behavior); + } + + // TODO: + // -> Show running behavior? + // -> Cown colors + // -> Mark ready behaviors + // Footer this->draw_footer(); } @@ -187,27 +314,6 @@ namespace rt::ui } private: - std::pair get_node_style(objects::DynObject* obj) - { - if (obj->get_prototype() == core::cownPrototypeObject()) - { - return {"[[", "]]"}; - } - - if (obj->get_prototype() == objects::regionPrototypeObject()) - { - return {"[\\", "/]"}; - } - - if (obj->is_immutable()) - { - // Make sure to also update the None node, when editing these - return {"(", ")"}; - } - - return {"[", "]"}; - } - bool is_borrow_edge(objects::Edge e) { return e.src != nullptr && e.target != nullptr && diff --git a/tests/example_1.frank b/tests/example_1.frank index 130f88f..5b025d0 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -3,15 +3,24 @@ c1 = Cown(Region()) c2 = Cown(Region()) +c3 = Cown(Region()) +c4 = Cown(Region()) +c5 = Cown(Region()) +c6 = Cown(Region()) +c7 = Cown(Region()) -when (c1): +when (c1, c2, c3): r = c1 when (c1, c2): r = c1 -when (): +when(c6, c7): r = None -when: +when(c5, c6): r = None + +when (c2, c3, c4, c5): + r = None + From 46ec5be8b1464bbe1a0d99f9f3f5c736c83afbc1 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 26 Mar 2025 10:59:41 +0100 Subject: [PATCH 08/23] Naming cowns --- src/lang/interpreter.cc | 24 ++++++++++++++++++----- src/lang/interpreter.h | 37 +++++++++++++++++++++++++++++++---- src/lang/passes/call_stmts.cc | 2 +- src/lang/passes/flatten.cc | 12 +++++++----- src/rt/core.cc | 5 +++-- src/rt/core.h | 16 ++++++++++++--- src/rt/core/builtin.cc | 13 +++++++++--- src/rt/rt.cc | 34 +++++++++++++++++++------------- src/rt/rt.h | 2 +- src/rt/ui/mermaid.cc | 36 +++++++++++++++++++++++++++++----- tests/example_1.frank | 12 ++++++------ 11 files changed, 144 insertions(+), 49 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 5a3c5dd..1356ab1 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -487,7 +487,7 @@ namespace verona::interpreter { auto frame = push_stack_frame(main); - for (auto elem : std::views::reverse(start_stack)) + for (auto elem : start_stack) { frame->frame->stack_push(elem, "staring stack"); } @@ -604,7 +604,8 @@ namespace verona::interpreter Bytecode* Behavior::spawn() { - assert(!this->is_complete); + assert(this->status == Status::Ready); + this->status = Status::Running; for (auto c : this->cowns) { @@ -616,7 +617,7 @@ namespace verona::interpreter void Behavior::complete() { - this->is_complete = true; + this->status = Status::Done; rt::remove_reference(nullptr, this->code); this->code = nullptr; @@ -630,6 +631,8 @@ namespace verona::interpreter void Scheduler::add(std::shared_ptr behavior) { + assert(behavior->status == Behavior::Status::New); + // TODO add a testing mode that selects based on a seed for (auto cown : behavior->cowns) { @@ -639,7 +642,7 @@ namespace verona::interpreter { auto pending = cown_info->second; // If a behavior is pending, set the successor - if (!pending->is_complete) + if (pending->status != Behavior::Status::Done) { if (pending->succ.insert(behavior).second) { @@ -655,10 +658,12 @@ namespace verona::interpreter if (behavior->pred_ctn == 0) { this->ready.push_back(behavior); + behavior->status = Behavior::Status::Ready; ss << "New behavior `" << behavior->name() << "` is ready"; } else { + behavior->status = Behavior::Status::Pending; ss << "New behavior `" << behavior->name() << "` is pending"; } @@ -674,6 +679,7 @@ namespace verona::interpreter // :notes: I imagine a world without ugly c++ :notes: auto behavior = std::make_shared( main_function, std::vector{}); + behavior->status = Behavior::Status::Ready; // Seriously, why do we use this language? The memory problems I currently // have could easly be avoided. while (behavior) @@ -699,6 +705,7 @@ namespace verona::interpreter succ->pred_ctn -= 1; if (succ->pred_ctn == 0) { + succ->status = Behavior::Status::Ready; this->ready.push_back(succ); } } @@ -739,8 +746,15 @@ namespace verona::interpreter std::cout << "> "; std::string line; std::getline(std::cin, line); - std::istringstream iss(line); + // Check for quit + if (line == "q") + { + exit(0); + } + + // Check selection + std::istringstream iss(line); int n = 0; if (iss >> n) { diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 587035d..835050e 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -1,11 +1,11 @@ #pragma once #include +#include #include #include #include #include -#include namespace rt::objects { @@ -48,6 +48,38 @@ namespace verona::interpreter { friend class rt::ui::ScheduleDiagram; + public: + enum class Status + { + New, + Pending, + Ready, + Running, + Done, + }; + + static std::string status_to_string(Status status) + { + switch (status) + { + case Status::New: + return "New"; + case Status::Pending: + return "Pending"; + case Status::Ready: + return "Ready"; + case Status::Running: + return "Running"; + case Status::Done: + return "Done"; + default: + return "Unknown"; + } + } + + Status status; + + private: // Static member for naming static int s_behavior_counter; @@ -71,9 +103,6 @@ namespace verona::interpreter // this behavior completes std::set> succ; - // TODO: Make enum - bool is_complete = false; - Behavior( rt::objects::DynObject* code_, std::vector cowns_); diff --git a/src/lang/passes/call_stmts.cc b/src/lang/passes/call_stmts.cc index fbbd3a8..950f7a9 100644 --- a/src/lang/passes/call_stmts.cc +++ b/src/lang/passes/call_stmts.cc @@ -21,7 +21,7 @@ PassDef call_stmts() [](auto& _) { if (_(Ident)->location().view() == "spawn_behavior") { - return Seq << _(Call) << create_print(_(Call), "Spawning Behavior"); + return Seq << _(Call); } else { diff --git a/src/lang/passes/flatten.cc b/src/lang/passes/flatten.cc index 17e4e38..1114683 100644 --- a/src/lang/passes/flatten.cc +++ b/src/lang/passes/flatten.cc @@ -171,14 +171,16 @@ PassDef flatten() } body << create_print(_(Func), func_head + " (Exit)"); + auto result = Seq << (CreateObject << (Func << (Compile << body))) + << (StoreFrame ^ _(Ident)); auto def_text = func_head; - if (_(Ident)->location().view().starts_with("__when_")) { - def_text = std::string("Creating behavior from: ") + func_head; + if (!_(Ident)->location().view().starts_with("__when_")) + { + result << create_print(_(Func), def_text); + // def_text = std::string("Creating behavior from: ") + func_head; } // Function cleanup - return Seq << (CreateObject << (Func << (Compile << body))) - << (StoreFrame ^ _(Ident)) - << create_print(_(Func), def_text); + return result; }, }}; } diff --git a/src/rt/core.cc b/src/rt/core.cc index 8df5c71..311beb1 100644 --- a/src/rt/core.cc +++ b/src/rt/core.cc @@ -1,5 +1,6 @@ #include "core.h" -namespace rt::core { - int CownObject::s_id_counter = 0; +namespace rt::core +{ + int CownObject::s_id_counter = 1; } \ No newline at end of file diff --git a/src/rt/core.h b/src/rt/core.h index 070ae6c..3780d7f 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -284,14 +284,22 @@ namespace rt::core int id; public: - CownObject(objects::DynObject* obj) + CownObject(objects::DynObject* obj, std::optional name_ = std::nullopt) : objects::DynObject(cownPrototypeObject(), objects::cown_region) { + id = s_id_counter++; + status = Status::Pending; auto old = set("value", obj); assert(!old); - id = s_id_counter++; + if (name_) { + name = name_.value(); + } else { + std::stringstream ss; + ss << "id << ">" << std::endl; + name = ss.str(); + } } [[nodiscard]] DynObject* set(std::string name, DynObject* obj) override @@ -344,10 +352,12 @@ namespace rt::core return this->id; } + // TODO: This should really be split into `get_name()` just getting the name + // and `get_info()` or the additional info text like lrc and status std::string get_name() override { std::stringstream ss; - ss << "id << ">" << std::endl; + ss << this->name << std::endl; ss << "status=" << to_string(status); return ss.str(); } diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 9df99c2..1394a61 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -181,14 +181,21 @@ namespace rt::core void ctor_builtins() { add_builtin("Cown", [](auto frame, auto args) { - if (args != 1) + if (args < 1 && args > 2) + { + ui::error("Cown() expected 1 or 2 arguments"); + } + + objects::DynObject* name = nullptr; + if (args == 2) { - ui::error("Cown() expected 1 argument"); + name = frame->stack_pop("name"); } auto region = frame->stack_pop("region for cown creation"); - auto cown = make_cown(region); + auto cown = make_cown(region, name); rt::move_reference(frame->object(), cown, region); + rt::remove_reference(frame->object(), name); return cown; }); diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 215e856..65a3cb1 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -30,6 +30,18 @@ namespace rt return nullptr; } + std::string get_key(objects::DynObject* key) + { + // TODO Add some checking. This is need to lookup the correct function in + // the prototype chain. + if (key && key->get_prototype() != core::stringPrototypeObject()) + { + ui::error("Object must be a string.", key); + } + core::StringObject* str_key = reinterpret_cast(key); + return str_key->as_key(); + } + objects::DynObject* make_func(verona::interpreter::Bytecode* body) { return new core::BytecodeFuncObject(body); @@ -63,9 +75,15 @@ namespace rt } } - objects::DynObject* make_cown(objects::DynObject* region) + objects::DynObject* + make_cown(objects::DynObject* value, objects::DynObject* name_obj) { - return new core::CownObject(region); + std::optional name; + if (name_obj) + { + name = get_key(name_obj); + } + return new core::CownObject(value, name); } void freeze(objects::DynObject* obj) @@ -97,18 +115,6 @@ namespace rt return obj->get(key); } - std::string get_key(objects::DynObject* key) - { - // TODO Add some checking. This is need to lookup the correct function in - // the prototype chain. - if (key && key->get_prototype() != core::stringPrototypeObject()) - { - ui::error("Key must be a string.", key); - } - core::StringObject* str_key = reinterpret_cast(key); - return str_key->as_key(); - } - std::optional get(objects::DynObject* obj, objects::DynObject* key) { diff --git a/src/rt/rt.h b/src/rt/rt.h index 929b118..751bca1 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -22,7 +22,7 @@ namespace rt objects::DynObject* make_iter(objects::DynObject* iter_src); objects::DynObject* make_str(std::string str_value); objects::DynObject* make_object(); - objects::DynObject* make_cown(objects::DynObject* region); + objects::DynObject* make_cown(objects::DynObject* value, objects::DynObject* name); void freeze(objects::DynObject* obj); objects::DynObject* create_region(); diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 273b091..7fdbfa7 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,10 @@ namespace rt::ui const char* IMM_REGION_ID = "ImmReg"; const char* COWN_REGION_ID = "CownReg"; + const char* BEHAVIOR_RUNNING_COLOR = "#eefcdd"; + const char* BEHAVIOR_READY_COLOR = "#ddeefc"; + const char* BEHAVIOR_PENDING_COLOR = "#fcfbdd"; + const char* FONT_SIZE = "16px"; const int EDGE_WIDTH = 2; const int ERROR_EDGE_WIDTH = 4; @@ -108,7 +113,6 @@ namespace rt::ui out << "```mermaid" << std::endl; out << "%%{init: {'theme': 'neutral', 'themeVariables': { 'fontSize': '" << FONT_SIZE << "' }}}%%" << std::endl; - out << "graph TD" << std::endl; } void draw_footer() @@ -184,14 +188,34 @@ namespace rt::ui void draw_behavior(behavior_ptr behavior) { - out << "subgraph " << behavior->id_str() << "[\"" << behavior->name() - << "\"]" << std::endl; + out << "subgraph " << behavior->id_str() << "[\" \"]" << std::endl; + out << " info_" << behavior->id_str() << "([\"" << behavior->name() + << "
Status: " + << verona::interpreter::Behavior::status_to_string(behavior->status) + << "\"])" << std::endl; for (auto [_, c] : behavior->ordered_cown) { this->draw_cown(c, behavior); } out << "end" << std::endl; + + // Set background color + auto background = ERROR_NODE_COLOR; + switch (behavior->status) + { + case verona::interpreter::Behavior::Status::Running: + background = BEHAVIOR_RUNNING_COLOR; + break; + case verona::interpreter::Behavior::Status::Ready: + background = BEHAVIOR_READY_COLOR; + break; + case verona::interpreter::Behavior::Status::Pending: + background = BEHAVIOR_PENDING_COLOR; + break; + } + out << "style " << behavior->id_str() << " fill:" << background + << std::endl; } void draw_dependencies(behavior_ptr behavior) @@ -248,8 +272,10 @@ namespace rt::ui // Header this->draw_header(); + out << "graph TD" << std::endl; - for (auto& [bid, behavior] : behaviors) + // Drawing in reverse order gives a better diagram + for (auto& [bid, behavior] : std::views::reverse(behaviors)) { this->draw_behavior(behavior); } @@ -262,7 +288,6 @@ namespace rt::ui // TODO: // -> Show running behavior? // -> Cown colors - // -> Mark ready behaviors // Footer this->draw_footer(); @@ -289,6 +314,7 @@ namespace rt::ui { // header this->draw_header(); + out << "graph TD" << std::endl; out << " id0(None):::immutable" << std::endl; draw_nodes(roots); diff --git a/tests/example_1.frank b/tests/example_1.frank index 5b025d0..ea09792 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -1,19 +1,19 @@ # For debugging: # mermaid_show_functions() -c1 = Cown(Region()) -c2 = Cown(Region()) -c3 = Cown(Region()) -c4 = Cown(Region()) +c1 = Cown(Region(), "c1") +c2 = Cown(Region(), "c2") +c3 = Cown(Region(), "c3") +c4 = Cown(Region(), "c4") c5 = Cown(Region()) c6 = Cown(Region()) c7 = Cown(Region()) when (c1, c2, c3): - r = c1 + c3.value.c2 = c2 when (c1, c2): - r = c1 + c1.value = c2 when(c6, c7): r = None From 3ddd996c26696259c35bf4bbcc4e7d573435cf04 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 26 Mar 2025 11:47:08 +0100 Subject: [PATCH 09/23] Refactoring with terrible error messages (C++) --- CMakeLists.txt | 1 + src/lang/interpreter.cc | 74 +++++-------------------------- src/lang/interpreter.h | 86 ++++-------------------------------- src/rt/behavior.h | 97 +++++++++++++++++++++++++++++++++++++++++ src/rt/core/behavior.cc | 62 ++++++++++++++++++++++++++ src/rt/core/builtin.cc | 4 +- src/rt/ui.h | 5 +-- src/rt/ui/mermaid.cc | 29 ++++++------ 8 files changed, 197 insertions(+), 161 deletions(-) create mode 100644 src/rt/behavior.h create mode 100644 src/rt/core/behavior.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 4627e24..6224b52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ add_library( src/rt/objects/region.cc src/rt/ui/mermaid.cc src/rt/core/builtin.cc + src/rt/core/behavior.cc ) add_library( diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 1356ab1..955ae39 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -1,5 +1,6 @@ #include "interpreter.h" +#include "../rt/behavior.h" #include "../rt/rt.h" #include "bytecode.h" #include "trieste/trieste.h" @@ -576,62 +577,9 @@ namespace verona::interpreter rt::post_run(initial, ui); } - int Behavior::s_behavior_counter = 0; - - Behavior::Behavior( - rt::objects::DynObject* code_, std::vector cowns_) - : id(s_behavior_counter++), cowns(cowns_), code(code_) - { - for (auto c : cowns) - { - this->ordered_cown[rt::get_cown_id(c)] = c; - } - } - - std::string Behavior::name() - { - std::stringstream ss; - ss << "Behavior_" << this->id; - return ss.str(); - } - - std::string Behavior::id_str() - { - std::stringstream ss; - ss << "B" << this->id; - return ss.str(); - } - - Bytecode* Behavior::spawn() - { - assert(this->status == Status::Ready); - this->status = Status::Running; - - for (auto c : this->cowns) - { - rt::aquire_cown(c); - } - - return rt::try_get_bytecode(this->code).value(); - } - - void Behavior::complete() - { - this->status = Status::Done; - rt::remove_reference(nullptr, this->code); - this->code = nullptr; - - for (auto c : this->cowns) - { - rt::release_cown(c); - rt::remove_reference(nullptr, c); - } - this->cowns.clear(); - } - - void Scheduler::add(std::shared_ptr behavior) + void Scheduler::add(rt::core::behavior_ptr behavior) { - assert(behavior->status == Behavior::Status::New); + assert(behavior->status == rt::core::Behavior::Status::New); // TODO add a testing mode that selects based on a seed for (auto cown : behavior->cowns) @@ -642,7 +590,7 @@ namespace verona::interpreter { auto pending = cown_info->second; // If a behavior is pending, set the successor - if (pending->status != Behavior::Status::Done) + if (pending->status != rt::core::Behavior::Status::Done) { if (pending->succ.insert(behavior).second) { @@ -658,12 +606,12 @@ namespace verona::interpreter if (behavior->pred_ctn == 0) { this->ready.push_back(behavior); - behavior->status = Behavior::Status::Ready; + behavior->status = rt::core::Behavior::Status::Ready; ss << "New behavior `" << behavior->name() << "` is ready"; } else { - behavior->status = Behavior::Status::Pending; + behavior->status = rt::core::Behavior::Status::Pending; ss << "New behavior `" << behavior->name() << "` is pending"; } @@ -677,9 +625,9 @@ namespace verona::interpreter // thereby also deleting the trieste nodes. rt::hack_inc_rc(main_function); // :notes: I imagine a world without ugly c++ :notes: - auto behavior = std::make_shared( + auto behavior = std::make_shared( main_function, std::vector{}); - behavior->status = Behavior::Status::Ready; + behavior->status = rt::core::Behavior::Status::Ready; // Seriously, why do we use this language? The memory problems I currently // have could easly be avoided. while (behavior) @@ -697,7 +645,7 @@ namespace verona::interpreter rt::remove_reference(nullptr, main_function); } - void Scheduler::complete(std::shared_ptr behavior) + void Scheduler::complete(rt::core::behavior_ptr behavior) { behavior->complete(); for (auto succ : behavior->succ) @@ -705,7 +653,7 @@ namespace verona::interpreter succ->pred_ctn -= 1; if (succ->pred_ctn == 0) { - succ->status = Behavior::Status::Ready; + succ->status = rt::core::Behavior::Status::Ready; this->ready.push_back(succ); } } @@ -720,7 +668,7 @@ namespace verona::interpreter mermaid->draw_schedule(this->ready, message); } - std::shared_ptr Scheduler::get_next() + rt::core::behavior_ptr Scheduler::get_next() { if (this->ready.empty()) { diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 835050e..55ddf0a 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -1,5 +1,7 @@ #pragma once +#include "../rt/behavior.h" + #include #include #include @@ -44,97 +46,25 @@ namespace verona::interpreter } }; - class Behavior - { - friend class rt::ui::ScheduleDiagram; - - public: - enum class Status - { - New, - Pending, - Ready, - Running, - Done, - }; - - static std::string status_to_string(Status status) - { - switch (status) - { - case Status::New: - return "New"; - case Status::Pending: - return "Pending"; - case Status::Ready: - return "Ready"; - case Status::Running: - return "Running"; - case Status::Done: - return "Done"; - default: - return "Unknown"; - } - } - - Status status; - - private: - // Static member for naming - static int s_behavior_counter; - - // A unique ID, this is used for drawing and naming, it isn't needed for - // scheduling. - int id; - - // The IDs of the cowns this behavior is waiting on. This is used to create - // a better mermaid diagram, it isn't needed for scheduling. - std::map ordered_cown; - - public: - // The cowns as they were passed in to the cown. These have to be provided - // to the new Interpreter to populate the frame - std::vector cowns; - // This uses a function object opposed to a Bytecode* to not leak memory - rt::objects::DynObject* code; - // The number of behaviors that this behavior is waiting on - int pred_ctn = 0; - // Behaviors which are waiting on this behavior. These will be notified once - // this behavior completes - std::set> succ; - - Behavior( - rt::objects::DynObject* code_, - std::vector cowns_); - - std::string name(); - std::string id_str(); - - Bytecode* spawn(); - // This completes the behavior by releasing all cowns - // decreffing all held objects and informing its successors. - void complete(); - }; - class Scheduler { // All behaviors that are ready to run - std::vector> ready = {}; + std::vector ready = {}; // A map from cowns to the last behavior that is waiting on them. // // The cowns in the key are weak pointers, they should never be // dereferenced. - std::unordered_map> - cowns = {}; + std::unordered_map cowns = + {}; public: - void add(std::shared_ptr behavior); + void add(rt::core::behavior_ptr behavior); void start(Bytecode* main); private: - void complete(std::shared_ptr behavior); + void complete(rt::core::behavior_ptr behavior); void draw_scedule(std::string message); - std::shared_ptr get_next(); + rt::core::behavior_ptr get_next(); }; } diff --git a/src/rt/behavior.h b/src/rt/behavior.h new file mode 100644 index 0000000..d031bdb --- /dev/null +++ b/src/rt/behavior.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include + +namespace verona::interpreter +{ + struct Bytecode; +} + +namespace rt::objects +{ + class DynObject; +} // namespace rt::objects + +namespace rt::ui +{ + class ScheduleDiagram; +} + +namespace rt::core +{ + class Behavior + { + friend class rt::ui::ScheduleDiagram; + + public: + enum class Status + { + New, + Pending, + Ready, + Running, + Done, + }; + + static std::string status_to_string(Status status) + { + switch (status) + { + case Status::New: + return "New"; + case Status::Pending: + return "Pending"; + case Status::Ready: + return "Ready"; + case Status::Running: + return "Running"; + case Status::Done: + return "Done"; + default: + return "Unknown"; + } + } + + Status status; + + private: + // Static member for naming + static int s_behavior_counter; + + // A unique ID, this is used for drawing and naming, it isn't needed for + // scheduling. + int id; + + // The IDs of the cowns this behavior is waiting on. This is used to create + // a better mermaid diagram, it isn't needed for scheduling. + std::map ordered_cown; + + public: + // The cowns as they were passed in to the cown. These have to be provided + // to the new Interpreter to populate the frame + std::vector cowns; + // This uses a function object opposed to a Bytecode* to not leak memory + objects::DynObject* code; + // The number of behaviors that this behavior is waiting on + int pred_ctn = 0; + // Behaviors which are waiting on this behavior. These will be notified once + // this behavior completes + std::set> succ; + + Behavior( + objects::DynObject* code_, std::vector cowns_); + + std::string name(); + std::string id_str(); + + verona::interpreter::Bytecode* spawn(); + // This completes the behavior by releasing all cowns + // decreffing all held objects + void complete(); + }; + + typedef std::shared_ptr behavior_ptr; +} // namespace rt::core diff --git a/src/rt/core/behavior.cc b/src/rt/core/behavior.cc new file mode 100644 index 0000000..c59019f --- /dev/null +++ b/src/rt/core/behavior.cc @@ -0,0 +1,62 @@ +#include "../behavior.h" + +#include "../rt.h" + +#include +#include + +namespace rt::core +{ + int Behavior::s_behavior_counter = 0; + + Behavior::Behavior( + rt::objects::DynObject* code_, std::vector cowns_) + : id(s_behavior_counter++), cowns(cowns_), code(code_) + { + for (auto c : cowns) + { + this->ordered_cown[rt::get_cown_id(c)] = c; + } + } + + std::string Behavior::name() + { + std::stringstream ss; + ss << "Behavior_" << this->id; + return ss.str(); + } + + std::string Behavior::id_str() + { + std::stringstream ss; + ss << "B" << this->id; + return ss.str(); + } + + verona::interpreter::Bytecode* Behavior::spawn() + { + assert(this->status == Status::Ready); + this->status = Status::Running; + + for (auto c : this->cowns) + { + rt::aquire_cown(c); + } + + return rt::try_get_bytecode(this->code).value(); + } + + void Behavior::complete() + { + this->status = Status::Done; + rt::remove_reference(nullptr, this->code); + this->code = nullptr; + + for (auto c : this->cowns) + { + rt::release_cown(c); + rt::remove_reference(nullptr, c); + } + this->cowns.clear(); + } +} // namespace rt::core diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 1394a61..b8fc5d8 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -1,3 +1,4 @@ +#include "../behavior.h" #include "../core.h" #include "../rt.h" @@ -412,8 +413,7 @@ namespace rt::core } // when auto behavior = frame->stack_pop("behavior"); - scheduler->add( - std::make_shared(behavior, cowns)); + scheduler->add(std::make_shared(behavior, cowns)); // 1. Create `Behavior` (ByteCodeFunc, [Cowns]) object // 2. Inform Scheduler about `Behavior` diff --git a/src/rt/ui.h b/src/rt/ui.h index fc87ddf..5e8ee5c 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -1,6 +1,6 @@ #pragma once -#include "../lang/interpreter.h" +#include "behavior.h" #include "objects/visit.h" #include @@ -97,8 +97,7 @@ namespace rt::ui std::vector& roots, std::string message) override; void draw_schedule( - std::vector> behaviors, - std::string message); + std::vector behaviors, std::string message); void highlight( std::string message, diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 7fdbfa7..3407b84 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -10,8 +10,6 @@ #include #include -typedef std::shared_ptr behavior_ptr; - namespace rt::ui { namespace fs = std::filesystem; @@ -165,7 +163,8 @@ namespace rt::ui return ss.str(); } - void draw_cown(rt::objects::DynObject* cown_obj, behavior_ptr behavior) + void + draw_cown(rt::objects::DynObject* cown_obj, core::behavior_ptr behavior) { assert(cown_obj->get_prototype() == core::cownPrototypeObject()); core::CownObject* cown = reinterpret_cast(cown_obj); @@ -186,13 +185,13 @@ namespace rt::ui out << std::endl; } - void draw_behavior(behavior_ptr behavior) + void draw_behavior(core::behavior_ptr behavior) { out << "subgraph " << behavior->id_str() << "[\" \"]" << std::endl; out << " info_" << behavior->id_str() << "([\"" << behavior->name() << "
Status: " - << verona::interpreter::Behavior::status_to_string(behavior->status) - << "\"])" << std::endl; + << core::Behavior::status_to_string(behavior->status) << "\"])" + << std::endl; for (auto [_, c] : behavior->ordered_cown) { @@ -204,13 +203,13 @@ namespace rt::ui auto background = ERROR_NODE_COLOR; switch (behavior->status) { - case verona::interpreter::Behavior::Status::Running: + case core::Behavior::Status::Running: background = BEHAVIOR_RUNNING_COLOR; break; - case verona::interpreter::Behavior::Status::Ready: + case core::Behavior::Status::Ready: background = BEHAVIOR_READY_COLOR; break; - case verona::interpreter::Behavior::Status::Pending: + case core::Behavior::Status::Pending: background = BEHAVIOR_PENDING_COLOR; break; } @@ -218,7 +217,7 @@ namespace rt::ui << std::endl; } - void draw_dependencies(behavior_ptr behavior) + void draw_dependencies(core::behavior_ptr behavior) { for (auto [_, cown_obj] : behavior->ordered_cown) { @@ -242,10 +241,10 @@ namespace rt::ui } } - std::map - aggregate_behaviors(std::vector pending) + std::map + aggregate_behaviors(std::vector pending) { - std::map behaviors; + std::map behaviors; while (!pending.empty()) { @@ -266,7 +265,7 @@ namespace rt::ui } public: - void draw(std::vector pending) + void draw(std::vector pending) { auto behaviors = aggregate_behaviors(pending); @@ -717,7 +716,7 @@ namespace rt::ui } void MermaidUI::draw_schedule( - std::vector behaviors, std::string message) + std::vector behaviors, std::string message) { this->prep_output(); From b80aebbfac5b3284d9620d096521da23af2e5aea Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 26 Mar 2025 15:32:06 +0100 Subject: [PATCH 10/23] Name attributes for behaviors --- src/lang/interpreter.cc | 10 +++++----- src/lang/interpreter.h | 6 +++++- src/lang/lang.h | 1 + src/lang/passes/grouping.cc | 21 +++++++++++++-------- src/lang/passes/parse.cc | 3 ++- src/rt/behavior.h | 8 ++++++-- src/rt/core.h | 14 +++++++++----- src/rt/core/behavior.cc | 21 ++++++++++++++++----- src/rt/core/builtin.cc | 15 ++++++++++++--- src/rt/ui/mermaid.cc | 2 +- tests/example_1.frank | 10 +++++++++- 11 files changed, 79 insertions(+), 32 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 955ae39..acc302f 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -607,12 +607,12 @@ namespace verona::interpreter { this->ready.push_back(behavior); behavior->status = rt::core::Behavior::Status::Ready; - ss << "New behavior `" << behavior->name() << "` is ready"; + ss << "New behavior `" << behavior->get_name() << "` is ready"; } else { behavior->status = rt::core::Behavior::Status::Pending; - ss << "New behavior `" << behavior->name() << "` is pending"; + ss << "New behavior `" << behavior->get_name() << "` is pending"; } this->draw_scedule(ss.str()); @@ -677,8 +677,8 @@ namespace verona::interpreter this->draw_scedule("Current Schedule:"); - // I hate c and c++ `unsigned` soo much... At least I'm getting paid to deal - // with this s... *suboptimal* language + // I hate c and c++ `unsigned` soo much... This is such an s... *suboptimal* + // language unsigned int selected = 0; while (true) { @@ -686,7 +686,7 @@ namespace verona::interpreter std::cout << "Available behaviors:" << std::endl; for (unsigned int idx = 0; idx < this->ready.size(); idx += 1) { - std::cout << "- " << idx << ": " << this->ready[idx]->name() + std::cout << "- " << idx << ": " << this->ready[idx]->get_name() << std::endl; } diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 55ddf0a..dbb941b 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -21,6 +21,7 @@ namespace rt::ui namespace verona::interpreter { + class Interpreter; struct Bytecode; void delete_bytecode(Bytecode* bytecode); @@ -55,7 +56,10 @@ namespace verona::interpreter // The cowns in the key are weak pointers, they should never be // dereferenced. std::unordered_map cowns = - {}; + {}; + // This feels hacky but also like the best solution? I can't even blame this + // on C++ + std::unordered_map running = {}; public: void add(rt::core::behavior_ptr behavior); diff --git a/src/lang/lang.h b/src/lang/lang.h index 8c2b65c..33f6a2c 100644 --- a/src/lang/lang.h +++ b/src/lang/lang.h @@ -21,6 +21,7 @@ inline const TokenDef Lookup{"lookup"}; inline const TokenDef Parens{"parens"}; inline const TokenDef Method{"method"}; inline const TokenDef When{"when"}; +inline const TokenDef Name{"Name"}; inline const TokenDef Op{"op"}; inline const TokenDef Rhs{"rhs"}; diff --git a/src/lang/passes/grouping.cc b/src/lang/passes/grouping.cc index 5cb3712..f498799 100644 --- a/src/lang/passes/grouping.cc +++ b/src/lang/passes/grouping.cc @@ -70,11 +70,12 @@ PassDef grouping() return create_from(When, _(When)) << _(Empty) << (Group << Parens) << _(Block); }, - T(When)[When] - << ((T(Group) << End) * - (T(Group) - << (T(Parens)[Parens] << ((~(T(List) << T(Ident)++[List]))))) * - (T(Group) << T(Block)[Block])) >> + ~(T(Group) << T(Name)[Name]) * + (T(When)[When] + << ((T(Group)) * + (T(Group) + << (T(Parens)[Parens] << ((~(T(List) << T(Ident)++[List]))))) * + (T(Group) << T(Block)[Block]))) >> [](auto& _) { auto when_name = new_when_ident(); @@ -87,10 +88,14 @@ PassDef grouping() // ===================================== // Call `spawn_behavior()` - auto list = create_from(List, _(Parens)) - << (Ident ^ when_name) << clone(_[List]); + auto args = create_from(List, _(Parens)) << (Ident ^ when_name); + if (_(Name)) + { + args = args << create_from(String, _(Name)); + } + args = args << clone(_[List]); auto call = create_from(Call, _(When)) - << (Ident ^ "spawn_behavior") << list; + << (Ident ^ "spawn_behavior") << args; // Put it all together return Seq << when_def << call; diff --git a/src/lang/passes/parse.cc b/src/lang/passes/parse.cc index 5922516..ca055fc 100644 --- a/src/lang/passes/parse.cc +++ b/src/lang/passes/parse.cc @@ -5,7 +5,7 @@ namespace verona::wf using namespace trieste::wf; inline const auto parse_tokens = - Ident | Lookup | Empty | Drop | Move | Null | String | Parens; + Ident | Lookup | Empty | Drop | Move | Null | String | Parens | Name; inline const auto parse_groups = Group | Assign | If | Else | Block | For | Func | List | Return | While | When; @@ -96,6 +96,7 @@ trieste::Parse parser() "(?:#[^\\n\\r]*)" >> [](auto&) {}, "def\\b" >> [](auto& m) { m.seq(Func); }, + "@name\\(\"([0-9A-Za-z_]+)\"\\)" >> [](auto& m) { m.add(Name, 1); }, "when\\b" >> [](auto& m) { m.seq(When); }, "\\(" >> [](auto& m) { m.push(Parens); }, "\\)" >> diff --git a/src/rt/behavior.h b/src/rt/behavior.h index d031bdb..665397c 100644 --- a/src/rt/behavior.h +++ b/src/rt/behavior.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -64,6 +65,7 @@ namespace rt::core // A unique ID, this is used for drawing and naming, it isn't needed for // scheduling. int id; + std::string name; // The IDs of the cowns this behavior is waiting on. This is used to create // a better mermaid diagram, it isn't needed for scheduling. @@ -82,9 +84,11 @@ namespace rt::core std::set> succ; Behavior( - objects::DynObject* code_, std::vector cowns_); + objects::DynObject* code_, + std::vector cowns_, + std::optional name_ = std::nullopt); - std::string name(); + std::string get_name(); std::string id_str(); verona::interpreter::Bytecode* spawn(); diff --git a/src/rt/core.h b/src/rt/core.h index 3780d7f..94cbdb2 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -284,20 +284,24 @@ namespace rt::core int id; public: - CownObject(objects::DynObject* obj, std::optional name_ = std::nullopt) + CownObject( + objects::DynObject* obj, std::optional name_ = std::nullopt) : objects::DynObject(cownPrototypeObject(), objects::cown_region) { id = s_id_counter++; - + status = Status::Pending; auto old = set("value", obj); assert(!old); - if (name_) { + if (name_) + { name = name_.value(); - } else { + } + else + { std::stringstream ss; - ss << "id << ">" << std::endl; + ss << "id << ">"; name = ss.str(); } } diff --git a/src/rt/core/behavior.cc b/src/rt/core/behavior.cc index c59019f..185333c 100644 --- a/src/rt/core/behavior.cc +++ b/src/rt/core/behavior.cc @@ -10,20 +10,31 @@ namespace rt::core int Behavior::s_behavior_counter = 0; Behavior::Behavior( - rt::objects::DynObject* code_, std::vector cowns_) + rt::objects::DynObject* code_, + std::vector cowns_, + std::optional name_) : id(s_behavior_counter++), cowns(cowns_), code(code_) { for (auto c : cowns) { this->ordered_cown[rt::get_cown_id(c)] = c; } + + if (name_) + { + this->name = name_.value(); + } + else + { + std::stringstream ss; + ss << "Behavior_" << this->id; + this->name = ss.str(); + } } - std::string Behavior::name() + std::string Behavior::get_name() { - std::stringstream ss; - ss << "Behavior_" << this->id; - return ss.str(); + return this->name; } std::string Behavior::id_str() diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index b8fc5d8..71ac1a2 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -401,8 +401,6 @@ namespace rt::core void concurrency_builtins(verona::interpreter::Scheduler* scheduler) { add_builtin("spawn_behavior", [=](auto frame, auto args) { - std::cout << "Yay, what a day to live :D" << std::endl; - // cowns (Stored on the stack in reverse order) // -1 since the first argument is the actual behavior std::vector cowns = {}; @@ -411,9 +409,20 @@ namespace rt::core auto value = frame->stack_pop("cown"); cowns.push_back(value); } + + std::optional name; + // The last argument might be a name for the behavior + if (cowns.back()->get_prototype() == rt::core::stringPrototypeObject()) + { + auto name_obj = cowns.back(); + name = dynamic_cast(name_obj)->as_key(); + rt::remove_reference(frame->object(), name_obj); + cowns.pop_back(); + } // when auto behavior = frame->stack_pop("behavior"); - scheduler->add(std::make_shared(behavior, cowns)); + scheduler->add( + std::make_shared(behavior, cowns, name)); // 1. Create `Behavior` (ByteCodeFunc, [Cowns]) object // 2. Inform Scheduler about `Behavior` diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 3407b84..9943fad 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -188,7 +188,7 @@ namespace rt::ui void draw_behavior(core::behavior_ptr behavior) { out << "subgraph " << behavior->id_str() << "[\" \"]" << std::endl; - out << " info_" << behavior->id_str() << "([\"" << behavior->name() + out << " info_" << behavior->id_str() << "([\"" << behavior->get_name() << "
Status: " << core::Behavior::status_to_string(behavior->status) << "\"])" << std::endl; diff --git a/tests/example_1.frank b/tests/example_1.frank index ea09792..b0a7969 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -1,7 +1,10 @@ # For debugging: # mermaid_show_functions() -c1 = Cown(Region(), "c1") +r = Region() +c1 = Cown(r, "c1") +r = None + c2 = Cown(Region(), "c2") c3 = Cown(Region(), "c3") c4 = Cown(Region(), "c4") @@ -9,18 +12,23 @@ c5 = Cown(Region()) c6 = Cown(Region()) c7 = Cown(Region()) +@name("B1") when (c1, c2, c3): c3.value.c2 = c2 +@name("B2") when (c1, c2): c1.value = c2 +@name("B3") when(c6, c7): r = None +@name("B4") when(c5, c6): r = None +@name("B5") when (c2, c3, c4, c5): r = None From 6c4fa2ffecd8e2054eaf79b1a44a8220c06d808b Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 26 Mar 2025 16:07:29 +0100 Subject: [PATCH 11/23] "Trim" std::couts --- src/lang/interpreter.cc | 5 +++-- src/rt/core.h | 2 -- src/rt/objects/dyn_object.h | 1 - src/rt/objects/region.cc | 29 ----------------------------- src/rt/objects/region.h | 2 -- 5 files changed, 3 insertions(+), 36 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index acc302f..77b9393 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -119,7 +119,7 @@ namespace verona::interpreter if (node == Print) { // Console output - std::cout << node->location().view() << std::endl << std::endl; + std::cout << ">>> " << node->location().view() << std::endl; // Mermaid output std::vector roots{frame()->object()}; @@ -136,7 +136,6 @@ namespace verona::interpreter // ========================================== // Operators that should be printed // ========================================== - std::cout << "Op: " << node->type().str() << std::endl; if (node == CreateObject) { rt::objects::DynObject* obj = nullptr; @@ -683,6 +682,7 @@ namespace verona::interpreter while (true) { // Promt the user: + std::cout << std::endl; std::cout << "Available behaviors:" << std::endl; for (unsigned int idx = 0; idx < this->ready.size(); idx += 1) { @@ -711,6 +711,7 @@ namespace verona::interpreter // Sanity checks and preventing undefined behavior. if (selected < this->ready.size()) { + std::cout << std::endl; break; } } diff --git a/src/rt/core.h b/src/rt/core.h index 94cbdb2..4f80fd8 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -66,7 +66,6 @@ namespace rt::core assert(old == nullptr && "the stack already had a value"); stack_size += 1; - std::cout << "pushed " << value << " (" << info << ")" << std::endl; if (rc_add) { rt::add_reference(this, value); @@ -77,7 +76,6 @@ namespace rt::core { stack_size -= 1; auto value = erase(stack_name(stack_size)); - std::cout << "poped " << value << " (" << value << ")" << std::endl; return value; } diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index dbcaf6f..d8f04cb 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -123,7 +123,6 @@ namespace rt::objects if (!is_immutable() && r != nullptr) r->objects.erase(this); - std::cout << "Deallocate: " << get_name() << std::endl; } size_t get_rc() diff --git a/src/rt/objects/region.cc b/src/rt/objects/region.cc index cc445f7..4764f92 100644 --- a/src/rt/objects/region.cc +++ b/src/rt/objects/region.cc @@ -59,8 +59,6 @@ namespace rt::objects if (obj->region.get_ptr() == get_local_region()) { - std::cout << "Adding object to region: " << obj->get_name() - << " rc = " << obj->get_rc() << std::endl; rc_of_added_objects += obj->get_rc(); internal_references++; obj->region = {r}; @@ -72,8 +70,6 @@ namespace rt::objects auto obj_region = get_region(obj); if (obj_region == r) { - std::cout << "Adding internal reference to object: " << obj->get_name() - << std::endl; internal_references++; return false; } @@ -105,12 +101,6 @@ namespace rt::objects }); r->local_reference_count += rc_of_added_objects - internal_references; - - std::cout << "Added " << rc_of_added_objects - internal_references - << " to LRC of region" << std::endl; - std::cout << "Region LRC: " << r->local_reference_count << std::endl; - std::cout << "Internal references found: " << internal_references - << std::endl; } void remove_region_reference(Region* src, Region* target) @@ -137,8 +127,6 @@ namespace rt::objects if (src) { assert(target->parent == src); - std::cout << "Removing parent reference from region: " << src << " to " - << target << std::endl; src->direct_subregions.erase(target->bridge); if (target->combined_lrc() != 0) { @@ -281,15 +269,6 @@ namespace rt::objects return; } - if (to_close_reg) - { - std::cout << "Cleaning LRCs and closing " << to_close_reg << std::endl; - } - else - { - std::cout << "Cleaning LRCs" << std::endl; - } - for (auto r : dirty_regions) { r->local_reference_count = 0; @@ -342,8 +321,6 @@ namespace rt::objects for (auto r : dirty_regions) { - std::cout << "Corrected LRC of " << r << " to " - << r->local_reference_count << std::endl; r->is_lrc_dirty = false; if (r->combined_lrc() == 0) { @@ -436,7 +413,6 @@ namespace rt::objects RegionObject* obj = new RegionObject(r); r->bridge = obj; r->local_reference_count++; - std::cout << "Created region " << r << " with bridge " << obj << std::endl; return obj; } @@ -451,8 +427,6 @@ namespace rt::objects if (r != get_local_region() && r != cown_region) { to_collect.insert(r); - std::cout << "Collecting region: " << r << " with bridge: " << r->bridge - << std::endl; } } } @@ -473,9 +447,6 @@ namespace rt::objects for (auto obj : src->objects) { auto r = get_region(obj); - std::cout << "Moving object: " << obj - << " with region bridge: " << r->bridge - << " to region with bridge: " << sink->bridge << std::endl; obj->region = {sink}; sink->objects.insert(obj); src->objects.erase(obj); diff --git a/src/rt/objects/region.h b/src/rt/objects/region.h index 25cecef..f2bdb41 100644 --- a/src/rt/objects/region.h +++ b/src/rt/objects/region.h @@ -74,8 +74,6 @@ namespace rt::objects ~Region() { - std::cout << "Destroying region: " << this << " with bridge " - << this->bridge << std::endl; } size_t combined_lrc() From 7ae5359cb6d75824c0e4e75c49ca5371f0c0e686 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 26 Mar 2025 16:59:23 +0100 Subject: [PATCH 12/23] Pausing interpreter on schedule (Super bad code) --- src/lang/interpreter.cc | 95 +++++++++++++++++++++++++++++++++-------- src/lang/interpreter.h | 16 ++++++- src/rt/core/behavior.cc | 8 ++-- tests/example_1.frank | 3 ++ 4 files changed, 100 insertions(+), 22 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 77b9393..bf7ede0 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -61,9 +61,15 @@ namespace verona::interpreter class Interpreter { + bool paused = false; rt::ui::UI* ui; std::vector frame_stack; + InterpreterFrame* top_frame() + { + return frame_stack.back(); + } + InterpreterFrame* push_stack_frame(trieste::Node body) { FrameObj* parent_obj = nullptr; @@ -480,19 +486,27 @@ namespace verona::interpreter } public: - Interpreter(rt::ui::UI* ui_) : ui(ui_) {} - - void - run(trieste::Node main, std::vector start_stack) + Interpreter( + rt::ui::UI* ui_, + trieste::Node block, + std::vector start_stack) + : ui(ui_) { - auto frame = push_stack_frame(main); + auto frame = push_stack_frame(block); for (auto elem : start_stack) { frame->frame->stack_push(elem, "staring stack"); } + } + + // Returns true if this interpreter is done, otherwise false. + bool resume() + { + this->paused = false; + auto frame = top_frame(); - while (frame) + while (!this->paused && frame) { const auto action = run_stmt(*frame->ip); @@ -555,6 +569,15 @@ namespace verona::interpreter frame = pop_stack_frame(); } } + + return !this->paused; + } + + // This will pause the interpreter once it's done processing the current + // statement. + void pause() + { + this->paused = true; } }; @@ -614,7 +637,11 @@ namespace verona::interpreter ss << "New behavior `" << behavior->get_name() << "` is pending"; } - this->draw_scedule(ss.str()); + this->next_schedule_msg = ss.str(); + if (this->current_int) + { + this->current_int->pause(); + } } void Scheduler::start(Bytecode* main_block) @@ -625,18 +652,39 @@ namespace verona::interpreter rt::hack_inc_rc(main_function); // :notes: I imagine a world without ugly c++ :notes: auto behavior = std::make_shared( - main_function, std::vector{}); + main_function, std::vector{}, "Main function"); behavior->status = rt::core::Behavior::Status::Ready; + this->ready.push_back(behavior); // Seriously, why do we use this language? The memory problems I currently // have could easly be avoided. while (behavior) { - auto block = behavior->spawn(); + Interpreter* inter; + if (behavior->status == rt::core::Behavior::Status::Ready) + { + auto block = behavior->spawn(); - Interpreter inter(rt::ui::globalUI()); - inter.run(block->body, behavior->cowns); + inter = + new Interpreter(rt::ui::globalUI(), block->body, behavior->cowns); + this->running[behavior] = inter; + } + else if (behavior->status == rt::core::Behavior::Status::Running) + { + inter = this->running[behavior]; + assert(inter); + } + else + { + assert(false && "HOW DID IT BREAK THIS BADLY?"); + } - this->complete(behavior); + this->current_int = inter; + // TODO: + // I believe, this would be the right place to only run one step at a time + if (inter->resume()) + { + this->complete(behavior); + } behavior = this->get_next(); } @@ -647,6 +695,8 @@ namespace verona::interpreter void Scheduler::complete(rt::core::behavior_ptr behavior) { behavior->complete(); + std::erase(this->ready, behavior); + for (auto succ : behavior->succ) { succ->pred_ctn -= 1; @@ -661,6 +711,12 @@ namespace verona::interpreter void Scheduler::draw_scedule(std::string message) { + if (this->next_schedule_msg) + { + message = this->next_schedule_msg.value(); + this->next_schedule_msg.reset(); + } + auto ui = rt::ui::globalUI(); assert(ui->is_mermaid()); auto mermaid = reinterpret_cast(ui); @@ -686,8 +742,14 @@ namespace verona::interpreter std::cout << "Available behaviors:" << std::endl; for (unsigned int idx = 0; idx < this->ready.size(); idx += 1) { - std::cout << "- " << idx << ": " << this->ready[idx]->get_name() - << std::endl; + auto b = this->ready[idx]; + std::cout << "- " << idx << ": " << b->get_name(); + + if (b->status == rt::core::Behavior::Status::Running) + { + std::cout << " (continue)"; + } + std::cout << std::endl; } // Get user input @@ -717,9 +779,8 @@ namespace verona::interpreter } } - auto removed = this->ready[selected]; - this->ready.erase(this->ready.begin() + selected); - return removed; + auto behavior = this->ready[selected]; + return behavior; } } // namespace verona::interpreter diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index dbb941b..9dcf9cc 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -47,6 +47,7 @@ namespace verona::interpreter } }; + // FIXME: The implementation of this should probably be in a different file... class Scheduler { // All behaviors that are ready to run @@ -56,11 +57,24 @@ namespace verona::interpreter // The cowns in the key are weak pointers, they should never be // dereferenced. std::unordered_map cowns = - {}; + {}; // This feels hacky but also like the best solution? I can't even blame this // on C++ std::unordered_map running = {}; + // FIXME: To not pause twice for a new behavior (schedule::Add) and + // inter->pause() we'll store a message here for the next + // draw scedule. + // TO be clear, this is super duper hacky and shouldn't be done + // like this. + std::optional next_schedule_msg; + + // FIXME: + // This should likely be gotten by requesting the current + // behavior in the runtime and then looking up the interpreter + // from the behavior. But no, this is faster; + Interpreter* current_int; + public: void add(rt::core::behavior_ptr behavior); diff --git a/src/rt/core/behavior.cc b/src/rt/core/behavior.cc index 185333c..99cc984 100644 --- a/src/rt/core/behavior.cc +++ b/src/rt/core/behavior.cc @@ -17,18 +17,18 @@ namespace rt::core { for (auto c : cowns) { - this->ordered_cown[rt::get_cown_id(c)] = c; + ordered_cown[rt::get_cown_id(c)] = c; } if (name_) { - this->name = name_.value(); + name = name_.value(); } else { std::stringstream ss; - ss << "Behavior_" << this->id; - this->name = ss.str(); + ss << "Behavior_" << id; + name = ss.str(); } } diff --git a/tests/example_1.frank b/tests/example_1.frank index b0a7969..2c5ef82 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -16,6 +16,9 @@ c7 = Cown(Region()) when (c1, c2, c3): c3.value.c2 = c2 + when (c1): + pass() + @name("B2") when (c1, c2): c1.value = c2 From d8001c5a33f65743cf1c95ebc910bb77b24ebe5d Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 27 Mar 2025 11:51:14 +0100 Subject: [PATCH 13/23] bare-bones print --- src/rt/core/builtin.cc | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index fb28da2..97db93a 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -352,6 +352,19 @@ namespace rt::core return result_obj; }); + + add_builtin("print", [](auto frame, auto args) { + if (args != 1) + { + ui::error("print() expected 1 argument"); + } + + auto value = frame->stack_pop("value to print"); + std::cout << "print: " << value->get_name() << std::endl; + rt::remove_reference(frame->object(), value); + + return std::nullopt; + }); } void pragma_builtins() From c1337b61c8eb943f49a6619e782cdfef6e074faa Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 27 Mar 2025 12:13:41 +0100 Subject: [PATCH 14/23] no longer prints quotation marks --- src/rt/core/builtin.cc | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 97db93a..c5c1de5 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -359,8 +359,15 @@ namespace rt::core ui::error("print() expected 1 argument"); } - auto value = frame->stack_pop("value to print"); - std::cout << "print: " << value->get_name() << std::endl; + auto value = frame->stack_pop("value to print"); + auto name = value->get_name(); + // Lazy way of dealing with stringPrototypeObject + if (name[0] == '\"') + { + name.erase(0, 1); + name.erase(name.size() - 1); + } + std::cout << name << std::endl; rt::remove_reference(frame->object(), value); return std::nullopt; From 8a614cd6dc7d7b9e524ee59b3e3fc5d689fe738a Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 27 Mar 2025 14:11:45 +0100 Subject: [PATCH 15/23] Making the local region exchangeable --- src/lang/interpreter.cc | 33 +++++++++++++++++++++++++++++---- src/rt/behavior.h | 22 +++++++++++++++++++++- src/rt/core/behavior.cc | 30 ++++++++++++++++++++++++++++++ src/rt/core/builtin.cc | 24 +++++------------------- src/rt/objects/dyn_object.h | 3 +-- src/rt/objects/region.cc | 21 ++++++++++++++------- src/rt/objects/region.h | 17 ++++++++++++++--- src/rt/rt.cc | 7 +++++++ src/rt/rt.h | 3 +++ src/rt/ui/mermaid.cc | 2 +- 10 files changed, 125 insertions(+), 37 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index bf7ede0..995be47 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -64,6 +64,7 @@ namespace verona::interpreter bool paused = false; rt::ui::UI* ui; std::vector frame_stack; + rt::core::behavior_ptr behavior; InterpreterFrame* top_frame() { @@ -489,15 +490,37 @@ namespace verona::interpreter Interpreter( rt::ui::UI* ui_, trieste::Node block, - std::vector start_stack) - : ui(ui_) + std::vector start_stack, + rt::core::behavior_ptr behavior_) + : ui(ui_), behavior(behavior_) { + // FIXME: Oh no, we need to set the local region before running this but + // the interpreter would then need to know about behaviors right? + // Maybe not? What if the scheduler sets the behavior before doing this? + // That could work, I don't like it but it could + // + // Looking at this, I believe it would be better to let the interpreter + // know about behaviors + + // There is a question where the active behavior should be set. + // + // Python mixes the runtime and interpreter a bit more. There the runtime + // has access to the interpreter state. So, it would be possible to store + // the behavior in the interpreter state and have it accessible to cowns. + // + // However, in FrankenScript the runtime is more passive, meaning that + // the interpreter drives the runtime and provides all needed information. + auto old_behavior = rt::get_active_behavior(); + rt::set_active_behavior(this->behavior); + auto frame = push_stack_frame(block); for (auto elem : start_stack) { frame->frame->stack_push(elem, "staring stack"); } + + rt::set_active_behavior(old_behavior); } // Returns true if this interpreter is done, otherwise false. @@ -506,6 +529,8 @@ namespace verona::interpreter this->paused = false; auto frame = top_frame(); + rt::set_active_behavior(this->behavior); + while (!this->paused && frame) { const auto action = run_stmt(*frame->ip); @@ -664,8 +689,8 @@ namespace verona::interpreter { auto block = behavior->spawn(); - inter = - new Interpreter(rt::ui::globalUI(), block->body, behavior->cowns); + inter = new Interpreter( + rt::ui::globalUI(), block->body, behavior->cowns, behavior); this->running[behavior] = inter; } else if (behavior->status == rt::core::Behavior::Status::Running) diff --git a/src/rt/behavior.h b/src/rt/behavior.h index 665397c..1989f75 100644 --- a/src/rt/behavior.h +++ b/src/rt/behavior.h @@ -14,6 +14,7 @@ namespace verona::interpreter namespace rt::objects { class DynObject; + struct Region; } // namespace rt::objects namespace rt::ui @@ -56,7 +57,21 @@ namespace rt::core } } - Status status; + static void set_active_behavior(std::shared_ptr); + static std::shared_ptr get_active_behavior(); + + private: + // This map is a collection of all behaviors that have started running and + // therefore also have a local region. This is needed here for the lovely + // mermaid output. This uses an ordered map in the hope that the diagram + // will keep the same layout every iteration. + // + // It uses behavior pointers since it's being updated from inside methods + // where `this` is a pointer and not a `shared_ptr`. This should be fine + // since each behavior should call `complete()` before being freed thereby + // also updating this list. + static std::map s_running_behaviors; + static std::shared_ptr s_active_behavior; private: // Static member for naming @@ -71,7 +86,12 @@ namespace rt::core // a better mermaid diagram, it isn't needed for scheduling. std::map ordered_cown; + // The local region of this behavior. This has to be swapped into the global + // `local_region` when this behavior runs. + objects::Region* local_region; + public: + Status status; // The cowns as they were passed in to the cown. These have to be provided // to the new Interpreter to populate the frame std::vector cowns; diff --git a/src/rt/core/behavior.cc b/src/rt/core/behavior.cc index 99cc984..9b75d03 100644 --- a/src/rt/core/behavior.cc +++ b/src/rt/core/behavior.cc @@ -1,13 +1,35 @@ #include "../behavior.h" +#include "../objects/region.h" #include "../rt.h" #include #include +namespace rt::objects +{ + void set_local_region(Region* region); +} + namespace rt::core { int Behavior::s_behavior_counter = 0; + std::map Behavior::s_running_behaviors = {}; + std::shared_ptr Behavior::s_active_behavior = nullptr; + + void Behavior::set_active_behavior(std::shared_ptr active) + { + s_active_behavior = active; + if (active) + { + objects::set_local_region(active->local_region); + } + } + + std::shared_ptr Behavior::get_active_behavior() + { + return s_active_behavior; + } Behavior::Behavior( rt::objects::DynObject* code_, @@ -51,9 +73,15 @@ namespace rt::core for (auto c : this->cowns) { + // TODO, store aquireing behavior and check the behavior later rt::aquire_cown(c); } + this->local_region = objects::Region::new_local_region(); + + // Add self to running behaviors to have it also drawn as a local region. + s_running_behaviors[this->id] = this; + return rt::try_get_bytecode(this->code).value(); } @@ -69,5 +97,7 @@ namespace rt::core rt::remove_reference(nullptr, c); } this->cowns.clear(); + + this->s_running_behaviors.erase(this->id); } } // namespace rt::core diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 71ac1a2..69614d8 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -424,25 +424,11 @@ namespace rt::core scheduler->add( std::make_shared(behavior, cowns, name)); - // 1. Create `Behavior` (ByteCodeFunc, [Cowns]) object - // 2. Inform Scheduler about `Behavior` - // - // In Scheduler: - // 3. Scheduler waits until all cowns are available (dependency graph) - // - The "draw the rest of the owl step" - // 4. Create a new interpreter from `bytecode.value()->body` - // - `rt::move_reference(NULL, new_interpreter->frame(), body)` - // 5. Aquire the cowns (Set them to aquired with this interpreter id) - // 6. Push the cowns on to the interpreter frame - // - `rt::move_reference(NULL, new_interpreter->frame(), cown);` - // 7. Start interpreter - // - // In interpreter (Already done by the lowering pass): - // 8. Cowns from the stack are assigned to the defined names on the frame - // - // After completion in Scheduler - // 9. Release cowns - // 10. `rt::remove_reference` the `behavior.func` + // @Max, Interesting for your report: Some kind of ownership transfer is + // needed here. Freezing is "the easiest" untill we get into the mess that + // function objects in cpython are. It could be interesting to see if we + // can't just transfer ownership to the behavior region. + freeze(behavior); return std::nullopt; }); diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index d8f04cb..a5f3406 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -30,7 +30,6 @@ namespace rt::objects Region* get_region(DynObject* obj); Region* get_local_region(); - void set_local_region(Region* region); // Representation of objects class DynObject @@ -115,7 +114,7 @@ namespace rt::objects { std::stringstream stream; stream << this; - stream << " still has references"; + stream << " still has references"; ui::error(stream.str(), this); } diff --git a/src/rt/objects/region.cc b/src/rt/objects/region.cc index 4764f92..883ec88 100644 --- a/src/rt/objects/region.cc +++ b/src/rt/objects/region.cc @@ -11,15 +11,21 @@ namespace rt::objects return obj->region.get_ptr(); } - thread_local objects::RegionPointer local_region = new Region(); + thread_local Region* local_region = Region::new_local_region(); + // FIXME: This should really be a static method on the region and not this + // free floating one IMO Region* get_local_region() { return local_region; } + // This should be a private static function in the Region to controll that + // only behaviors can set the value. There is no other instance where this + // should be called. void set_local_region(Region* region) { + assert(region->is_local_region); local_region = region; } @@ -118,7 +124,7 @@ namespace rt::objects if (target == cown_region) return; - if (src == get_local_region()) + if (src->is_local_region) { Region::dec_lrc(target); return; @@ -153,13 +159,13 @@ namespace rt::objects if (src_region == target_region) return; - if (src_region == get_local_region()) + if (src_region->is_local_region) { Region::inc_lrc(target_region); return; } - if (target_region == get_local_region()) + if (target_region->is_local_region) { add_to_region(src_region, target, source); return; @@ -276,6 +282,7 @@ namespace rt::objects bool continue_visit = true; std::set seen; + // FIXME: This is just completly broken... visit(get_local_region(), [&](Edge e) { auto src = e.src; auto dst = e.target; @@ -424,7 +431,7 @@ namespace rt::objects // Needs to check for sub_region_reference_count for send, but not // deallocate. - if (r != get_local_region() && r != cown_region) + if (!r->is_local_region && r != cown_region) { to_collect.insert(r); } @@ -517,7 +524,7 @@ namespace rt::objects assert(bridge->get_prototype() == objects::regionPrototypeObject()); auto r = get_region(bridge); - assert(r != get_local_region()); + assert(!r->is_local_region); if (r->parent != nullptr) { @@ -538,6 +545,6 @@ namespace rt::objects auto old_proto = bridge->set_prototype(nullptr); remove_reference(bridge, old_proto); // Move all objects in the region - move_objects(r, local_region); + move_objects(r, get_local_region()); } } diff --git a/src/rt/objects/region.h b/src/rt/objects/region.h index f2bdb41..e4dad61 100644 --- a/src/rt/objects/region.h +++ b/src/rt/objects/region.h @@ -50,6 +50,8 @@ namespace rt::objects // part of the LRC or other pointers in this struct. bool is_lrc_dirty = false; + bool is_local_region = false; + // For nested regions, this points at the owning region. // This guarantees that the regions for trees. Region* parent{nullptr}; @@ -72,15 +74,20 @@ namespace rt::objects // Bridge children of the region std::set direct_subregions{}; - ~Region() - { - } + ~Region() {} size_t combined_lrc() { return local_reference_count + sub_region_reference_count; } + static Region* new_local_region() + { + auto r = new Region(); + r->is_local_region = true; + return r; + } + static void action(Region*); static void dec_lrc(Region* r) @@ -254,9 +261,13 @@ namespace rt::objects // encode special regions. using RegionPointer = utils::TaggedPointer; + // The immutable region stays the same, regardless of which interpreter + // or behavior is currently running inline Region immutable_region_impl; inline constexpr Region* immutable_region{&immutable_region_impl}; + // The cown region stays the same, regardless of which interpreter + // or behavior is currently running inline Region cown_region_impl; inline constexpr Region* cown_region{&cown_region_impl}; } // namespace rt::objects diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 65a3cb1..3d0a539 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -374,4 +374,11 @@ namespace rt obj->change_rc(+1); } + rt::core::behavior_ptr get_active_behavior() { + return core::Behavior::get_active_behavior(); + } + void set_active_behavior(rt::core::behavior_ptr behavior) { + core::Behavior::set_active_behavior(behavior); + } + } // namespace rt diff --git a/src/rt/rt.h b/src/rt/rt.h index 751bca1..59c615e 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -77,4 +77,7 @@ namespace rt // This increases the rc without asking questions. Very much a // hack but I don't care anymore. void hack_inc_rc(objects::DynObject* obj); + + rt::core::behavior_ptr get_active_behavior(); + void set_active_behavior(rt::core::behavior_ptr behavior); } // namespace rt diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 9943fad..5338dae 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -343,7 +343,7 @@ namespace rt::ui { return e.src != nullptr && e.target != nullptr && objects::get_region(e.src) != objects::get_region(e.target) && - objects::get_region(e.src) == objects::get_local_region(); + objects::get_region(e.src)->is_local_region; } std::string node_decoration(objects::DynObject* dst, bool reachable) From f226d97cf6fcf598895e31765d220cb58d72d845 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 27 Mar 2025 16:56:37 +0100 Subject: [PATCH 16/23] Drawing multiple local regions --- src/lang/interpreter.cc | 6 +-- src/rt/behavior.h | 4 ++ src/rt/ui.h | 2 + src/rt/ui/mermaid.cc | 97 +++++++++++++++++++++++++++++------------ 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 995be47..a24a0ce 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -125,12 +125,12 @@ namespace verona::interpreter // ========================================== if (node == Print) { + auto message = std::string(node->location().view()); // Console output - std::cout << ">>> " << node->location().view() << std::endl; + std::cout << ">>> " << message << std::endl; // Mermaid output - std::vector roots{frame()->object()}; - ui->output(roots, std::string(node->location().view())); + ui->output(message); // Continue return ExecNext{}; diff --git a/src/rt/behavior.h b/src/rt/behavior.h index 1989f75..529475a 100644 --- a/src/rt/behavior.h +++ b/src/rt/behavior.h @@ -19,14 +19,18 @@ namespace rt::objects namespace rt::ui { + class MermaidUI; class ScheduleDiagram; + class ObjectGraphDiagram; } namespace rt::core { class Behavior { + friend class rt::ui::MermaidUI; friend class rt::ui::ScheduleDiagram; + friend class rt::ui::ObjectGraphDiagram; public: enum class Status diff --git a/src/rt/ui.h b/src/rt/ui.h index 5e8ee5c..c8a3072 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -18,6 +18,7 @@ namespace rt::ui virtual void set_output_file(std::string path_) = 0; virtual void output(std::vector&, std::string) {} + virtual void output(std::string) {} virtual void highlight(std::string, std::vector&) {} @@ -95,6 +96,7 @@ namespace rt::ui void output( std::vector& roots, std::string message) override; + void output(std::string message) override; void draw_schedule( std::vector behaviors, std::string message); diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 5338dae..31e85d0 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -148,6 +148,21 @@ namespace rt::ui return {"[", "]"}; } + + std::string behavior_node_name(core::Behavior* behavior) + { + std::stringstream ss; + ss << "info_" << behavior->id_str(); + return ss.str(); + } + + void draw_behavior_info(core::Behavior* behavior) + { + out << " " << this->behavior_node_name(behavior) << "([\"" + << behavior->get_name() << "
Status: " + << core::Behavior::status_to_string(behavior->status) << "\"])" + << std::endl; + } }; class ScheduleDiagram : protected MermaidDiagram @@ -188,10 +203,7 @@ namespace rt::ui void draw_behavior(core::behavior_ptr behavior) { out << "subgraph " << behavior->id_str() << "[\" \"]" << std::endl; - out << " info_" << behavior->id_str() << "([\"" << behavior->get_name() - << "
Status: " - << core::Behavior::status_to_string(behavior->status) << "\"])" - << std::endl; + draw_behavior_info(behavior.get()); for (auto [_, c] : behavior->ordered_cown) { @@ -309,14 +321,18 @@ namespace rt::ui regions[objects::immutable_region].nodes.push_back(0); } - void draw(std::vector& roots) + void draw( + std::vector& roots, + std::map& behaviors) { // header this->draw_header(); out << "graph TD" << std::endl; out << " id0(None):::immutable" << std::endl; + draw_behavior_nodes(behaviors); draw_nodes(roots); + draw_behaviors(behaviors); draw_regions(); draw_taint(); draw_highlight(); @@ -488,7 +504,28 @@ namespace rt::ui indent.erase(indent.size() - 2); } - void draw_region(objects::Region* r, std::string& indent) + void draw_behavior_nodes(std::map& behaviors) + { + for (auto [id, b] : behaviors) + { + draw_behavior_info(b); + } + } + + void draw_behaviors(std::map& behaviors) + { + std::string ident = ""; + for (auto [id, b] : behaviors) + { + // C++ and the weird referencing rules... + draw_region(b->local_region, ident, b); + } + } + + void draw_region( + objects::Region* r, + std::string& indent, + core::Behavior* behavior = nullptr) { auto info = ®ions[r]; if (info->drawn) @@ -503,13 +540,21 @@ namespace rt::ui out << "reg" << r << "[\" \"]" << std::endl; // Content + if (behavior) + { + out << " " << indent << this->behavior_node_name(behavior) + << std::endl; + } draw_region_body(r, info, indent); // Footer out << indent << "end" << std::endl; - out << indent << "style reg" << r - << " fill:" << REGION_COLORS[depth % std::size(REGION_COLORS)] - << std::endl; + auto color = REGION_COLORS[depth % std::size(REGION_COLORS)]; + if (r->is_local_region) + { + color = LOCAL_REGION_COLOR; + } + out << indent << "style reg" << r << " fill:" << color << std::endl; } void draw_regions() @@ -545,18 +590,6 @@ namespace rt::ui } regions[objects::immutable_region].drawn = true; - // Local region - { - auto region = objects::get_local_region(); - out << "subgraph " << LOCAL_REGION_ID << "[\"Local region\"]" - << std::endl; - draw_region_body(objects::cown_region, ®ions[region], indent); - out << "end" << std::endl; - out << "style " << LOCAL_REGION_ID << " fill:" << LOCAL_REGION_COLOR - << std::endl; - } - regions[objects::get_local_region()].drawn = true; - // Draw all other regions if (MermaidUI::pragma_draw_regions_nested) { @@ -702,7 +735,7 @@ namespace rt::ui out << "
" << message << "
" << std::endl; ObjectGraphDiagram diag(this); - diag.draw(roots); + diag.draw(roots, core::Behavior::s_running_behaviors); if (should_break()) { @@ -715,6 +748,11 @@ namespace rt::ui } } + void MermaidUI::output(std::string message) { + auto roots = local_root_objects(); + this->output(roots, message); + } + void MermaidUI::draw_schedule( std::vector behaviors, std::string message) { @@ -831,15 +869,18 @@ namespace rt::ui std::vector MermaidUI::local_root_objects() { - auto local_set = &objects::get_local_region()->objects; std::vector nodes_vec; - for (auto item : *local_set) - { - if (always_hide.contains(item) || unreachable_hide.contains(item)) + for (auto [_, behavior] : core::Behavior::s_running_behaviors) { + auto local_set = &behavior->local_region->objects; + + for (auto item : *local_set) { - continue; + if (always_hide.contains(item) || unreachable_hide.contains(item)) + { + continue; + } + nodes_vec.push_back(item); } - nodes_vec.push_back(item); } return nodes_vec; From f9756fcf2cbb7d3c2683caaa4fc15170885773e1 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 28 Mar 2025 12:25:08 +0100 Subject: [PATCH 17/23] Better frame names --- src/rt/core.cc | 1 + src/rt/core.h | 5 +++++ src/rt/objects/dyn_object.h | 5 +++-- src/rt/ui/mermaid.cc | 7 +++---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/rt/core.cc b/src/rt/core.cc index 311beb1..67a0af8 100644 --- a/src/rt/core.cc +++ b/src/rt/core.cc @@ -2,5 +2,6 @@ namespace rt::core { + int FrameObject::s_frame_id_counter = 1; int CownObject::s_id_counter = 1; } \ No newline at end of file diff --git a/src/rt/core.h b/src/rt/core.h index 4f80fd8..b56c2f5 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -19,6 +19,7 @@ namespace rt::core class FrameObject : public objects::DynObject, public verona::interpreter::FrameObj { + static int s_frame_id_counter; static constexpr std::string_view STACK_PREFIX = "_stack"; static inline thread_local std::vector stack_keys; size_t stack_size = 0; @@ -47,6 +48,10 @@ namespace rt::core objects::add_reference(this, parent_frame); assert(!old_value); } + + std::stringstream ss; + ss << ""; + name = ss.str(); } static FrameObject* create_first_stack() diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index a5f3406..a9cacd7 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -56,10 +56,12 @@ namespace rt::objects size_t rc{1}; RegionPointer region{nullptr}; DynObject* prototype{nullptr}; - std::string name; std::map fields{}; + protected: + std::string name; + public: size_t change_rc(signed delta) { @@ -121,7 +123,6 @@ namespace rt::objects auto r = get_region(this); if (!is_immutable() && r != nullptr) r->objects.erase(this); - } size_t get_rc() diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 31e85d0..1915ae8 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -38,13 +38,12 @@ namespace rt::ui "#d9d450", }; - const char* LOCAL_REGION_ID = "LocalReg"; const char* IMM_REGION_ID = "ImmReg"; const char* COWN_REGION_ID = "CownReg"; - const char* BEHAVIOR_RUNNING_COLOR = "#eefcdd"; - const char* BEHAVIOR_READY_COLOR = "#ddeefc"; - const char* BEHAVIOR_PENDING_COLOR = "#fcfbdd"; + const char* BEHAVIOR_RUNNING_COLOR = "#eeeeee"; + const char* BEHAVIOR_READY_COLOR = "#eefcdd"; + const char* BEHAVIOR_PENDING_COLOR = "#e6d5fb"; const char* FONT_SIZE = "16px"; const int EDGE_WIDTH = 2; From 6421848701c8b6a22c2bec4737558bfdd62647f6 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 28 Mar 2025 13:05:08 +0100 Subject: [PATCH 18/23] Hide prototypes by default --- src/lang/interpreter.cc | 1 + src/rt/behavior.h | 1 + src/rt/core.h | 15 +++++++++++++++ src/rt/ui.h | 9 +++++++++ src/rt/ui/mermaid.cc | 23 +++++++++++++++++++++-- 5 files changed, 47 insertions(+), 2 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index a24a0ce..f25a426 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -746,6 +746,7 @@ namespace verona::interpreter assert(ui->is_mermaid()); auto mermaid = reinterpret_cast(ui); mermaid->draw_schedule(this->ready, message); + mermaid->close_file(); } rt::core::behavior_ptr Scheduler::get_next() diff --git a/src/rt/behavior.h b/src/rt/behavior.h index 529475a..b534c79 100644 --- a/src/rt/behavior.h +++ b/src/rt/behavior.h @@ -26,6 +26,7 @@ namespace rt::ui namespace rt::core { + // TODO rename this to `Behaviour` class Behavior { friend class rt::ui::MermaidUI; diff --git a/src/rt/core.h b/src/rt/core.h index b56c2f5..aa8cd0a 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -451,6 +451,21 @@ namespace rt::core }; return globals; } + inline std::set* global_prototypes() + { + static std::set* globals = + new std::set{ + objects::regionPrototypeObject(), + framePrototypeObject(), + funcPrototypeObject(), + bytecodeFuncPrototypeObject(), + builtinFuncPrototypeObject(), + stringPrototypeObject(), + keyIterPrototypeObject(), + cownPrototypeObject(), + }; + return globals; + } inline std::map* global_names() { diff --git a/src/rt/ui.h b/src/rt/ui.h index c8a3072..83ffbcf 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -18,6 +18,7 @@ namespace rt::ui virtual void set_output_file(std::string path_) = 0; virtual void output(std::vector&, std::string) {} + virtual void output(std::string) {} virtual void highlight(std::string, std::vector&) {} @@ -94,6 +95,11 @@ namespace rt::ui void prep_output(); + void close_file() + { + out.close(); + } + void output( std::vector& roots, std::string message) override; void output(std::string message) override; @@ -170,6 +176,9 @@ namespace rt::ui void hide_cown_region(); void show_cown_region(); + void hide_prototypes(); + void show_prototypes(); + void error(std::string) override; void diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 1915ae8..8c9e942 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -695,6 +695,7 @@ namespace rt::ui MermaidUI::MermaidUI() { hide_cown_region(); + hide_prototypes(); } void MermaidUI::prep_output() @@ -747,7 +748,8 @@ namespace rt::ui } } - void MermaidUI::output(std::string message) { + void MermaidUI::output(std::string message) + { auto roots = local_root_objects(); this->output(roots, message); } @@ -835,6 +837,22 @@ namespace rt::ui remove_always_hide(core::cownPrototypeObject()); } + void MermaidUI::hide_prototypes() + { + for (auto proto : *core::global_prototypes()) + { + add_always_hide(proto); + } + } + + void MermaidUI::show_prototypes() + { + for (auto proto : *core::global_prototypes()) + { + remove_always_hide(core::cownPrototypeObject()); + } + } + void MermaidUI::error(std::string info) { // Make sure ui doesn't pause @@ -869,7 +887,8 @@ namespace rt::ui std::vector MermaidUI::local_root_objects() { std::vector nodes_vec; - for (auto [_, behavior] : core::Behavior::s_running_behaviors) { + for (auto [_, behavior] : core::Behavior::s_running_behaviors) + { auto local_set = &behavior->local_region->objects; for (auto item : *local_set) From f3f78ee149fa08007e1fe1d0a7d0444f97cff6f4 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 28 Mar 2025 13:36:16 +0100 Subject: [PATCH 19/23] Schedule Diagram show cowns as arrows --- src/lang/interpreter.cc | 8 +++-- src/rt/behavior.h | 6 ++++ src/rt/core.h | 3 +- src/rt/core/behavior.cc | 11 +++++++ src/rt/objects/dyn_object.h | 6 ++++ src/rt/ui/mermaid.cc | 59 +++++++++++-------------------------- 6 files changed, 46 insertions(+), 47 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index f25a426..a945ee1 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -635,14 +635,16 @@ namespace verona::interpreter auto cown_info = cowns.find(cown); if (cown_info != cowns.end()) { - auto pending = cown_info->second; + auto predecessor = cown_info->second; // If a behavior is pending, set the successor - if (pending->status != rt::core::Behavior::Status::Done) + if (predecessor->status != rt::core::Behavior::Status::Done) { - if (pending->succ.insert(behavior).second) + if (predecessor->succ.insert(behavior).second) { behavior->pred_ctn += 1; } + // Only needed for Mermaid: + behavior->cown_deps[cown] = predecessor.get(); } } // Update pointer to the last pending behavior diff --git a/src/rt/behavior.h b/src/rt/behavior.h index b534c79..4b52d38 100644 --- a/src/rt/behavior.h +++ b/src/rt/behavior.h @@ -96,6 +96,12 @@ namespace rt::core objects::Region* local_region; public: + // This maps the cowns of this behavior to the previous behavior this + // is waiting on. This is used to draw the dependencies, it is not used + // for sceduling. + // Both of these pointers are weak reference. + std::map cown_deps; + Status status; // The cowns as they were passed in to the cown. These have to be provided // to the new Interpreter to populate the frame diff --git a/src/rt/core.h b/src/rt/core.h index aa8cd0a..4ec0cb9 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -361,10 +361,9 @@ namespace rt::core // TODO: This should really be split into `get_name()` just getting the name // and `get_info()` or the additional info text like lrc and status - std::string get_name() override + std::optional get_additional_info() override { std::stringstream ss; - ss << this->name << std::endl; ss << "status=" << to_string(status); return ss.str(); } diff --git a/src/rt/core/behavior.cc b/src/rt/core/behavior.cc index 9b75d03..7bc87a9 100644 --- a/src/rt/core/behavior.cc +++ b/src/rt/core/behavior.cc @@ -85,6 +85,9 @@ namespace rt::core return rt::try_get_bytecode(this->code).value(); } + // FIXME: Currently both the scheduler and the behavior has a function + // to complete a behavior. All of this should really be in one place. It + // might be better to move all of this into the scheduler. void Behavior::complete() { this->status = Status::Done; @@ -98,6 +101,14 @@ namespace rt::core } this->cowns.clear(); + for (auto [cown, waiting_on] : cown_deps) + { + if (waiting_on == this) + { + cown_deps.erase(cown); + } + } + this->s_running_behaviors.erase(this->id); } } // namespace rt::core diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index a9cacd7..9d859bd 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -137,6 +137,12 @@ namespace rt::objects return name; } + // TODO make more types use this instead of `get_name()` + virtual std::optional get_additional_info() + { + return std::nullopt; + } + /// TODO remove virtual once we have primitive functions. virtual DynObject* is_primitive() { diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 8c9e942..c553153 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -201,15 +201,7 @@ namespace rt::ui void draw_behavior(core::behavior_ptr behavior) { - out << "subgraph " << behavior->id_str() << "[\" \"]" << std::endl; draw_behavior_info(behavior.get()); - - for (auto [_, c] : behavior->ordered_cown) - { - this->draw_cown(c, behavior); - } - out << "end" << std::endl; - // Set background color auto background = ERROR_NODE_COLOR; switch (behavior->status) @@ -224,31 +216,23 @@ namespace rt::ui background = BEHAVIOR_PENDING_COLOR; break; } - out << "style " << behavior->id_str() << " fill:" << background - << std::endl; - } - - void draw_dependencies(core::behavior_ptr behavior) - { - for (auto [_, cown_obj] : behavior->ordered_cown) - { - assert(cown_obj->get_prototype() == core::cownPrototypeObject()); - core::CownObject* cown = reinterpret_cast(cown_obj); - auto cown_id = cown->get_id(); - - // Draw dependencies - for (auto succ : behavior->succ) - { - if (succ->ordered_cown.contains(cown_id)) - { - out << " "; - out << cown_node_id(cown, succ->id); - out << " --> "; - out << cown_node_id(cown, behavior->id); - out << std::endl; - edge_counter += 1; - } - } + out << "style " << this->behavior_node_name(behavior.get()) + << " fill:" << background << std::endl; + + for (auto [cown, pred] : behavior->cown_deps) + { + out << " "; + out << this->behavior_node_name(behavior.get()); + out << " --> |"; + // TODO: This really shouldn't directly access the name. get_name() + // should always just return the name and then there is a + // `more_info()` method that provides additional info like + // the cown status or LRC for regions etc. + out << escape(cown->get_name()); + out << "| "; + out << this->behavior_node_name(pred); + out << std::endl; + edge_counter += 1; } } @@ -290,15 +274,6 @@ namespace rt::ui this->draw_behavior(behavior); } - for (auto& [bid, behavior] : behaviors) - { - this->draw_dependencies(behavior); - } - - // TODO: - // -> Show running behavior? - // -> Cown colors - // Footer this->draw_footer(); } From 5fe108b15e547fcf2adc03e2d22c6e7057b34b6f Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 28 Mar 2025 15:12:45 +0100 Subject: [PATCH 20/23] C++ problems --- src/lang/interpreter.cc | 9 ++++++++- src/lang/passes/parse.cc | 2 +- src/rt/rt.cc | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index a945ee1..8b18bbd 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -68,7 +68,14 @@ namespace verona::interpreter InterpreterFrame* top_frame() { - return frame_stack.back(); + if (frame_stack.empty()) + { + return nullptr; + } + else + { + return frame_stack.back(); + } } InterpreterFrame* push_stack_frame(trieste::Node body) diff --git a/src/lang/passes/parse.cc b/src/lang/passes/parse.cc index ca055fc..f7b6b36 100644 --- a/src/lang/passes/parse.cc +++ b/src/lang/passes/parse.cc @@ -96,7 +96,7 @@ trieste::Parse parser() "(?:#[^\\n\\r]*)" >> [](auto&) {}, "def\\b" >> [](auto& m) { m.seq(Func); }, - "@name\\(\"([0-9A-Za-z_]+)\"\\)" >> [](auto& m) { m.add(Name, 1); }, + "@name\\(\"([^\\n\"]+)\"\\)" >> [](auto& m) { m.add(Name, 1); }, "when\\b" >> [](auto& m) { m.seq(When); }, "\\(" >> [](auto& m) { m.push(Parens); }, "\\)" >> diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 3d0a539..000d102 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -361,7 +361,7 @@ namespace rt int get_cown_id(objects::DynObject* cown) { - if (cown->get_prototype() != core::cownPrototypeObject()) + if (cown && cown->get_prototype() != core::cownPrototypeObject()) { ui::error("The given object is not a cown", cown); } From 7c9f091ea30214b62e27351f58893e4747701cbf Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 28 Mar 2025 16:11:13 +0100 Subject: [PATCH 21/23] Merge mermaid diagrams and other cahos --- src/lang/interpreter.cc | 28 ++++-- src/lang/interpreter.h | 8 +- src/rt/behavior.h | 12 --- src/rt/core/behavior.cc | 6 -- src/rt/rt.cc | 7 +- src/rt/ui.h | 10 +-- src/rt/ui/mermaid.cc | 190 ++++++++++++++-------------------------- tests/example_1.frank | 6 +- 8 files changed, 97 insertions(+), 170 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 8b18bbd..4acd1ea 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -501,14 +501,6 @@ namespace verona::interpreter rt::core::behavior_ptr behavior_) : ui(ui_), behavior(behavior_) { - // FIXME: Oh no, we need to set the local region before running this but - // the interpreter would then need to know about behaviors right? - // Maybe not? What if the scheduler sets the behavior before doing this? - // That could work, I don't like it but it could - // - // Looking at this, I believe it would be better to let the interpreter - // know about behaviors - // There is a question where the active behavior should be set. // // Python mixes the runtime and interpreter a bit more. There the runtime @@ -631,6 +623,21 @@ namespace verona::interpreter rt::post_run(initial, ui); } + Scheduler::Scheduler() + { + auto ui = rt::ui::globalUI(); + assert(ui->is_mermaid()); + reinterpret_cast(ui)->scheduler_ready_list = + &this->ready; + } + + Scheduler::~Scheduler() + { + auto ui = rt::ui::globalUI(); + assert(ui->is_mermaid()); + reinterpret_cast(ui)->scheduler_ready_list = nullptr; + } + void Scheduler::add(rt::core::behavior_ptr behavior) { assert(behavior->status == rt::core::Behavior::Status::New); @@ -751,10 +758,13 @@ namespace verona::interpreter this->next_schedule_msg.reset(); } + // FIXME: We should really get wrid of the UI* abstraction. There is no way + // that we'll ever change the output at this point and it just makes several + // things harder, like this: auto ui = rt::ui::globalUI(); assert(ui->is_mermaid()); auto mermaid = reinterpret_cast(ui); - mermaid->draw_schedule(this->ready, message); + mermaid->output(message); mermaid->close_file(); } diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 9dcf9cc..619b558 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -14,11 +14,6 @@ namespace rt::objects class DynObject; } // namespace rt::objects -namespace rt::ui -{ - class ScheduleDiagram; -} - namespace verona::interpreter { class Interpreter; @@ -76,6 +71,9 @@ namespace verona::interpreter Interpreter* current_int; public: + Scheduler(); + ~Scheduler(); + void add(rt::core::behavior_ptr behavior); void start(Bytecode* main); diff --git a/src/rt/behavior.h b/src/rt/behavior.h index 4b52d38..e4a0f38 100644 --- a/src/rt/behavior.h +++ b/src/rt/behavior.h @@ -20,7 +20,6 @@ namespace rt::objects namespace rt::ui { class MermaidUI; - class ScheduleDiagram; class ObjectGraphDiagram; } @@ -30,7 +29,6 @@ namespace rt::core class Behavior { friend class rt::ui::MermaidUI; - friend class rt::ui::ScheduleDiagram; friend class rt::ui::ObjectGraphDiagram; public: @@ -66,16 +64,6 @@ namespace rt::core static std::shared_ptr get_active_behavior(); private: - // This map is a collection of all behaviors that have started running and - // therefore also have a local region. This is needed here for the lovely - // mermaid output. This uses an ordered map in the hope that the diagram - // will keep the same layout every iteration. - // - // It uses behavior pointers since it's being updated from inside methods - // where `this` is a pointer and not a `shared_ptr`. This should be fine - // since each behavior should call `complete()` before being freed thereby - // also updating this list. - static std::map s_running_behaviors; static std::shared_ptr s_active_behavior; private: diff --git a/src/rt/core/behavior.cc b/src/rt/core/behavior.cc index 7bc87a9..bc17df9 100644 --- a/src/rt/core/behavior.cc +++ b/src/rt/core/behavior.cc @@ -14,7 +14,6 @@ namespace rt::objects namespace rt::core { int Behavior::s_behavior_counter = 0; - std::map Behavior::s_running_behaviors = {}; std::shared_ptr Behavior::s_active_behavior = nullptr; void Behavior::set_active_behavior(std::shared_ptr active) @@ -79,9 +78,6 @@ namespace rt::core this->local_region = objects::Region::new_local_region(); - // Add self to running behaviors to have it also drawn as a local region. - s_running_behaviors[this->id] = this; - return rt::try_get_bytecode(this->code).value(); } @@ -108,7 +104,5 @@ namespace rt::core cown_deps.erase(cown); } } - - this->s_running_behaviors.erase(this->id); } } // namespace rt::core diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 000d102..604430e 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -251,13 +251,8 @@ namespace rt std::cout << "Final count: " << objects::DynObject::get_count() << std::endl; - std::vector roots; - for (auto obj : objects::DynObject::get_objects()) - { - roots.push_back(obj); - } ui::MermaidUI::highlight_unreachable = true; - ui->output(roots, "Memory leak detected!"); + ui->output("Memory leak detected!"); std::exit(1); } diff --git a/src/rt/ui.h b/src/rt/ui.h index 83ffbcf..d5e9ee4 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -41,7 +41,6 @@ namespace rt::core namespace rt::ui { class MermaidDiagram; - class ScheduleDiagram; class ObjectGraphDiagram; class MermaidUI : public UI @@ -50,9 +49,13 @@ namespace rt::ui static inline bool pragma_draw_regions_nested = true; static inline bool highlight_unreachable = false; + // This feels really wrong, but is the easiest fix rn. The list should + // probably always be passed in to the `output()` call but that would + // require more refactorings + std::vector* scheduler_ready_list; + private: friend class ObjectGraphDiagram; - friend class ScheduleDiagram; friend class MermaidDiagram; friend void core::mermaid_builtins(ui::UI* ui); @@ -104,9 +107,6 @@ namespace rt::ui std::vector& roots, std::string message) override; void output(std::string message) override; - void draw_schedule( - std::vector behaviors, std::string message); - void highlight( std::string message, std::vector& highlight) override; diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index c553153..5533098 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -161,47 +161,6 @@ namespace rt::ui << behavior->get_name() << "
Status: " << core::Behavior::status_to_string(behavior->status) << "\"])" << std::endl; - } - }; - - class ScheduleDiagram : protected MermaidDiagram - { - public: - ScheduleDiagram(MermaidUI* info_) : MermaidDiagram(info_) {} - - private: - std::string cown_node_id(rt::objects::DynObject* cown, int behavior_id) - { - std::stringstream ss; - ss << cown << "_" << behavior_id; - return ss.str(); - } - - void - draw_cown(rt::objects::DynObject* cown_obj, core::behavior_ptr behavior) - { - assert(cown_obj->get_prototype() == core::cownPrototypeObject()); - core::CownObject* cown = reinterpret_cast(cown_obj); - auto cown_id = cown->get_id(); - - auto markers = get_node_style(cown); - - // Header - out << " "; - out << cown_node_id(cown, behavior->id); - out << markers.first; - - // Content - out << "cown " << cown_id; - - // Footer - out << markers.second; - out << std::endl; - } - - void draw_behavior(core::behavior_ptr behavior) - { - draw_behavior_info(behavior.get()); // Set background color auto background = ERROR_NODE_COLOR; switch (behavior->status) @@ -216,66 +175,8 @@ namespace rt::ui background = BEHAVIOR_PENDING_COLOR; break; } - out << "style " << this->behavior_node_name(behavior.get()) + out << "style " << this->behavior_node_name(behavior) << " fill:" << background << std::endl; - - for (auto [cown, pred] : behavior->cown_deps) - { - out << " "; - out << this->behavior_node_name(behavior.get()); - out << " --> |"; - // TODO: This really shouldn't directly access the name. get_name() - // should always just return the name and then there is a - // `more_info()` method that provides additional info like - // the cown status or LRC for regions etc. - out << escape(cown->get_name()); - out << "| "; - out << this->behavior_node_name(pred); - out << std::endl; - edge_counter += 1; - } - } - - std::map - aggregate_behaviors(std::vector pending) - { - std::map behaviors; - - while (!pending.empty()) - { - auto b = pending.back(); - pending.pop_back(); - - auto [_, inserted] = behaviors.insert({b->id, b}); - if (inserted) - { - for (auto succ : b->succ) - { - pending.push_back(succ); - } - } - } - - return behaviors; - } - - public: - void draw(std::vector pending) - { - auto behaviors = aggregate_behaviors(pending); - - // Header - this->draw_header(); - out << "graph TD" << std::endl; - - // Drawing in reverse order gives a better diagram - for (auto& [bid, behavior] : std::views::reverse(behaviors)) - { - this->draw_behavior(behavior); - } - - // Footer - this->draw_footer(); } }; @@ -295,15 +196,15 @@ namespace rt::ui regions[objects::immutable_region].nodes.push_back(0); } - void draw( - std::vector& roots, - std::map& behaviors) + void draw(std::vector& roots) { // header this->draw_header(); out << "graph TD" << std::endl; out << " id0(None):::immutable" << std::endl; + auto behaviors = aggregate_behaviors(); + draw_behavior_nodes(behaviors); draw_nodes(roots); draw_behaviors(behaviors); @@ -349,6 +250,31 @@ namespace rt::ui return ""; } + std::map aggregate_behaviors() + { + // Clone the vector + std::vector pending = + *this->info->scheduler_ready_list; + std::map behaviors; + + while (!pending.empty()) + { + auto b = pending.back(); + pending.pop_back(); + + auto [_, inserted] = behaviors.insert({b->id, b}); + if (inserted) + { + for (auto succ : b->succ) + { + pending.push_back(succ); + } + } + } + + return behaviors; + } + /// @brief Draws the target node and the edge from the source to the target. NodeInfo* draw_edge(objects::Edge e, bool reachable) { @@ -478,21 +404,47 @@ namespace rt::ui indent.erase(indent.size() - 2); } - void draw_behavior_nodes(std::map& behaviors) + void draw_behavior_nodes(std::map& behaviors) { for (auto [id, b] : behaviors) { - draw_behavior_info(b); + draw_behavior_info(b.get()); } } - void draw_behaviors(std::map& behaviors) + void draw_behaviors(std::map& behaviors) { std::string ident = ""; for (auto [id, b] : behaviors) { - // C++ and the weird referencing rules... - draw_region(b->local_region, ident, b); + if (b->status == core::Behavior::Status::Running) + { + // C++ and the weird referencing rules... + draw_region(b->local_region, ident, b.get()); + } + else + { + for (auto cown : b->cowns) + { + out << " "; + out << this->behavior_node_name(b.get()); + out << " --> |"; + out << escape(cown->get_name()); + out << "| "; + + auto pred = b->cown_deps[cown]; + if (pred) + { + out << this->behavior_node_name(pred); + } + else + { + out << this->nodes[cown]; + } + out << std::endl; + edge_counter += 1; + } + } } } @@ -710,7 +662,7 @@ namespace rt::ui out << "
" << message << "
" << std::endl; ObjectGraphDiagram diag(this); - diag.draw(roots, core::Behavior::s_running_behaviors); + diag.draw(roots); if (should_break()) { @@ -729,20 +681,6 @@ namespace rt::ui this->output(roots, message); } - void MermaidUI::draw_schedule( - std::vector behaviors, std::string message) - { - this->prep_output(); - - out << "### " << message << std::endl; - - ScheduleDiagram diag(this); - diag.draw(behaviors); - - // Make sure the output is available. - out.flush(); - } - void MermaidUI::highlight( std::string message, std::vector& highlight) { @@ -862,8 +800,12 @@ namespace rt::ui std::vector MermaidUI::local_root_objects() { std::vector nodes_vec; - for (auto [_, behavior] : core::Behavior::s_running_behaviors) + for (auto behavior : *this->scheduler_ready_list) { + if (!behavior->local_region) + { + continue; + } auto local_set = &behavior->local_region->objects; for (auto item : *local_set) diff --git a/tests/example_1.frank b/tests/example_1.frank index 2c5ef82..634e65d 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -25,13 +25,13 @@ when (c1, c2): @name("B3") when(c6, c7): - r = None + pass() @name("B4") when(c5, c6): - r = None + pass() @name("B5") when (c2, c3, c4, c5): - r = None + pass() From 7643dd618cfe56007c3a575b1b28556352f76c7b Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 28 Mar 2025 17:15:43 +0100 Subject: [PATCH 22/23] Track cown owner --- src/rt/core.h | 17 ++++++++++++++-- src/rt/core/behavior.cc | 3 +-- src/rt/core/builtin.cc | 6 ++++-- src/rt/objects/dyn_object.h | 2 +- src/rt/rt.cc | 6 +++--- src/rt/rt.h | 2 +- src/rt/ui/mermaid.cc | 10 +++++++++- tests/example_1.frank | 39 ++++++++++++------------------------- 8 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/rt/core.h b/src/rt/core.h index 4ec0cb9..03f893a 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -1,4 +1,5 @@ #include "../lang/interpreter.h" +#include "behavior.h" #include "objects/prototype_object.h" #include "objects/region.h" #include "objects/region_object.h" @@ -285,6 +286,7 @@ namespace rt::core Status status; int id; + core::Behavior* owner; public: CownObject( @@ -294,6 +296,7 @@ namespace rt::core id = s_id_counter++; status = Status::Pending; + this->owner = Behavior::get_active_behavior().get(); auto old = set("value", obj); assert(!old); @@ -365,6 +368,11 @@ namespace rt::core { std::stringstream ss; ss << "status=" << to_string(status); + if (status == Status::Pending || status == Status::Acquired) + { + assert(this->owner); + ss << " (" << this->owner->get_name() << ")"; + } return ss.str(); } @@ -381,7 +389,7 @@ namespace rt::core // but this is single threaded case Status::Acquired: case Status::Pending: - return false; + return Behavior::get_active_behavior().get() != this->owner; case Status::Released: default: return true; @@ -406,6 +414,7 @@ namespace rt::core if (!value || value->is_immutable() || value->is_cown()) { status = Status::Released; + this->owner = nullptr; return; } @@ -413,16 +422,18 @@ namespace rt::core if (region->combined_lrc() == 0) { status = Status::Released; + this->owner = nullptr; } } - void aquire() + void aquire(Behavior* behavior) { // Who needs other safety checks than this? // This is so gonna bite me... assert(this->status == Status::Released); this->status = Status::Acquired; + this->owner = behavior; } void release() @@ -430,6 +441,7 @@ namespace rt::core assert(this->status == Status::Acquired); this->status = Status::Released; + this->owner = nullptr; } }; @@ -450,6 +462,7 @@ namespace rt::core }; return globals; } + inline std::set* global_prototypes() { static std::set* globals = diff --git a/src/rt/core/behavior.cc b/src/rt/core/behavior.cc index bc17df9..3d1e55b 100644 --- a/src/rt/core/behavior.cc +++ b/src/rt/core/behavior.cc @@ -72,8 +72,7 @@ namespace rt::core for (auto c : this->cowns) { - // TODO, store aquireing behavior and check the behavior later - rt::aquire_cown(c); + rt::aquire_cown(c, this); } this->local_region = objects::Region::new_local_region(); diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 1d52052..865554d 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -375,7 +375,7 @@ namespace rt::core name.erase(0, 1); name.erase(name.size() - 1); } - std::cout << name << std::endl; + std::cout << name << std::endl; rt::remove_reference(frame->object(), value); return std::nullopt; @@ -432,7 +432,9 @@ namespace rt::core std::optional name; // The last argument might be a name for the behavior - if (cowns.back()->get_prototype() == rt::core::stringPrototypeObject()) + if ( + !cowns.empty() && + cowns.back()->get_prototype() == rt::core::stringPrototypeObject()) { auto name_obj = cowns.back(); name = dynamic_cast(name_obj)->as_key(); diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 9d859bd..995632f 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -263,7 +263,7 @@ namespace rt::objects { if (is_cown()) { - ui::error("Cannot mutate a cown that is not aquired", this); + ui::error("Cannot mutate a cown that is not aquired by the current behaviour", this); } else { diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 604430e..6d458e9 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -105,7 +105,7 @@ namespace rt { if (obj->is_cown()) { - ui::error("Cannot access data on a cown that is not aquired", obj); + ui::error("Cannot access data on a cown that is not aquired by the current behaviour", obj); } else { @@ -334,14 +334,14 @@ namespace rt return reinterpret_cast(cown)->is_released(); } - void aquire_cown(objects::DynObject* cown) + void aquire_cown(objects::DynObject* cown, core::Behavior* behavior) { if (cown->get_prototype() != core::cownPrototypeObject()) { ui::error("The given object is not a cown", cown); } - reinterpret_cast(cown)->aquire(); + reinterpret_cast(cown)->aquire(behavior); } void release_cown(objects::DynObject* cown) diff --git a/src/rt/rt.h b/src/rt/rt.h index 59c615e..4f28b6a 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -70,7 +70,7 @@ namespace rt void cown_update_state(objects::DynObject* cown); bool is_cown_released(objects::DynObject* cown); - void aquire_cown(objects::DynObject* cown); + void aquire_cown(objects::DynObject* cown, core::Behavior* behavior); void release_cown(objects::DynObject* cown); int get_cown_id(objects::DynObject* cown); diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 5533098..38e0a1c 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -175,7 +175,7 @@ namespace rt::ui background = BEHAVIOR_PENDING_COLOR; break; } - out << "style " << this->behavior_node_name(behavior) + out << " style " << this->behavior_node_name(behavior) << " fill:" << background << std::endl; } }; @@ -313,6 +313,14 @@ namespace rt::ui // Content out << escape(dst->get_name()); + auto info = dst->get_additional_info(); + if (info) + { + out << "
"; + out << escape(info.value()); + } + // FIXME: Make RC display optional, on by default but can be turned off + // with a CLI flag like --no-rc or --simple out << "
rc=" << dst->rc; out << (rt::core::globals()->contains(dst) ? " #40;global#41;" : ""); diff --git a/tests/example_1.frank b/tests/example_1.frank index 634e65d..b49ef8b 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -1,37 +1,22 @@ # For debugging: # mermaid_show_functions() -r = Region() -c1 = Cown(r, "c1") -r = None - -c2 = Cown(Region(), "c2") -c3 = Cown(Region(), "c3") -c4 = Cown(Region(), "c4") -c5 = Cown(Region()) -c6 = Cown(Region()) -c7 = Cown(Region()) +c2 = Cown(None, "c2") +c1 = Cown(c2, "c1") @name("B1") -when (c1, c2, c3): - c3.value.c2 = c2 +when (c1): + c2 = c1.value - when (c1): + @name("B1.1") + when: pass() -@name("B2") -when (c1, c2): - c1.value = c2 - -@name("B3") -when(c6, c7): - pass() + c2.value = Region() -@name("B4") -when(c5, c6): - pass() - -@name("B5") -when (c2, c3, c4, c5): - pass() +@name("B2") +when (c2): + @name("B2.1") + when: + pass() From d3167be3e0b3f12a593460a4da89dcc87c7b0553 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Fri, 28 Mar 2025 18:12:43 +0100 Subject: [PATCH 23/23] Scheduling the Pending release state is brocken but less segs --- src/lang/interpreter.cc | 2 +- src/lang/interpreter.h | 3 +++ src/rt/core.h | 10 ++++++++-- src/rt/objects/region.cc | 16 ++++++++++++---- src/rt/objects/region.h | 4 ++-- src/rt/rt.cc | 1 - tests/example_1.frank | 20 ++++++-------------- 7 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/lang/interpreter.cc b/src/lang/interpreter.cc index 4acd1ea..c48fdc7 100644 --- a/src/lang/interpreter.cc +++ b/src/lang/interpreter.cc @@ -693,7 +693,7 @@ namespace verona::interpreter rt::hack_inc_rc(main_function); // :notes: I imagine a world without ugly c++ :notes: auto behavior = std::make_shared( - main_function, std::vector{}, "Main function"); + main_function, std::vector{}, "main"); behavior->status = rt::core::Behavior::Status::Ready; this->ready.push_back(behavior); // Seriously, why do we use this language? The memory problems I currently diff --git a/src/lang/interpreter.h b/src/lang/interpreter.h index 619b558..430c0ce 100644 --- a/src/lang/interpreter.h +++ b/src/lang/interpreter.h @@ -78,6 +78,9 @@ namespace verona::interpreter void start(Bytecode* main); + // void new_pending_cown(rt::objects::DynObject* cown, rt::core::behavior_ptr behavior); + // void pending_cown_released(rt::objects::DynObject* cown, rt::core::behavior_ptr behavior); + private: void complete(rt::core::behavior_ptr behavior); void draw_scedule(std::string message); diff --git a/src/rt/core.h b/src/rt/core.h index 03f893a..5af1f96 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -300,6 +300,14 @@ namespace rt::core auto old = set("value", obj); assert(!old); + // This is really wonky. The scheduler should actually know about this + // new cown, but meh? + if (this->status == Status::Pending) + { + this->change_rc(1); + this->owner->cowns.push_back(this); + } + if (name_) { name = name_.value(); @@ -438,8 +446,6 @@ namespace rt::core void release() { - assert(this->status == Status::Acquired); - this->status = Status::Released; this->owner = nullptr; } diff --git a/src/rt/objects/region.cc b/src/rt/objects/region.cc index 883ec88..b137da7 100644 --- a/src/rt/objects/region.cc +++ b/src/rt/objects/region.cc @@ -282,7 +282,9 @@ namespace rt::objects bool continue_visit = true; std::set seen; - // FIXME: This is just completly broken... + // FIXME: This works only for the current behavior that has + // set the local region. And only because the `dirty_regions` + // has been cleared except the current region. visit(get_local_region(), [&](Edge e) { auto src = e.src; auto dst = e.target; @@ -336,13 +338,19 @@ namespace rt::objects } dirty_regions.clear(); - assert( - (!to_close_reg || to_close_reg->is_closed()) && - "The region should be closed now"); + if (to_close_reg && !to_close_reg->is_closed()) + { + ui::error("Unable to close the region"); + } } void Region::clean_lrcs() { + // This is a hack, basically we don't want `try_clean` to + // look at any other regions than the current one. That's + // why we remove all other regions. + dirty_regions.clear(); + dirty_regions.insert(this); clean_lrcs_and_close(nullptr); } diff --git a/src/rt/objects/region.h b/src/rt/objects/region.h index e4dad61..4a9066b 100644 --- a/src/rt/objects/region.h +++ b/src/rt/objects/region.h @@ -195,8 +195,8 @@ namespace rt::objects /// Cleans the LRC's and forces the region to close, by setting all local /// references to `None` - static void clean_lrcs_and_close(Region* reg = nullptr); - static void clean_lrcs(); + void clean_lrcs_and_close(Region* reg = nullptr); + void clean_lrcs(); bool is_closed() { diff --git a/src/rt/rt.cc b/src/rt/rt.cc index 6d458e9..388a885 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -219,7 +219,6 @@ namespace rt { std::cout << "Test complete - checking for cycles in local region..." << std::endl; - objects::Region::clean_lrcs(); objects::Region::collect(); auto globals = core::globals(); if (objects::DynObject::get_count() != initial_count) diff --git a/tests/example_1.frank b/tests/example_1.frank index b49ef8b..84aa88e 100644 --- a/tests/example_1.frank +++ b/tests/example_1.frank @@ -1,22 +1,14 @@ # For debugging: # mermaid_show_functions() -c2 = Cown(None, "c2") -c1 = Cown(c2, "c1") +r = Region() +c1 = Cown(r, "c1") +r.f = {} @name("B1") when (c1): - c2 = c1.value + print("Running B1") - @name("B1.1") - when: - pass() - - c2.value = Region() - -@name("B2") -when (c2): - @name("B2.1") - when: - pass() +is_closed(r) +close(r)