diff --git a/.github/workflows/buildci.yml b/.github/workflows/buildci.yml index ddf8cde..5a58cc6 100644 --- a/.github/workflows/buildci.yml +++ b/.github/workflows/buildci.yml @@ -11,6 +11,7 @@ on: jobs: build: runs-on: ${{ matrix.os }} + timeout-minutes: 10 strategy: # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. @@ -56,6 +57,7 @@ jobs: format: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 01f2380..8dac12a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,8 @@ set_property(TEST leak_with_global.vpy PROPERTY WILL_FAIL true) set_property(TEST invalid_read.vpy PROPERTY WILL_FAIL true) set_property(TEST invalid_shared_region.vpy PROPERTY WILL_FAIL true) set_property(TEST invalid_write.vpy PROPERTY WILL_FAIL true) -set_property(TEST fail_cross_region_reg.vpy PROPERTY WILL_FAIL true) +set_property(TEST invalid_child_region.vpy PROPERTY WILL_FAIL true) +set_property(TEST invalid_not_bridge.vpy PROPERTY WILL_FAIL true) set_property(TEST region_bad_1.vpy PROPERTY WILL_FAIL true) set_property(TEST region_bad_2.vpy PROPERTY WILL_FAIL true) +set_property(TEST fail_cross_region_ref.vpy PROPERTY WILL_FAIL true) diff --git a/docs/builtin.md b/docs/builtin.md index 2dfdce6..03395c5 100644 --- a/docs/builtin.md +++ b/docs/builtin.md @@ -12,7 +12,7 @@ Aborts the interpreter process. It's intended to indicate that an execution bran ## Constructors -#### `region()` +#### `Region()` Creates a new region object. @@ -74,7 +74,15 @@ This explicitly shows the "Cown region" in the generated diagrams. #### `mermaid_hide_cown_region()` -This hides the "Cown region" and cown prototype in diagram. (This is the default) +This hides the "Cown region" and cown prototype in diagram. (Default) + +#### `mermaid_show_immutable_region()` + +This explicitly shows the "Immutable region" in the generated diagrams + +#### `mermaid_hide_immutable_region()` + +This hides the "Immutable region" in diagram. (Default) #### `mermaid_show_functions()` @@ -82,4 +90,4 @@ Shows user defined functions in the diagram. #### `mermaid_hide_functions()` -Hides user defined functions in the diagram. (This is the default) +Hides user defined functions in the diagram. (Default) diff --git a/src/rt/core.h b/src/rt/core.h index 5828145..e8cb353 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -196,19 +196,24 @@ namespace rt::core class CownObject : public objects::DynObject { public: - CownObject(objects::DynObject* region) + CownObject(objects::DynObject* bridge) : objects::DynObject(cownPrototypeObject(), objects::cown_region) { - // FIXME: Add once regions are reified - // assert( - // region->get_prototype() == regionPrototype() && - // "Cowns can only store regions"); - // - // FIXME: Also check that the region has a LRC == 1, with 1 - // being the reference passed into this constructor + auto region = objects::get_region(bridge); + if (region->bridge != bridge) + { + std::stringstream ss; + ss << bridge << " is not the bridge object of the region"; + ui::error(ss.str(), bridge); + } + + if (region->local_reference_count > 1) + { + ui::error("The given region has a LRC > 1", bridge); + } // this->set would fail, since this is a cown - this->fields["value"] = region; + this->fields["value"] = bridge; } std::string get_name() override @@ -233,7 +238,7 @@ namespace rt::core { static std::set* globals = new std::set{ - objects::regionObjectPrototypeObject(), + objects::regionPrototypeObject(), framePrototypeObject(), funcPrototypeObject(), bytecodeFuncPrototypeObject(), diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 3656029..af0e478 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -124,6 +124,19 @@ namespace rt::core return std::nullopt; }); + add_builtin( + "mermaid_show_immutable_region", [mermaid](auto, auto, auto args) { + assert(args == 0); + mermaid->draw_immutable_region = true; + return std::nullopt; + }); + add_builtin( + "mermaid_hide_immutable_region", [mermaid](auto, auto, auto args) { + assert(args == 0); + mermaid->draw_immutable_region = false; + return std::nullopt; + }); + add_builtin("mermaid_show_functions", [mermaid](auto, auto, auto args) { assert(args == 0); mermaid->show_functions(); diff --git a/src/rt/objects/region.cc b/src/rt/objects/region.cc index 4958010..68bee31 100644 --- a/src/rt/objects/region.cc +++ b/src/rt/objects/region.cc @@ -52,7 +52,7 @@ namespace rt::objects return false; } - if (obj->get_prototype() != objects::regionObjectPrototypeObject()) + if (obj->get_prototype() != objects::regionPrototypeObject()) { ui::error("Cannot add interior region object to another region"); } @@ -133,7 +133,7 @@ namespace rt::objects return; } - if (target->get_prototype() != objects::regionObjectPrototypeObject()) + if (target->get_prototype() != objects::regionPrototypeObject()) { ui::error("Cannot add interior region object to another region"); } @@ -251,4 +251,19 @@ namespace rt::objects return obj; } + void Region::action(Region* r) + { + if ((r->local_reference_count == 0) && (r->parent == nullptr)) + { + // TODO, this can be hooked to perform delayed operations like send. + // Needs to check for sub_region_reference_count for send, but not + // deallocate. + + if (r != get_local_region() && r != cown_region) + { + to_collect.push_back(r); + std::cout << "Collecting region: " << r << std::endl; + } + } + } } diff --git a/src/rt/objects/region.h b/src/rt/objects/region.h index 9344127..7a228e5 100644 --- a/src/rt/objects/region.h +++ b/src/rt/objects/region.h @@ -55,18 +55,7 @@ namespace rt::objects return local_reference_count + sub_region_reference_count; } - static void action(Region* r) - { - if ((r->local_reference_count == 0) && (r->parent == nullptr)) - { - // TODO, this can be hooked to perform delayed operations like send. - // Needs to check for sub_region_reference_count for send, but not - // deallocate. - - to_collect.push_back(r); - std::cout << "Collecting region: " << r << std::endl; - } - } + static void action(Region*); static void dec_lrc(Region* r) { @@ -117,9 +106,9 @@ namespace rt::objects // Check if already parented to another region. if (r->parent != nullptr) { - // FIXME: Highlight, once regions have been reified ui::error( - "Region already has a parent: Creating region DAG not supported!"); + "Region already has a parent: Creating region DAG not supported!", + r->bridge); } // Prevent creating a cycle @@ -129,7 +118,7 @@ namespace rt::objects if (ancestors == r) { // FIXME: Highlight, once regions have been reified - ui::error("Cycle created in region hierarchy"); + ui::error("Cycle created in region hierarchy", r->bridge); } ancestors = ancestors->parent; } diff --git a/src/rt/objects/region_object.h b/src/rt/objects/region_object.h index 4847f6a..595a81a 100644 --- a/src/rt/objects/region_object.h +++ b/src/rt/objects/region_object.h @@ -1,11 +1,12 @@ #pragma once #include "prototype_object.h" +#include "region.h" namespace rt::objects { // The prototype object for region entry point objects - inline PrototypeObject* regionObjectPrototypeObject() + inline PrototypeObject* regionPrototypeObject() { static PrototypeObject* proto = new PrototypeObject("RegionObject"); return proto; @@ -14,8 +15,17 @@ namespace rt::objects class RegionObject : public DynObject { public: - RegionObject(Region* region) - : DynObject(regionObjectPrototypeObject(), region) - {} + RegionObject(Region* region) : DynObject(regionPrototypeObject(), region) {} + + std::string get_name() override + { + auto region = get_region(this); + + std::stringstream stream; + stream << this << std::endl; + stream << "lrc=" << region->local_reference_count << std::endl; + stream << "sbrc=" << region->sub_region_reference_count; + return stream.str(); + } }; -} \ No newline at end of file +} diff --git a/src/rt/rt.cc b/src/rt/rt.cc index ac48528..b04419d 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -92,7 +92,7 @@ namespace rt { // TODO Add some checking. This is need to lookup the correct function in // the prototype chain. - if (key->get_prototype() != core::stringPrototypeObject()) + if (key && key->get_prototype() != core::stringPrototypeObject()) { ui::error("Key must be a string.", key); } diff --git a/src/rt/ui.h b/src/rt/ui.h index 816e02e..163336e 100644 --- a/src/rt/ui.h +++ b/src/rt/ui.h @@ -57,8 +57,10 @@ namespace rt::ui std::set taint; /// Indicates if the cown region show be explicitly drawn bool draw_cown_region; + bool draw_immutable_region; /// Indicates if local functions should be visible bool draw_funcs; + bool highlight_unreachable = true; std::vector error_objects; std::vector error_edges; diff --git a/src/rt/ui/mermaid.cc b/src/rt/ui/mermaid.cc index 9614c22..0063d0d 100644 --- a/src/rt/ui/mermaid.cc +++ b/src/rt/ui/mermaid.cc @@ -17,13 +17,19 @@ namespace rt::ui const char* UNREACHABLE_NODE_COLOR = "red"; const char* ERROR_NODE_COLOR = "red"; - const char* IMMUTABLE_REGION_COLOR = "#32445d"; + const char* IMMUTABLE_NODE_COLOR = "#243042"; + const char* IMMUTABLE_REGION_COLOR = "#485464"; const char* IMMUTABLE_EDGE_COLOR = "#94f7ff"; + const char* REGION_COLORS[] = { + "#666", "#555", "#444", "#333", "#222", "#111"}; + const char* LOCAL_REGION_ID = "LocalReg"; const char* IMM_REGION_ID = "ImmReg"; const char* COWN_REGION_ID = "CownReg"; + const char* FONT_SIZE = "16px"; + void replace(std::string& text, std::string from, std::string replace) { size_t pos = 0; @@ -44,6 +50,7 @@ namespace rt::ui replace(text, "<", "#60;"); replace(text, ">", "#62;"); replace(text, "\"", "#34;"); + replace(text, "\n", "
"); return text; } @@ -60,6 +67,13 @@ namespace rt::ui return os; } + struct RegionInfo + { + std::vector nodes; + std::vector regions; + bool drawn; + }; + class MermaidDiagram { MermaidUI* info; @@ -70,14 +84,14 @@ namespace rt::ui // Give a nice id to each object. std::map nodes; - std::map> region_strings; + std::map regions; public: MermaidDiagram(MermaidUI* info_) : info(info_), out(info->out) { // Add nullptr nodes[nullptr] = {0}; - region_strings[rt::objects::immutable_region].push_back(0); + regions[objects::immutable_region].nodes.push_back(0); } void color_edge(size_t edge_id, const char* color) @@ -90,26 +104,55 @@ namespace rt::ui { // Header out << "```mermaid" << std::endl; + out << "%%{init: {'themeVariables': { 'fontSize': '" << FONT_SIZE + << "' }}}%%"; out << "graph TD" << std::endl; + out << " id0(None):::immutable" << std::endl; draw_nodes(roots); draw_regions(); draw_taint(); draw_error(); - draw_info(); - out << "style " << IMM_REGION_ID << " fill:" << IMMUTABLE_REGION_COLOR - << std::endl; out << "classDef unreachable stroke-width:2px,stroke:" << UNREACHABLE_NODE_COLOR << std::endl; out << "classDef error stroke-width:4px,stroke:" << ERROR_NODE_COLOR << std::endl; out << "classDef tainted fill:" << TAINT_NODE_COLOR << std::endl; + out << "classDef immutable fill:" << IMMUTABLE_NODE_COLOR << std::endl; // Footer (end of mermaid graph) out << "```" << std::endl; } 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 && + objects::get_region(e.src) != objects::get_region(e.target) && + objects::get_region(e.src) == objects::get_local_region(); + } + /// @brief Draws the target node and the edge from the source to the target. NodeInfo* draw_edge(objects::Edge e, bool reachable) { @@ -123,7 +166,7 @@ namespace rt::ui { auto src_node = &nodes[src]; out << " " << *src_node; - out << ((src_node->is_opaque) ? "-.->" : "-->"); + out << (is_borrow_edge(e) ? "-.->" : "-->"); out << " |" << escape(e.key) << "| "; edge_id = edge_counter; edge_counter += 1; @@ -140,10 +183,11 @@ namespace rt::ui // Draw a new node nodes[dst] = {id_counter++, dst->is_opaque()}; auto node = &nodes[dst]; + auto markers = get_node_style(dst); // Header out << *node; - out << (dst->is_cown() ? "[[" : "["); + out << markers.first; // Content out << escape(dst->get_name()); @@ -151,8 +195,12 @@ namespace rt::ui out << (rt::core::globals()->contains(dst) ? " #40;global#41;" : ""); // Footer - out << (dst->is_cown() ? "]]" : "]"); - out << (reachable ? "" : ":::unreachable"); + out << markers.second; + out + << (dst->is_immutable() ? + ":::immutable" : + (reachable && info->highlight_unreachable ? "" : + ":::unreachable")); out << std::endl; result = node; @@ -197,7 +245,7 @@ namespace rt::ui auto region = objects::get_region(dst); if (region != nullptr) { - region_strings[region].push_back(node->id); + regions[region].nodes.push_back(node->id); } return true; @@ -216,64 +264,98 @@ namespace rt::ui } } + void + draw_region_body(objects::Region* r, RegionInfo* info, std::string& indent) + { + indent += " "; + for (auto obj : info->nodes) + { + out << indent << "id" << obj << std::endl; + } + for (auto reg : info->regions) + { + draw_region(reg, indent); + } + indent.erase(indent.size() - 2); + } + + void draw_region(objects::Region* r, std::string& indent) + { + auto info = ®ions[r]; + if (info->drawn) + { + return; + } + info->drawn = true; + auto depth = indent.size() / 2; + + // Header + out << indent << "subgraph "; + out << "reg" << r << "[\" \"]" << std::endl; + + // Content + 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; + } + void draw_regions() { - // Output any region parent edges. - for (auto [region, objects] : region_strings) + // Build parent relations + for (auto [reg, _] : regions) { - if (region->parent == nullptr) - { - continue; - } - if ((!info->draw_cown_region) && region->parent == objects::cown_region) - { - continue; - } + regions[reg->parent].regions.push_back(reg); + } - out << " region" << region->parent << " <-.-o region" << region + std::string indent; + if (info->draw_cown_region) + { + auto region = objects::cown_region; + out << "subgraph " << COWN_REGION_ID << "[\"Cown region\"]" + << std::endl; + draw_region_body(region, ®ions[region], indent); + out << "end" << std::endl; + out << "style " << COWN_REGION_ID << " fill:" << REGION_COLORS[0] << std::endl; - edge_counter += 1; } + regions[objects::cown_region].drawn = true; - // Output all the region membership information - for (auto [region, objects] : region_strings) + if (info->draw_immutable_region) { - if ((!info->draw_cown_region) && region == objects::cown_region) - { - continue; - } - - out << "subgraph "; + auto region = objects::immutable_region; + out << "subgraph " << IMM_REGION_ID << "[\"Immutable region\"]" + << std::endl; + draw_region_body(region, ®ions[region], indent); + out << "end" << std::endl; + out << "style " << IMM_REGION_ID << " fill:" << IMMUTABLE_REGION_COLOR + << std::endl; + } + regions[objects::immutable_region].drawn = true; - if (region == objects::get_local_region()) - { - out << LOCAL_REGION_ID << "[\"Local region\"]" << std::endl; - } - else if (region == objects::immutable_region) - { - out << IMM_REGION_ID << "[\"Immutable region\"]" << std::endl; - out << " id0[nullptr]" << std::endl; - } - else if (region == objects::cown_region) - { - out << COWN_REGION_ID << "[\"Cown region\"]" << std::endl; - out << " region" << region << "[\\" << region - << "
sbrc=" << region->sub_region_reference_count << "/]" - << std::endl; - } - else - { - out << " " << std::endl; - out << " region" << region << "[\\" << region - << "
lrc=" << region->local_reference_count - << "
sbrc=" << region->sub_region_reference_count << "/]" - << std::endl; - } - for (auto obj : objects) - { - out << " id" << obj << std::endl; - } + // 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:" << REGION_COLORS[0] + << std::endl; + } + regions[objects::get_local_region()].drawn = true; + + // Draw all other regions + for (auto reg : regions[nullptr].regions) + { + draw_region(reg, indent); + } + for (auto reg : regions[objects::cown_region].regions) + { + draw_region(reg, indent); } } @@ -318,24 +400,6 @@ namespace rt::ui out << "class " << *node_info << " error;" << std::endl; } } - - void draw_info() - { - auto globals = rt::core::globals(); - auto local_ctn = objects::DynObject::get_count() - globals->size(); - - // Header - out << "subgraph info" << std::endl; - out << " i01["; - - // Info - out << "Locals: " << local_ctn << "
"; - out << "Globals: " << globals->size() << "
"; - - // Footer - out << "]" << std::endl; - out << "end" << std::endl; - } }; MermaidUI::MermaidUI() @@ -440,6 +504,7 @@ namespace rt::ui { // Make sure ui doesn't pause steps += 10; + highlight_unreachable = false; // Construct message std::stringstream ss; diff --git a/tests/cowns/valid_01.vpy b/tests/cowns/cown1.vpy similarity index 99% rename from tests/cowns/valid_01.vpy rename to tests/cowns/cown1.vpy index da22a09..121ea1b 100644 --- a/tests/cowns/valid_01.vpy +++ b/tests/cowns/cown1.vpy @@ -4,7 +4,6 @@ global = {} a = Region() a.b = {} - c01 = cown(move a) # Store the cown in a global diff --git a/tests/cowns/valid_02.vpy b/tests/cowns/cown2.vpy similarity index 94% rename from tests/cowns/valid_02.vpy rename to tests/cowns/cown2.vpy index 82940ac..fc3d67e 100644 --- a/tests/cowns/valid_02.vpy +++ b/tests/cowns/cown2.vpy @@ -7,7 +7,7 @@ drop x def add_cown(obj, name): a = Region() - a.name = name + a.name = {} obj[name] = cown(move a) add_cown(global, "c1") diff --git a/tests/cowns/invalid_child_region.vpy b/tests/cowns/invalid_child_region.vpy new file mode 100644 index 0000000..3c1e276 --- /dev/null +++ b/tests/cowns/invalid_child_region.vpy @@ -0,0 +1,8 @@ +# Creating a region +r1 = Region() +r2 = Region() + +r2.child = r1 + +# Error due to invalid LRC +co = cown(move r1) diff --git a/tests/cowns/invalid_not_bridge.vpy b/tests/cowns/invalid_not_bridge.vpy new file mode 100644 index 0000000..10a7905 --- /dev/null +++ b/tests/cowns/invalid_not_bridge.vpy @@ -0,0 +1,7 @@ +# A simple region +a = Region() +a.b = {} + +# Error: Only the bridge object can be used for cown construction +b = a.b +c01 = cown(move b) diff --git a/tests/cowns/invalid_shared_region.vpy b/tests/cowns/invalid_shared_region.vpy index 7966208..e4f0dfb 100644 --- a/tests/cowns/invalid_shared_region.vpy +++ b/tests/cowns/invalid_shared_region.vpy @@ -1,8 +1,9 @@ # Creating a region a = Region() +a.v = {} # LRC = 2 -r01 = a +r01 = a.v -# Error due to invalid RC -co = cown(take a) +# Error due to invalid LRC +co = cown(move a) diff --git a/tests/region/fail_cross_region_reg.vpy b/tests/regions/fail_cross_region_ref.vpy similarity index 100% rename from tests/region/fail_cross_region_reg.vpy rename to tests/regions/fail_cross_region_ref.vpy diff --git a/tests/regions/region4.vpy b/tests/regions/region4.vpy new file mode 100644 index 0000000..92da4a2 --- /dev/null +++ b/tests/regions/region4.vpy @@ -0,0 +1,30 @@ +# Create multiple regions +r1 = Region() +r1.level = "One" + +r2 = Region() +r2.level = "Two" + +r3 = Region() +r3.level = "Three" + +r4 = Region() +r4.level = "Four" + +r5 = Region() +r5.level = "Five" + +# Stack regions +r1.n = move r2 +r1.n.n = r3 + +r4.n = move r5 +r3.n = move r4 +drop r3 + +# Move this into a cown +c = cown(move r1) + +# Check the visuals +mermaid_show_cown_region() +mermaid_show_immutable_region()