K. Rustan M. Leino
10 October 2023, IFIP WG 2.3, Trento, Italy
class Cell {
var data: int
method Increment()
modifies this
{
data := data + 1;
}
}
class Machine {
var motor: Motor
var screen: Monitor
method Operate()
modifies this, motor, screen
{
motor.Turn();
screen.Update();
}
}
class Machine {
ghost var Repr: set<object>
...
var motor: Motor
var screen: Monitor
method Operate()
...
modifies Repr
{
motor.Turn();
screen.Update();
}
}
datatype Machine =
| Machine(motor: Motor, screen: Monitor)
{
method OperateImmutable() returns (this': Machine)
{
var motor' := motor.TurnImmutable();
var screen' := screen.UpdateImmutable();
this' := Machine(motor', screen');
}
}
datatype Machine =
| Machine(r: region, motor: Motor, screen: Monitor)
{
method OperateImmutable() returns (this': Machine)
{
var Machine(r, motor, screen) := this;
motor.Turn();
screen.Update();
this' := Machine(r, motor, screen);
}
}
The immutable datatype uses a region that it modifies, and the client never needs to know!
class Cell {
var data: int
}
method Inc(c: Cell)
modifies c
{
c.data := c.data + 1;
}
method Inc(: Heap, c: Cell) returns (: Heap)
modifies c
{
c.data := c.data + 1;
}
method Inc`(c: Cell)
modifies c
{
c.data := c.data + 1;
}
method Inc```(c: Cell`, d: Cell`, e: Cell`)
modifies c`
{
c`.data := c`.data + d`.data + e`.data;
}
method Inc```(c: Cell`, d: Cell`, e: Cell`)
modifies c
{
c.data := c.data + d.data + e.data;
}
If regions are distinct and are parts of types, then
method Inc2``(c: Cell`, e: Cell`)
modifies c
{
var : region;
var d := new Cell`;
d.data := 1;
Inc```(c, d, e);
}
The region names in this method body can probably be inferred.
{
var outer: Cell;
{
var : region;
var inner := new Cell`;
// the following assignment is ill-typed,
// because the type of `outer` cannot mention
outer := inner;
}
}
The lexical regions are similar to what the programming language Euclid provided
provenance
noun
the place of origin or earliest known history of something
There are many names:
I will use the term tracked types
Method ::= method M(x*) returns (x*) BlockStmt
BlockStmt ::= { Stmt* }
Stmt ::=
| var x
| x := Expr // simple assignment
| x* := M(Expr*) // method call
| var Ctor(x*) := Expr // datatype destructor
| if Expr BlockStmt else BlockStmt
| BlockStmt
Expr ::=
| x // variable
| F(Expr*) // constants, operators, functions
| Ctor(Expr*) // datatype constructor
A language like this is typical when defining linear types.
The judgment
U;V S U';V'
says that if
U
and
V
are available when statement S
is started, then
U'
and
V'
are available after statement S
.
U;V E : m
says that, if U;V
are available, then expression E
evaluates to a value of mode m
(un
/tr
).
If x
requires manual initialization:
U;V var x U;V
If x
is auto-initialized:
x : un
----------------------
U;V var x U,x;V
x : tr
----------------------
U;V var x U;V,x
If type of x
requires manual initialization:
U;V var x U;V
If x
is auto-initialized:
U;V var x U;V +x
x : m'
U;V E : m
m' m
-------------------------------
U;V,V x := E U;V +x
U;V E : _
U;V S U;V
U;V T U;V
U' = U U
V' = V V
---------------------------------
U;V,V if E S else T U';V'
U;V { S } U;V
------------------------------------
U;V { S S ... S } U;V
M : (m) returns (m)
U;V E : m
x : m'
m' m
------------------------------------
U;V,V x := M(E) U;V +x
datatype m, Ctor : m
U;V E : m
x : m'
m' m
-------------------------------------------
U;V,V var Ctor(x) := E U;V +x
Note that dereliction is built into the rules, but that's okay,
because of the ... E : m
requirement above
and the x : m'
in the rule for simple assignment
x : m
x U V
------------
U;V x : m
F : m m
U;V E : m
----------------
U;V F(E) : m
datatype m, Ctor : m
U;V E : m
---------------------
U;V Ctor(E) : m
Stmt ::= ...
| x := new Type` // object allocation
| Expr`.f := Expr // object field update
Expr ::= ...
| Expr`.f // object field selection
V
---------------------------------
U;V x := new Type` U;V +x
class m, f : m'
U;,V E : m
U;,V E : m'
--------------------------------------
U;,V,V,V E`.f := E U;,V
class m, f : m'
U;V E : m
V
-----------------
U;V E`.f : m'
(At least) three things are missing/wrong:
tracked class QueryEngine {
var : region
var cache: Cache`
...
method Operate()
{
var , cache := this; // "unpack this"
cache`.Lookup();
...
this := new QueryEngine{ , cache }; // "pack this"
}
}
This seems unnatural for objects.
But maybe any “tracked object” should really be a tracked datatype value?
tracked datatype Counter =
| Counter(: region, cell: Cell`)
{
method Inc() returns (this': Counter)
{
var Counter(, cell) := this;
var x := cell`.data + 1; // RHS consumes cell
cell`.data := x; // LHS consumes cell
this' := Counter(, cell);
}
}
tracked datatype Machine =
| Machine(: region, motor: Motor`, screen: Monitor`)
{
predicate Valid() {
var Machine(, motor, screen) := this;
screen`.vsize == screen`.hsize
}
}
This is a complication in Chalice / Viper
U;V
environment to U;B;V
where B
has the shared values
ordering on un
,sh
,tr
?
sh
need to be contained, like regions do? (Lifetimes) Can this mechanism build on the Type`
mechanism?Is a method always implicitly allowed to extend a given region?
method UndeclaredAllocations`() {
var cell := new Cell`;
cell.data := 12;
}
Can you do local storage with this region mechanism? (Then, is there a distinction between regions that can or cannot be extended?)
method LocalStorage() {
var : region;
var cell := new Cell`;
cell`.DoSomething();
AnotherMethod`();
}
Can these regions be used as a programming device? (Then, do all regions need to be present at run time?)
method EfficientDeallocation() {
var : region;
...
SomeMethod`(...);
...
// is automatically freed here
}