Skip to content

Commit f62016b

Browse files
authored
Examples and fixes for PLDI'25 #66
PlDI25 examples
2 parents a6179ea + 4e6e293 commit f62016b

File tree

14 files changed

+387
-12
lines changed

14 files changed

+387
-12
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,4 @@ set_property(TEST frozen_bridge_segfault.frank PROPERTY WILL_FAIL true)
9090
set_property(TEST unresolved_field.frank PROPERTY WILL_FAIL true)
9191
set_property(TEST unresolved_global.frank PROPERTY WILL_FAIL true)
9292
set_property(TEST unresolved_name.frank PROPERTY WILL_FAIL true)
93+
set_property(TEST func_no_return.frank PROPERTY WILL_FAIL true)

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ model inspired by [behaviour-oriented concurrency](https://doi.org/10.1145/36228
77
To this end, FrankenScript programs generate a file called `mermaid.md` with a Mermaid diagram per line in the source program showing the object and region
88
graph of the program at that program point.
99

10+
This is a legend for the diagrams that FrankenScript generates:
11+
12+
![](./docs/frankenscript-legend.png)
13+
1014
## Pre-requisites
1115

1216
This project is C++20 based and uses CMake as the build system. We also recommend installing Ninja to speed up the build process.
@@ -49,3 +53,4 @@ You can run in interactive mode by running:
4953
```
5054

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

docs/frankenscript-legend.png

292 KB
Loading

examples/01-objects.frank

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# This file shows how different objects can be created and used in FrankenScript
2+
#
3+
# The following line creates a dictionary and assigns it to the variable `a`.
4+
# The variable name is on the arrow from the frame to the object. The object
5+
# displays the memory address and the reference count of the object.
6+
a = {}
7+
8+
# Fields of dictionaries can be assigned in two ways:
9+
a["field1"] = {} # 1
10+
a.field2 = {} # 2
11+
12+
# Fields can be accessed the same way
13+
b = a.field1
14+
c = a["field1"]
15+
16+
# FrankenScipt uses reference counting. Values are deallocated when the reference
17+
# count hits 0. This can be used to remove unused elements from the diagram.
18+
a = None
19+
b = None
20+
c = None
21+
22+
# FrankenScript also provides strings, created by double quotes:
23+
d = "This is a string"
24+
25+
# All objects in FrankenScript are dictionaries. The language uses prototypes
26+
# to share functionality across objects and to identify the type of object.
27+
# The diagram currently shows the prototype for frame objects (called `[Frame]),
28+
# and the prototype for strings (called `[String]`).
29+
#
30+
# Prototypes can be accessed via the `__proto__` field.
31+
e = d.__proto__
32+
33+
# The prototype can also be written to. This creates an object `g` with the
34+
# prototype `f`
35+
f = {}
36+
f.field = {}
37+
g = {}
38+
g.__proto__ = f
39+
40+
# If a field is not found on an object the prototype will be checked for the
41+
# field. In this example, a reference to `f.field` is returned since `g` doesn't
42+
# have a field called `field` but the prototype has one.
43+
h = g.field

examples/02-freezing.frank

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Freezing describes the action of making an object immutable.
2+
#
3+
# Objects in FrankenScript are mutable by default.
4+
x = {}
5+
6+
# Immutable objects are ice blue and not contained by a specific region
7+
freeze(x)
8+
9+
# Assigning to a field of `x` afterward will throw an error
10+
# x.field = {} # ERROR
11+
12+
# Calling `freeze(x)` freezes the object `x` points to but not the variable
13+
# itself, so x can be reassigned
14+
x = {}
15+
x.f = {} # Mutation of x
16+
17+
# Freezing is deep, meaning that all objects reachable from a
18+
# frozen object will also be frozen. This is needed for safe sharing
19+
# across concurrent readers. See §2.1 for more details
20+
freeze(x)
21+
22+
# The immutable status in Lungfish refers to the observable state.
23+
# This means that the reference count can remain mutable as long as
24+
# it is not observable in the program. Notice how this new reference
25+
# increases the reference count. See §5.3.5 for more details.
26+
xx = x
27+
28+
# Setting all references to an immutable object to `None`
29+
# will deallocate it.
30+
xx = None
31+
x = None

examples/03-regions1.frank

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Lungfish uses regions to track and enforce ownership dynamically.
2+
# The previous examples only worked in the local region drawn in light
3+
# green. This example shows how new regions can be created and used.
4+
#
5+
# The `Region()` constructor creates a new region and returns its bridge
6+
# object. The new region is drawn in yellow. Any object in the yellow
7+
# rectangle belongs to the region. The trapezoid shape denotes the
8+
# bridge object. It displays the following information about the region:
9+
# - LRC: The number of incoming references from the local region. This
10+
# counter tracks references to all objects in the region not
11+
# just the bridge object. See §3.1 for more details.
12+
# - SBRC: The number of open subregions.
13+
# - RC: The reference count of the bridge object.
14+
r = Region()
15+
16+
# Any objects reachable from the bridge or an object in the region
17+
# will automatically be part of the region. Notice how the new dictionaries
18+
# are members of the region, indicated by them being in the same yellow box.
19+
r.field = {}
20+
r.field.data = {}
21+
22+
# Objects inside a region have no topological restrictions. As such we can create
23+
# cycles, like this:
24+
r.field.bridge = r
25+
r.field.data.parent = r.field
26+
27+
# All objects are by created in the local region.
28+
x = {}
29+
x.data = {}
30+
31+
# An object in the local region is moved into a region, when an object in the
32+
# region references it. All reachable objects from the moved object are also
33+
# moved into the region. Figure 7 in the paper shows the individual steps of
34+
# this process.
35+
r.x = x
36+
37+
# Moving the value of `x` into the region increased the LRC since the variable `x`
38+
# is a reference from the local frame into the region. Reassigning `x` to another
39+
# value will decrement the LRC again. This is done by a write barrier, discussed in
40+
# §3.2 of the paper.
41+
x = None
42+
43+
# References to frozen objects are unrestricted. Table 1 in the paper provides a
44+
# good overview of what references are allowed.
45+
r.x.data = None
46+
47+
# When a region has no incoming references it and all contained objects can
48+
# be deallocated as a unit. This even allows the collection of topologies
49+
# with cycles.
50+
r = None

examples/04-regions2.frank

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# 03-regions1.frank covered how new regions can be created. This
2+
# example covers how ownership is enforced with regions.
3+
r1 = Region()
4+
r1.data = {}
5+
r2 = Region()
6+
7+
# Region 1 owns an object with the name `data`. All objects can at most have
8+
# one owner. Creating a reference from one region to an object in another region
9+
# will result in an error
10+
# r2.data = r1.data # Error
11+
12+
# However, creating a single reference to a bridge object of another region
13+
# is allowed. This reference is called the owning reference, indicated by
14+
# the solid orange arrow. The region of the referenced bridge object is now
15+
# a subregion of the first region.
16+
r1.data.r2 = r2
17+
18+
# Attempts to add additional owning references will result in an error:
19+
# r1.new = r2 # Error
20+
21+
# Regions are arranged in a forest. It is not allowed to create a cycle
22+
# between regions. Attempting to make `r1` a subregion of `r2` which is
23+
# currently a subregion of `r1` will result in an error:
24+
# r2.r1 = r1 # Error
25+
26+
# Region 2 is currently considered open as it has an incoming reference from
27+
# the local region. This is indicated by the LRC not being zero. The parent
28+
# region `r1` has a SBRC of 1 indicating that a subregion of it is open.
29+
#
30+
# Removing the local reference into `r2` will close the region and also decrement
31+
# the SBRC of the parent region.
32+
r2 = None
33+
34+
# Creating a reference into the subregion will open it again.
35+
r2 = r1.data.r2
36+
37+
# Subregions can be unparented again by overwriting the owning reference.
38+
# This is possible since there can only be one owing reference at a time.
39+
r1.data.r2 = None
40+
41+
# Region 2 can now take ownership of `r1`.
42+
r2.r1 = r1
43+
44+
# Regions can also be frozen to allow multiple regions to reference them.
45+
# The bridge object of the frozen region will remain, but it will lose the
46+
# `[RegionObject]` prototype.
47+
freeze(r1)
48+
49+
# The previous bridge object referenced by `r1` can now be referenced by other
50+
# regions.
51+
r3 = Region()
52+
r3.r1 = r1

examples/05-regions3.frank

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# 04-regions2.frank covered how ownership is enfored with regions.
2+
# This example covers some built-in functions for regions.
3+
4+
r1 = Region()
5+
r1.data1 = {}
6+
r1.data2 = {}
7+
8+
# The `is_closed()` function can be used to check if a region
9+
# is open. Region 1 is currently considered open since it has
10+
# an incoming reference from the local region.
11+
res = is_closed(r1)
12+
13+
# Region 2 is created to be the new owner of Region 1.
14+
r2 = Region()
15+
r2.child = r1
16+
17+
# The `close()` function can be used to set all incoming local
18+
# references to None.
19+
close(r1)
20+
21+
# We can also check the status with `is_closed`.
22+
res = is_closed(r2.child)
23+
24+
# A subregion can be merged into its parent region, with the
25+
# `merge()` function. The bridge object of the subregion loses
26+
# the `[RegionObject]` prototype, since it is no longer the bridge
27+
# object of a region.
28+
merge(r2.child, r2)
29+
30+
# Regions can also be dissolved, thereby moving all contained
31+
# objects back into the local region.
32+
#
33+
# This function differs syntactically from the paper which
34+
# uses `merge(r2)`.
35+
dissolve(r2)
36+
37+
# This can be used to reconstruct regions. This example uses
38+
# a for loop:
39+
r1 = Region()
40+
for key, value in r2.child:
41+
r1[key] = value
42+
r2.child = r1

examples/06-cowns.frank

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# This is the sixth example and assumes the knowledge of the
2+
# previous examples.
3+
#
4+
# Lungfish uses concurrent owners, called Cowns, to coordinate
5+
# concurrent access at runtime. Cowns and their states are
6+
# introduced in §2.3.1 of the paper.
7+
#
8+
# Cowns can store immutable objects, regions and other cowns.
9+
# The following will create a cown pointing to the built-in
10+
# frozen value `None`.
11+
c1 = Cown(None)
12+
13+
# The cown has the status "Released" which means that a concurrent
14+
# unit can aquire it and access its data. Attempting to access
15+
# the data in this state will result in an error.
16+
# ref = c1.value # Error
17+
18+
# Regions are used to track ownership of mutable data. This can be
19+
# combined with cowns to safely share data. This created an open region:
20+
r1 = Region()
21+
r1.data = {}
22+
data = r1.data
23+
24+
# A cown created from an open region has the status "Pending Release".
25+
# This status allows access to the contained value. However, it will
26+
# switch to the "Released" status when the region is closed
27+
c2 = Cown(r1)
28+
close(c2.value)
29+
30+
# Cowns can be safely shared across threads since they dynamically enforce
31+
# ownership and concurrent coordination at runtime. They are therefore allowed
32+
# to be referenced by frozen objects. The freeze will not affect the cown
33+
# or the contained values.
34+
x = {}
35+
x.cown = c2
36+
freeze(x)
37+
38+
# Cowns can also be referenced from one or multiple regions like this:
39+
r2 = Region()
40+
r2.cown = c2
41+
r3 = Region()
42+
r3.cown = c2
43+
44+
# Regions referencing a cown can still be closed and sent. This example
45+
# uses the `move` keyword to perform a destructive read and close the
46+
# region as part of the cown creation. The cown is therefore created in
47+
# the "Released" status
48+
c3 = Cown(move r2)
49+
50+
# Cowns can also point to other cowns. The second cown is also
51+
# in the "Released" state since the given cown is allowed to
52+
# have local references.
53+
c4 = Cown(c2)

examples/07-expressions.frank

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# The previous examples introduced objects, freezing, regions and cowns.
2+
# This example is a showcase of most expressions that FrankenScript
3+
# supports. It can be used as a reference for writing your own scripts.
4+
#
5+
# A list of built-in function can be found in the `docs` folder. Here is
6+
# a link to the rendered version on GitHub:
7+
# <https://github.com/fxpl/frankenscript/blob/main/docs/builtin.md>
8+
9+
# This is how you construct the objects discussed in example 01
10+
a = {}
11+
x = "a new string"
12+
x = Region()
13+
x = Cown(None)
14+
15+
# FrankenScript has a few built-in and frozen objects:
16+
x = True # The boolean value `true`
17+
x = False # The boolean value `false`
18+
x = None # The null value, similar to Python's None
19+
20+
# FrankenScript supports functions with arguments and return values.
21+
def id(x):
22+
return x
23+
24+
# A function can be called like this. Each function call adds a new frame
25+
# to the diagram. The frame holds all variables known to the current scope.
26+
# The frame is deleted when the function returns.
27+
id(a)
28+
29+
# Function objects are hidden from the mermaid to make it cleaner. But they're
30+
# still normal objects that can be used in assignments like this:
31+
a.id = id
32+
33+
# This can be used to simulate method calls. Calling a function on stored in a
34+
# field will pass the object in as the first argument.
35+
a = a.id() # a is passed in as the first argument
36+
37+
# The move keyword can be used for destructive reads. The previous location
38+
# is reassigned to None.
39+
b = move a
40+
41+
# FrankenScript has two comparison operators. These check for object identity:
42+
res = b == None
43+
res = b != None
44+
45+
# Boolean values can be used in if statements:
46+
if res:
47+
pass() # A built-in function for empty blocks
48+
else:
49+
unreachable() # A built-in function for unreachable branches
50+
51+
# The else block is optional:
52+
if res:
53+
pass()
54+
55+
# Boolean expressions can also be used in while loops:
56+
while res == True:
57+
res = False
58+
59+
# For loops can be used to iterate over all fields of an object.
60+
a = {}
61+
a.field = "value"
62+
for key, value in b:
63+
pass()

0 commit comments

Comments
 (0)