Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,24 @@ You can run in interactive mode by running:

Which will keep overwritting the `mermaid.md` file with the new heap state after each step.

## Lungfish

FrankenScript has been submitted as an PLDI25 artifact to explain the *Lungfish*
Ownership Model implemented here.

A critical part of Lungfish is the write-barrier shown in Figure 6 of the Paper. FrankenScript implements
these functions in `src/rt/objects/region.cc`. The important functions are:

* `add_reference(source, target)`: This adds a new reference from `source` to `target`.
* `add_to_region(region, target, source)`: This adds `target` and all reachable nodes to `region` if possible.
* `remove_reference(source, old_target)`: This removes a reference from `source` to `old_target`
* `move_reference(old_src, new_src, target)`: This is the `writeBarrier()` function, which adds a new reference
from `new_src` to `target` and removes the reference from `old_src` to `target`.

The interpreter, implemented in `src/lang/interpreter.cc`, calls these functions via the public API of the
runtime (`rt::add_reference`, `rt::remove_reference`, `rt::move_reference`). The `add_to_region()` method is
never called directly by the interpreter.

A good example for the write-barrier is the `StoreField` bytecode implementation:

https://github.com/fxpl/frankenscript/blob/30e431c7ba8022f29fb4927913976bbf18356789/src/lang/interpreter.cc#L303-L318
20 changes: 10 additions & 10 deletions src/rt/objects/region.cc
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,10 @@ namespace rt::objects
add_region_reference(src_region, target, src);
}

void remove_reference(DynObject* src_initial, DynObject* old_dst_initial)
void remove_reference(DynObject* source, DynObject* old_target)
{
visit(
{src_initial, "", old_dst_initial},
{source, "", old_target},
[&](Edge e) {
if (e.target == nullptr)
return false;
Expand All @@ -238,26 +238,26 @@ namespace rt::objects
Region::collect();
}

void move_reference(DynObject* src, DynObject* dst, DynObject* target)
void move_reference(DynObject* old_src, DynObject* new_src, DynObject* target)
{
assert(src != nullptr);
assert(dst != nullptr);
assert(old_src != nullptr);
assert(new_src != nullptr);
if (target == nullptr || target->is_immutable() || target->is_cown())
{
return;
}

auto src_region = get_region(src);
auto dst_region = get_region(dst);
if (src_region == dst_region)
auto old_src_region = get_region(old_src);
auto new_src_region = get_region(new_src);
if (old_src_region == new_src_region)
{
return;
}

auto old_target_region = get_region(target);
auto old_target_bridge = old_target_region->bridge;

add_region_reference(dst_region, target, src);
add_region_reference(new_src_region, target, old_src);

// If the bridge was implicitly frozen we don't need to remove
// the region reference. In fact, we shouldn't since the region
Expand All @@ -269,7 +269,7 @@ namespace rt::objects

// Note that the region of the target might have changed after the
// `add_region_refernce` call
remove_region_reference(src_region, old_target_region);
remove_region_reference(old_src_region, old_target_region);
}

void Region::clean_lrcs_and_close(Region* to_close_reg)
Expand Down