diff --git a/src/rt/core.h b/src/rt/core.h index d1aaf88..d0d8a1c 100644 --- a/src/rt/core.h +++ b/src/rt/core.h @@ -281,14 +281,17 @@ namespace rt::core : objects::DynObject(cownPrototypeObject(), objects::cown_region) { status = Status::Pending; - auto region = objects::get_region(obj); + auto old = set("value", obj); + assert(!old); + } - if (!obj->is_immutable() && !obj->is_cown()) - { - // TODO: Make sure we're parenting the region and add third state, like - // pending - // Staring in an aquired state will allow the normal usage of `set` + [[nodiscard]] DynObject* set(std::string name, DynObject* obj) override + { + assert_modifiable(); + if (obj && !obj->is_immutable() && !obj->is_cown()) + { + auto region = objects::get_region(obj); // Potentiall error message std::stringstream ss; ss << "Object is neither immutable nor a cown, attempted to threat it " @@ -307,16 +310,23 @@ namespace rt::core << region->parent->bridge; ui::error(ss.str(), {this, "", obj}); } + + region->cown = this; } - auto old = set("value", obj); - assert(!old); + DynObject* old = fields[name]; + fields[name] = obj; - // 1x LRC from the stack - if (region->local_reference_count == 1) + if (old && !old->is_immutable() && !old->is_cown()) { - status = Status::Released; + auto old_reg = objects::get_region(old); + assert(old_reg->cown == this); + old_reg->cown = nullptr; } + + update_status(); + + return old; } std::string get_name() override @@ -346,6 +356,34 @@ namespace rt::core return true; } } + + bool is_released() + { + return this->status == Status::Released; + } + + /// This function updates the status of the cown. It mainly checks if a + /// cown in the pending state can be released. + void update_status() + { + if (status != Status::Pending) + { + return; + } + + auto value = this->get("value").value(); + if (!value || value->is_immutable() || value->is_cown()) + { + status = Status::Released; + return; + } + + auto region = objects::get_region(value); + if (region->combined_lrc() == 0) + { + status = Status::Released; + } + } }; inline std::set* globals() diff --git a/src/rt/core/builtin.cc b/src/rt/core/builtin.cc index 659da3a..fb28da2 100644 --- a/src/rt/core/builtin.cc +++ b/src/rt/core/builtin.cc @@ -334,6 +334,24 @@ namespace rt::core return std::nullopt; }); + + add_builtin("is_released", [](auto frame, auto args) { + if (args != 1) + { + ui::error("is_released() expected 1 argument"); + } + + auto cown = frame->stack_pop("cown to check"); + auto result = rt::is_cown_released(cown); + rt::remove_reference(frame->object(), cown); + + auto result_obj = rt::get_bool(result); + // The return will be linked to the frame by the interpreter, but the RC + // has to be increased here. + result_obj->change_rc(1); + + return result_obj; + }); } void pragma_builtins() diff --git a/src/rt/objects/dyn_object.h b/src/rt/objects/dyn_object.h index 5529eb7..9e4c126 100644 --- a/src/rt/objects/dyn_object.h +++ b/src/rt/objects/dyn_object.h @@ -268,7 +268,7 @@ namespace rt::objects } } - [[nodiscard]] DynObject* set(std::string name, DynObject* value) + [[nodiscard]] virtual DynObject* set(std::string name, DynObject* value) { assert_modifiable(); DynObject* old = fields[name]; diff --git a/src/rt/objects/region.h b/src/rt/objects/region.h index 7ce1ee9..c6f4f82 100644 --- a/src/rt/objects/region.h +++ b/src/rt/objects/region.h @@ -6,6 +6,11 @@ #include #include +namespace rt +{ + void cown_update_state(objects::DynObject* obj); +} + namespace rt::objects { class DynObject; @@ -49,6 +54,11 @@ namespace rt::objects // This guarantees that the regions for trees. Region* parent{nullptr}; + // This points to the cown that owns this region. The parent will be + // filled with the cown_region if this is owned by a cown. This ensures + // that the region can't be reparented. + DynObject* cown{nullptr}; + // The number of direct subregions, whose LRC is non-zero size_t sub_region_reference_count{0}; @@ -81,9 +91,17 @@ namespace rt::objects r->local_reference_count--; // Edge triggered LRC for parent. if (r->combined_lrc() == 0) + { dec_sbrc(r); + if (r->cown) + { + cown_update_state(r->cown); + } + } else + { action(r); + } } // Decrements sbrc for ancestors of 'r' @@ -96,6 +114,12 @@ namespace rt::objects if (r->combined_lrc() != 0) break; } + + if (r->cown) + { + cown_update_state(r->cown); + } + action(r); } @@ -149,7 +173,10 @@ namespace rt::objects ui::error("Cycle created in region hierarchy", r->bridge); } - p->direct_subregions.insert(r->bridge); + if (p) + { + p->direct_subregions.insert(r->bridge); + } // Set the parent and increment the parent reference count. r->parent = p; diff --git a/src/rt/rt.cc b/src/rt/rt.cc index be3a10a..46f8b62 100644 --- a/src/rt/rt.cc +++ b/src/rt/rt.cc @@ -313,4 +313,23 @@ namespace rt objects::dissolve_region(bridge); } + bool is_cown_released(objects::DynObject* cown) + { + if (cown->get_prototype() != core::cownPrototypeObject()) + { + ui::error("The given object is not a cown", cown); + } + + return reinterpret_cast(cown)->is_released(); + } + + 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(); + } } // namespace rt diff --git a/src/rt/rt.h b/src/rt/rt.h index 281d142..6870c04 100644 --- a/src/rt/rt.h +++ b/src/rt/rt.h @@ -64,4 +64,9 @@ namespace rt void merge_regions(objects::DynObject* src, objects::DynObject* sink); void dissolve_region(objects::DynObject* bridge); + /// This notifies the given cown that something has changed and the state + /// might need to be updated. This can cause a cown in the pending state to be + /// released. + void cown_update_state(objects::DynObject* cown); + bool is_cown_released(objects::DynObject* cown); } // namespace rt diff --git a/tests/cowns/cown_from_cown.frank b/tests/cowns/cown_from_cown.frank index 75be723..e8ba364 100644 --- a/tests/cowns/cown_from_cown.frank +++ b/tests/cowns/cown_from_cown.frank @@ -5,3 +5,8 @@ c01 = Cown(move a) # Should succeed, cowns can contain other cowns c02 = Cown(move c01) + +if is_released(c02): + pass() +else: + unreachable() diff --git a/tests/cowns/cown_from_immutable.frank b/tests/cowns/cown_from_immutable.frank index b0a5934..0cab644 100644 --- a/tests/cowns/cown_from_immutable.frank +++ b/tests/cowns/cown_from_immutable.frank @@ -6,3 +6,8 @@ freeze(share) # Should succeed, as 'share' is now immutable c = Cown(share) + +if is_released(c): + pass() +else: + unreachable() diff --git a/tests/cowns/cown_release_on_swap1.frank b/tests/cowns/cown_release_on_swap1.frank new file mode 100644 index 0000000..1bf8236 --- /dev/null +++ b/tests/cowns/cown_release_on_swap1.frank @@ -0,0 +1,21 @@ +# Creating an open region +r1 = Region() + +# Creating a cown in the pending state +c1 = Cown(r1) + +# Make sure the cown is still pending +if is_released(c1) == False: + pass() +else: + unreachable() + +# Replacing the value with a closed region should release the cown +r2 = Region() +c1.value = move r2 + +# Make sure the cown is released +if is_released(c1) == True: + pass() +else: + unreachable() diff --git a/tests/cowns/cown_release_on_swap2.frank b/tests/cowns/cown_release_on_swap2.frank new file mode 100644 index 0000000..185be20 --- /dev/null +++ b/tests/cowns/cown_release_on_swap2.frank @@ -0,0 +1,20 @@ +# Creating an open region +r1 = Region() + +# Creating a cown in the pending state +c1 = Cown(r1) + +# Make sure the cown is still pending +if is_released(c1) == False: + pass() +else: + unreachable() + +# Replacing the value with a frozen value should release the cown +c1.value = None + +# Make sure the cown is released +if is_released(c1) == True: + pass() +else: + unreachable() diff --git a/tests/cowns/cown_release_on_swap3.frank b/tests/cowns/cown_release_on_swap3.frank new file mode 100644 index 0000000..cf54a55 --- /dev/null +++ b/tests/cowns/cown_release_on_swap3.frank @@ -0,0 +1,20 @@ +# Creating an open region +r1 = Region() + +# Creating a cown in the pending state +c1 = Cown(r1) + +# Make sure the cown is still pending +if is_released(c1) == False: + pass() +else: + unreachable() + +# Replacing the value with a cown should release the cown +c1.value = Cown(None) + +# Make sure the cown is released +if is_released(c1) == True: + pass() +else: + unreachable() diff --git a/tests/cowns/cown_released_on_close1.frank b/tests/cowns/cown_released_on_close1.frank new file mode 100644 index 0000000..1143cbf --- /dev/null +++ b/tests/cowns/cown_released_on_close1.frank @@ -0,0 +1,20 @@ +# Creating an open region +r1 = Region() + +# Creating a cown in the pending state +c1 = Cown(r1) + +# Make sure the cown is still pending +if is_released(c1) == False: + pass() +else: + unreachable() + +# Force close r1 +close(r1) + +# The cown should now be released +if is_released(c1) == True: + pass() +else: + unreachable() diff --git a/tests/cowns/cown_released_on_close2.frank b/tests/cowns/cown_released_on_close2.frank new file mode 100644 index 0000000..0e078bc --- /dev/null +++ b/tests/cowns/cown_released_on_close2.frank @@ -0,0 +1,20 @@ +# Creating an open region +r1 = Region() + +# Creating a cown in the pending state +c1 = Cown(r1) + +# Make sure the cown is still pending +if is_released(c1) == False: + pass() +else: + unreachable() + +# Manually close r1 +r1 = None + +# The cown should now be released +if is_released(c1) == True: + pass() +else: + unreachable() diff --git a/tests/regressions/cown_none.frank b/tests/regressions/cown_none.frank new file mode 100644 index 0000000..4b7a0d3 --- /dev/null +++ b/tests/regressions/cown_none.frank @@ -0,0 +1 @@ +c1 = Cown(None) diff --git a/tests/regressions/cown_unknown.frank b/tests/regressions/cown_unknown.frank new file mode 100644 index 0000000..57a5e3b --- /dev/null +++ b/tests/regressions/cown_unknown.frank @@ -0,0 +1,5 @@ +c1 = Cown(None) +r1 = Region() +c2 = Cown(c1) + +