An overview of Mezzo Franois Pottier INRIA Bertinoro, June 2015 1 - - PowerPoint PPT Presentation

an overview of mezzo
SMART_READER_LITE
LIVE PREVIEW

An overview of Mezzo Franois Pottier INRIA Bertinoro, June 2015 1 - - PowerPoint PPT Presentation

An overview of Mezzo Franois Pottier INRIA Bertinoro, June 2015 1 / 91 Acknowledgements Jonathan Protzenko, Thibaut Balabonski, Henri Chataing, Armal Guneau, Cyprien Mangin. 2 / 91 What is Mezzo? Try it out in your browser: Or


slide-1
SLIDE 1

An overview of Mezzo

François Pottier

INRIA

Bertinoro, June 2015

1 / 91

slide-2
SLIDE 2

Acknowledgements

Jonathan Protzenko, Thibaut Balabonski, Henri Chataing, Armaël Guéneau, Cyprien Mangin.

2 / 91

slide-3
SLIDE 3

What is Mezzo?

An experimental programming language in the tradition of ML. Try it out in your browser:

http://gallium.inria.fr/~protzenk/mezzo-web/

Or install it:

  • pam install mezzo

3 / 91

slide-4
SLIDE 4

Premise

The types of OCaml, Haskell, Java, C#, etc.:

  • describe the structure of data,
  • but do not distinguish trees and graphs,
  • and do not control who has permission to read or write.

4 / 91

slide-5
SLIDE 5

Question

Could a more ambitious static discipline:

  • rule out more programming errors, including data races,
  • and enable new programming idioms,
  • while remaining reasonably simple and flexible?

5 / 91

slide-6
SLIDE 6

A quick comparison

In comparison with Tobias Wrigstad's talk (yesterday),

  • data race freedom and ownership transfer are goals too;
  • getting rid of GC is not;
  • types and permissions do not influence code generation;

they are erased at runtime.

6 / 91

slide-7
SLIDE 7

Outline

A first example and a few principles Write-once references: usage Mezzo: (some) design principles Write-once references: interface & implementation Mezzo: the good and the bad Algebraic data structures Sharing mutable data Conclusion

7 / 91

slide-8
SLIDE 8

A first example and a few principles

Write-once references: usage

8 / 91

slide-9
SLIDE 9

Write-once references

A write-once reference:

  • can be written at most once;
  • can be read only after it has been written.

Let us look at a concrete example of use...

9 / 91

slide-10
SLIDE 10

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-11
SLIDE 11

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-12
SLIDE 12

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-13
SLIDE 13

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-14
SLIDE 14

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-15
SLIDE 15

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-16
SLIDE 16

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-17
SLIDE 17

Usage

. . writable . .

new

. frozen .

set

.

get

.

  • pen woref

val r1 = new () (* r1 @ writable *) val r2 = r1 (* r1 @ writable * r2 = r1 *) val () = set (r1, 3); (* r1 @ frozen int * r2 = r1 *) val x2 = get r2 (* r1 @ frozen int * r2 = r1 * x2 @ int *) val rs = (r1, r2) (* r1 @ frozen int * r2 = r1 * x2 @ int * rs @ (=r1, =r2) *) (* rs @ (frozen int, frozen int) *)

. . Demo!

10 / 91

slide-18
SLIDE 18

A first example and a few principles

Mezzo: (some) design principles

11 / 91

slide-19
SLIDE 19

Permissions

Like a program logic, the static discipline is flow-sensitive.

  • A current (set of) permission(s) exists at each program point.
  • Different permissions exist at different points.

Permissions do not exist at runtime.

12 / 91

slide-20
SLIDE 20

Permissions

Thus, there is no such thing as the type of a variable x. Instead,

  • at each program point in the scope of x,
  • there may be zero, one, or more permissions to use x

in certain ways.

13 / 91

slide-21
SLIDE 21

Layout and ownership

Permissions have layout and ownership readings.

  • e.g., r @ writable

x @ t describes the shape and extent of a heap fragment,

rooted at x, and describes certain access rights for it. “To know about x” is “to have access to x” is “to own x”.

14 / 91

slide-22
SLIDE 22

Just two access modes

Every permission is either duplicable or affine. The basic rules are:

  • Immutable data is duplicable, i.e., shareable.
  • Mutable data is affine, i.e., uniquely owned.
  • Mutable data can become immutable; not the converse.

15 / 91

slide-23
SLIDE 23

Aliasing

  • Writing let x = y in ...

gives rise to an equation x = y.

  • It is a permission:

x @ =y, where =y is a singleton type.

  • In its presence, x @ t and y @ t are interconvertible.
  • Thus, any name is as good as any other.
  • The same idea applies to let x = xs.head in ... .

16 / 91

slide-24
SLIDE 24

Value ̸= permission

A value can be copied (always). No permission is required.

(* empty *) let y = (x, x) in (* y @ (=x, =x) *)

17 / 91

slide-25
SLIDE 25

Value ̸= permission

A duplicable permission can be copied. This is implicit.

(* x @ int *) let y = (x, x) in (* x @ int * y @ (=x, =x) *) (* x @ int * y @ (int, int) *)

18 / 91

slide-26
SLIDE 26

Value ̸= permission

A duplicable permission can be copied. This is implicit.

(* x @ int *) let y = (x, x) in (* x @ int * y @ (=x, =x) *) (* x @ int * y @ (int, int) *)

18 / 91

slide-27
SLIDE 27

Value ̸= permission

An affine permission cannot be copied.

(* x @ ref int *) let y = (x, x) in (* x @ ref int * y @ (=x, =x) *) assert y @ (ref int, ref int) (* WRONG! *)

In other words, mutable data cannot be shared.

19 / 91

slide-28
SLIDE 28

Value ̸= permission

An affine permission cannot be copied.

(* x @ ref int *) let y = (x, x) in (* x @ ref int * y @ (=x, =x) *) assert y @ (ref int, ref int) (* WRONG! *)

In other words, mutable data cannot be shared.

19 / 91

slide-29
SLIDE 29

Examples of duplicable versus affine

  • x @ list int is duplicable: read access can be shared.
  • x = y is duplicable: equalities are forever.
  • x @ mlist int and x @ list (ref int) are affine: they give

exclusive access to part of the heap.

20 / 91

slide-30
SLIDE 30

Separation

x @ ref int * y @ ref int implies x and y are distinct.

Conjunction is separating at mutable data.

z @ (t, u) means z @ (=x, =y) * x @ t * y @ u, for x, y fresh.

Hence, product is separating.

21 / 91

slide-31
SLIDE 31

Separation

The same principle applies to records. Hence, list (ref int) denotes a list of distinct references. Mutable data must be tree-structured.

  • though x @ ref (=x) can be written and constructed.

22 / 91

slide-32
SLIDE 32

A first example and a few principles

Write-once references: interface & implementation

23 / 91

slide-33
SLIDE 33

Specification

A usage protocol can be described in a module signature:

  • A state is a (user-defined) type.
  • A transition is a (user-defined) function.

24 / 91

slide-34
SLIDE 34

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

.

25 / 91

slide-35
SLIDE 35

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

a state

25 / 91

slide-36
SLIDE 36

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

another state

25 / 91

slide-37
SLIDE 37

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

implicit transition from frozen to frozen * frozen

25 / 91

slide-38
SLIDE 38

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

explicit transition into writable

25 / 91

slide-39
SLIDE 39

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

set requires r (dynamic) and r @ writable (static)

25 / 91

slide-40
SLIDE 40

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

consumes keyword means r @ writable NOT returned

25 / 91

slide-41
SLIDE 41

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

duplicable a is a permission

25 / 91

slide-42
SLIDE 42

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

explicit transition from writable to frozen

25 / 91

slide-43
SLIDE 43

Specification of write-once refs

This protocol has two states and four transitions. This is the interface file woref.mzi:

abstract writable . abstract frozen a . fact duplicable (frozen a) . val new: () -> writable . val set: [a] (consumes r: .writable .

.

, x: a | duplicable .a)

  • > (| r @ frozen a) .

val get: [a] frozen a -> a .

. .

get r requires r @ frozen a

25 / 91

slide-44
SLIDE 44

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

.

26 / 91

slide-45
SLIDE 45

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

a field of type ()

26 / 91

slide-46
SLIDE 46

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

a field of type a where a must be duplicable

26 / 91

slide-47
SLIDE 47

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

initially, r @ writable

26 / 91

slide-48
SLIDE 48

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

hence, r @ Writable { contents: () }

26 / 91

slide-49
SLIDE 49

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

after the assignment, r @ Writable { contents: =x }

26 / 91

slide-50
SLIDE 50

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

hence, r @ Writable { contents: a }

26 / 91

slide-51
SLIDE 51

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

after the tag update, r @ Frozen { contents: a }

26 / 91

slide-52
SLIDE 52

Implementation

This is the implementation file woref.mz:

data mutable writable = Writable { contents: () .} data frozen a = Frozen { contents: .(a | duplicable a) } val new () : writable = Writable { contents = () } val set [a] (consumes r: writable, x: a | duplicable a) : (| r @ frozen a) =

.r.contents <- x; .

tag of r <- Frozen . (* this is a no-op *) val get [a] (r: frozen a) : a = r.contents

. .

hence, r @ frozen a

26 / 91

slide-53
SLIDE 53

A first example and a few principles

Mezzo: the good and the bad

27 / 91

slide-54
SLIDE 54

The good

The uniqueness of read/write permissions:

  • rules out several categories of errors:
  • data races; hence, shared-memory concurrency is safe;
  • representation exposure;
  • violations of (certain) object protocols.
  • allows the type of an object to vary with time, which enables:
  • explicit memory re-use;
  • gradual initialization;
  • describing (certain) object protocols.

28 / 91

slide-55
SLIDE 55

The good

Here are some other positive aspects:

  • all of the power of ML, and more;
  • higher-order functions, pattern matching, polymorphism, etc.
  • no need to annotate types with owners;
  • to have a permission is to own
  • ownership transfer is easy;
  • just pass (or return, or store, or extract) a permission
  • no need to annotate function types with effects.
  • just pass and return a permission

29 / 91

slide-56
SLIDE 56

The good

Moving an element into or out of a container is easy. Here is a typical container interface:

abstract bag a val new: [a] () -> bag a val insert: [a] (bag a, consumes a) -> () val extract: [a] bag a -> option a

30 / 91

slide-57
SLIDE 57

The bad

The discipline forbids sharing mutable data. For this reason, borrowing an element from a container is typically restricted to duplicable elements:

val find: [a] duplicable a => (a -> bool) -> list a -> option a

This affects user-defined data structures, arrays, regions, etc.

31 / 91

slide-58
SLIDE 58

The bad

Fortunately,

  • there is no restriction on the use of immutable data;
  • there are several ways of sharing mutable data:
  • (static) nesting; regions;
  • (dynamic) adoption & abandon;
  • (dynamic) locks.

32 / 91

slide-59
SLIDE 59

Outline

A first example and a few principles Algebraic data structures (More) Principles Computing the length of a list Melding mutable lists Concatenating immutable lists Sharing mutable data Conclusion

33 / 91

slide-60
SLIDE 60

Algebraic data structures

(More) Principles

34 / 91

slide-61
SLIDE 61

Immutable lists

The algebraic data type of immutable lists is defined as in ML:

data list a = | Nil | Cons { head: a; tail: list a }

35 / 91

slide-62
SLIDE 62

Mutable lists

To define a type of mutable lists, one adds a keyword:

data mutable mlist a = | MNil | MCons { head: a; tail: mlist a }

36 / 91

slide-63
SLIDE 63

Examples

For instance,

  • x @ list int provides (read) access to an immutable list of

integers, rooted at x.

  • x @ mlist int provides (exclusive, read/write) access to a

mutable list of integers at x.

  • x @ list (ref int) offers read access to the spine and

read/write access to the elements, which are distinct cells.

37 / 91

slide-64
SLIDE 64

Permission refinement

Permission refinement takes place at case analysis. .

match xs with | MNil ->

.

... | MCons ->

.

let x = xs.head in

.

... end

In contrast, traditional separation logic has untagged union. .

38 / 91

slide-65
SLIDE 65

Permission refinement

Permission refinement takes place at case analysis. .

match xs with | MNil ->

.

... | MCons ->

.

let x = xs.head in

.

... end

In contrast, traditional separation logic has untagged union. . .

a nominal permission: xs @ mlist a

38 / 91

slide-66
SLIDE 66

Permission refinement

Permission refinement takes place at case analysis. .

match xs with | MNil ->

.

... | MCons ->

.

let x = xs.head in

.

... end

In contrast, traditional separation logic has untagged union. . .

a structural permission: xs @ MNil

38 / 91

slide-67
SLIDE 67

Permission refinement

Permission refinement takes place at case analysis. .

match xs with | MNil ->

.

... | MCons ->

.

let x = xs.head in

.

... end

In contrast, traditional separation logic has untagged union. . .

another structural permission: xs @ MCons { head: a; tail: mlist a }

38 / 91

slide-68
SLIDE 68

Permission refinement

Permission refinement takes place at case analysis. .

match xs with | MNil ->

.

... | MCons ->

.

let x = xs.head in

.

... end

In contrast, traditional separation logic has untagged union. . .

automatically expanded to: xs @ MCons { head: (=h); tail: (=t) } * h @ a * t @ mlist a

38 / 91

slide-69
SLIDE 69

Permission refinement

Permission refinement takes place at case analysis. .

match xs with | MNil ->

.

... | MCons ->

.

let x = xs.head in

.

... end

In contrast, traditional separation logic has untagged union. . .

  • r (sugar):

xs @ MCons { head = h; tail = t } * h @ a * t @ mlist a

38 / 91

slide-70
SLIDE 70

Permission refinement

Permission refinement takes place at case analysis. .

match xs with | MNil ->

.

... | MCons ->

.

let x = xs.head in

.

... end

In contrast, traditional separation logic has untagged union. . .

so, after the read access: xs @ MCons { head = h; tail = t } * h @ a * t @ mlist a * x = h

38 / 91

slide-71
SLIDE 71

Principles

This illustrates two mechanisms:

  • A nominal permission can be unfolded and refined,

yielding a structural permission.

  • A structural permission can be decomposed,

yielding separate permissions for the block and its fields. These reasoning steps are implicit and reversible.

39 / 91

slide-72
SLIDE 72

Algebraic data structures

Computing the length of a list

40 / 91

slide-73
SLIDE 73

Interface

Here is the type of the length function for mutable lists.

val length: [a] mlist a -> int

It should be understood as follows:

  • length requires one argument xs,

along with the permission xs @ mlist a.

  • length returns one result n,

along with the permission xs @ mlist a * n @ int.

41 / 91

slide-74
SLIDE 74

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

.

42 / 91

slide-75
SLIDE 75

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

initially: xs @ mlist a

42 / 91

slide-76
SLIDE 76

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

upon entry into the first branch: xs @ MNil

42 / 91

slide-77
SLIDE 77

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

upon exit of the first branch: xs @ MNil

42 / 91

slide-78
SLIDE 78

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

upon exit of the first branch: xs @ mlist a

42 / 91

slide-79
SLIDE 79

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

upon entry into the second branch: xs @ MCons { head = h; tail = t } h @ a t @ mlist a

42 / 91

slide-80
SLIDE 80

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

after the call, nothing has changed: xs @ MCons { head = h; tail = t } h @ a t @ mlist a

42 / 91

slide-81
SLIDE 81

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

thus, by recombining: xs @ MCons { head: a; tail: mlist a }

42 / 91

slide-82
SLIDE 82

Implementation

val rec length_aux [a] (accu: int, xs: mlist a) : int = match xs with . | MNil -> . accu . | MCons -> . length_aux (accu + 1, xs.tail) .

.

end val length [a] (xs: mlist a) : int = length_aux (0, xs)

. .

thus, by folding: xs @ mlist a

42 / 91

slide-83
SLIDE 83

Tail recursion versus iteration

The analysis of this code is surprisingly simple.

  • This is a tail-recursive function, i.e.,

a loop in disguise.

  • As we go, there is a list ahead of us and

a list segment behind us.

  • Ownership of the latter is implicit, i.e.,

framed out. Recursive reasoning, iterative execution.

43 / 91

slide-84
SLIDE 84

Algebraic data structures

Melding mutable lists

44 / 91

slide-85
SLIDE 85

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

.

45 / 91

slide-86
SLIDE 86

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs is not consumed: at the end, it is still a valid non-empty list

45 / 91

slide-87
SLIDE 87

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

at the end, ys is accessible through xs, hence must no longer be used directly

45 / 91

slide-88
SLIDE 88

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail = t } t @ MNil ys @ mlist a

45 / 91

slide-89
SLIDE 89

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail = ys } t @ MNil ys @ mlist a

45 / 91

slide-90
SLIDE 90

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail: mlist a } t @ MNil

45 / 91

slide-91
SLIDE 91

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail: mlist a }

45 / 91

slide-92
SLIDE 92

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail = t } t @ MCons { head: a; tail: mlist a } ys @ mlist a

45 / 91

slide-93
SLIDE 93

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail = t } t @ MCons { head: a; tail: mlist a }

45 / 91

slide-94
SLIDE 94

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail = t } t @ mlist a

45 / 91

slide-95
SLIDE 95

Melding mutable lists (1/2)

val rec meld_aux [a] (xs: .MCons { head: a; tail: mlist a }, consumes .ys: mlist a) : () = match xs.tail with | MNil

  • > .

xs.tail <- ys . | MCons -> . meld_aux (xs.tail, ys) . end

. .

xs @ MCons { head: a; tail: mlist a }

45 / 91

slide-96
SLIDE 96

Melding mutable lists (2/2)

val meld [a] (consumes xs: mlist a, consumes ys: mlist a) : mlist a = match xs with | MNil

  • > ys

| MCons -> meld_aux (xs, ys); xs end

46 / 91

slide-97
SLIDE 97

Algebraic data structures

Concatenating immutable lists

47 / 91

slide-98
SLIDE 98

Three states

. .

MCons

.

head

.

tail

An MCons cell:

  • mutable,
  • uninitialized tail,
  • type: MCons { head:

a; tail: () }

. .

Cons

.

head

.

tail

An isolated Cons cell:

  • immutable,
  • not the start of a well-formed list,
  • type: Cons { head:

a; tail = t }

. .

Cons

.

head

.

tail

A list cell:

  • immutable,
  • the start of a well-formed list,
  • type list a

48 / 91

slide-99
SLIDE 99

The big picture

. .

MCons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-100
SLIDE 100

The big picture

. .

MCons

.

head

.

tail

.

MCons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-101
SLIDE 101

The big picture

. .

Cons

.

head

.

tail

.

MCons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-102
SLIDE 102

The big picture

. .

Cons

.

head

.

tail

.

MCons

.

head

.

tail

.

MCons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-103
SLIDE 103

The big picture

. .

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

MCons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-104
SLIDE 104

The big picture

. .

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-105
SLIDE 105

The big picture

. .

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-106
SLIDE 106

The big picture

. .

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-107
SLIDE 107

The big picture

. .

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

Cons

.

head

.

tail

.

xs

.

ys

49 / 91

slide-108
SLIDE 108

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

.

50 / 91

slide-109
SLIDE 109

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

all three inputs are consumed

50 / 91

slide-110
SLIDE 110

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

dst is initially unfinished

50 / 91

slide-111
SLIDE 111

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

xs and ys are initially valid

50 / 91

slide-112
SLIDE 112

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

upon return, dst is valid

50 / 91

slide-113
SLIDE 113

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

dst.tail is initialized

50 / 91

slide-114
SLIDE 114

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

dst is frozen

50 / 91

slide-115
SLIDE 115

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

xs @ Cons { head = h; tail = t } dst @ Cons { head: a; tail = dst' } dst' @ MCons { head: a; tail: () } t @ list a ys @ list a

50 / 91

slide-116
SLIDE 116

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

dst @ Cons { head: a; tail = dst' } dst' @ MCons { head: a; tail: () } t @ list a ys @ list a

50 / 91

slide-117
SLIDE 117

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

dst @ Cons { head: a; tail = dst' } dst' @ list a

50 / 91

slide-118
SLIDE 118

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

dst @ Cons { head: a; tail: list a }

50 / 91

slide-119
SLIDE 119

Concatenating immutable lists (1/2)

val rec append_aux [a] (consumes .( dst: MCons { head: a; tail: () }, .

.

xs: list a, ys: list a . )) : (| dst @ list a) .= match xs with | Cons -> let dst' = MCons { head = xs.head; tail = () } in dst.tail <- dst'; . tag of dst <- Cons; . append_aux (dst', xs.tail, ys) .

.

| Nil -> dst.tail <- ys; tag of dst <- Cons end

. .

dst @ list a

50 / 91

slide-120
SLIDE 120

Concatenating immutable lists (2/2)

val append [a] (consumes (xs: list a, ys: list a)) : list a = match xs with | Cons -> let dst = MCons { head = xs.head; tail = () } in append_aux (dst, xs.tail, ys); dst | Nil -> ys end

51 / 91

slide-121
SLIDE 121

Remark

The type of append:

[a] (consumes (list a, list a)) -> list a

is a subtype of:

[a] (list a, list a | duplicable a) -> list a

The arguments are consumed only if not duplicable.

52 / 91

slide-122
SLIDE 122

Outline

A first example and a few principles Algebraic data structures Sharing mutable data Regions (and nesting) Adoption and abandon Locks Conclusion

53 / 91

slide-123
SLIDE 123

What have we so far?

An affine permission is a (static) unique token. We have seen that we can

  • aggregate several tokens,

yielding a token for a (tree-structured) composite object

  • conversely, split a token for a tree

into separate tokens for the root and sub-trees

54 / 91

slide-124
SLIDE 124

What have we so far?

We have seen that pointer and permission are distinct concepts: either one can exist without the other. We have exploited this at a very local scale, e.g. when type-checking meld and append. Yet, we have not exploited this in algebraic data type definitions.

  • we always marry a pointer to a sub-tree

and a permission to access it

55 / 91

slide-125
SLIDE 125

What have we so far?

As long as we stick to this style, we cannot express:

  • aliasing, where an object is accessible via two pointers;
  • shared memory, where an object is accessible to two threads.

56 / 91

slide-126
SLIDE 126

What do we need?

We need ways of saying, roughly,

  • “this is a pointer...”
  • “...without a permission...”
  • “...but here is how to get the permission when needed.”

57 / 91

slide-127
SLIDE 127

Sharing mutable data

Regions (and nesting)

58 / 91

slide-128
SLIDE 128

Regions

A region is a group of objects (of identical type). There is one permission for the group, instead of one per object. A region does not exist at runtime. It is imaginary. See e.g. Haskell's ST monad. See also Cyclone (Swamy et al., 2006).

59 / 91

slide-129
SLIDE 129

Regions

An affine type of regions - internally defined as the unit type:

abstract region val newregion: () -> region

A duplicable type of mutable references that inhabit a region:

abstract rref (r : value) a fact duplicable (rref r a)

These objects can be shared without restriction.

60 / 91

slide-130
SLIDE 130

Regions

val newrref: (consumes x: a | r @ region) -> rref r a val get: (x: rref r a | duplicable a | r @ region) -> a val set: (x: rref r a, consumes y: a | r @ region) -> ()

All three are polymorphic in r and a. Quantifiers omitted. The token r @ region is required to use any reference in r. The references are collectively “owned by the region”.

61 / 91

slide-131
SLIDE 131

Limitations

Regions have no runtime cost. However,

  • get is restricted to duplicable elements (prev. slide).
  • Handling affine elements requires a more clumsy mechanism

for focusing on at most one element at a time.

  • Focusing on two elements, also known as multi-focusing,

would entail a proof obligation: x ̸= y.

  • Membership in a region cannot be revoked.

62 / 91

slide-132
SLIDE 132

A word about nesting

Nesting (Boyland, 2010) is a static mechanism for organizing permissions into a hierarchy. The hierarchy is constructed as the program runs and grows with time. Nesting can be axiomatized in Mezzo (by adding a few primitive

  • perations which do nothing at runtime).

Regions can be defined as a library on top of nesting. Like regions, nesting has limitations (prev. slide).

63 / 91

slide-133
SLIDE 133

Sharing mutable data

Adoption and abandon

64 / 91

slide-134
SLIDE 134

Towards runtime regions

What if something like regions existed at runtime? Old idea, if one thinks of a region as a “memory allocation area”.

  • Tofte and Talpin, 1994

Here, however, there is a single garbage-collected heap. We are thinking of a “region” as a “unit of ownership”.

65 / 91

slide-135
SLIDE 135

Towards runtime regions

Imagine a “region” is a runtime object that maintains a list of its “members”. We prefer to speak of adopter and adoptees. Conceptually,

  • Adoption (a.k.a. give) adds an adoptee to the list.
  • Abandon (a.k.a. take) extracts an adoptee from the list,
  • and fails at runtime if it isn't in the list!

66 / 91

slide-136
SLIDE 136

Adoption and abandon

This removes the difficulties with static regions.

  • an adopter-adoptee relationship can be revoked.
  • “focusing” amounts to taking an adoptee away from its

adopter, then giving it back.

  • “focusing” on multiple elements is permitted.
  • they must be distinct, or the program fails at runtime!

67 / 91

slide-137
SLIDE 137

One typical application

A FIFO queue as a linked list with first and last pointers. There is aliasing. This cannot be type-checked in vanilla Mezzo. We let the “queue” object adopt all of the “list cell” objects. The code type-checks (but could fail at runtime if we mistakenly break our intended invariant). See P. and Protzenko, ICFP 2013.

68 / 91

slide-138
SLIDE 138

Implementation

Searching a linked list of adoptees would be too slow. Instead, each adoptee points to its adopter (if it has one). Every object has a special adopter field, which may be null.

  • Adoption, give x to y, means:

x.adopter <- y

  • Abandon, take x from y, means:

if x.adopter == y then x.adopter <- null else fail

69 / 91

slide-139
SLIDE 139

Static discipline, in one slide

An adopter owns its adoptees. Adoption and abandon are very much like inserting and extracting an element out of a container:

  • both require a permission for the adopter;
  • adoption consumes a permission for the new adoptee;

abandon allows recovering it. . Demo!

70 / 91

slide-140
SLIDE 140

Static discipline, in one slide

An adopter owns its adoptees. Adoption and abandon are very much like inserting and extracting an element out of a container:

  • both require a permission for the adopter;
  • adoption consumes a permission for the new adoptee;

abandon allows recovering it. . . Demo!

70 / 91

slide-141
SLIDE 141

Sharing mutable data

Locks

71 / 91

slide-142
SLIDE 142

Towards hidden state

Regions and adoption-and-abandon serve a common purpose:

  • move from one-token-per-object to one-token-per-group;
  • introduce a duplicable type of pointer-into-the-group;
  • thus permitting aliasing within a group.

72 / 91

slide-143
SLIDE 143

Towards hidden state

A problem remains, though:

  • every bit of mutable state is controlled by some unique token;
  • i.e., every side effect must be advertised in a function's type;
  • thus, multiple clients must coordinate and exchange a token.

There is a certain lack of modularity.

73 / 91

slide-144
SLIDE 144

Example

Consider a “counter” abstraction, encapsulated as a function.

  • it has abstract state: its type is {p : perm} ((| p) -> int | p).
  • it cannot be shared by two threads,
  • unless they synchronize and exchange p;
  • without synchronization, there would be a data race!

A well-typed Mezzo program is data-race free. . . Demo!

74 / 91

slide-145
SLIDE 145

Example

Consider a “counter” abstraction, encapsulated as a function.

  • it has abstract state: its type is {p : perm} ((| p) -> int | p).
  • it cannot be shared by two threads,
  • unless they synchronize and exchange p;
  • without synchronization, there would be a data race!

A well-typed Mezzo program is data-race free. . . Demo!

74 / 91

slide-146
SLIDE 146

Locks and hidden state

Introducing a lock at the same time:

  • removes the data race,
  • allows the counter to have type () -> int.

The counter now has hidden state. Let's see how this works...

75 / 91

slide-147
SLIDE 147

Locks (1/2)

The axiomatization of locks begins with two abstract types:

abstract lock (p: perm) fact duplicable (lock p) abstract locked

The permission p is the lock invariant.

76 / 91

slide-148
SLIDE 148

Locks (2/2)

The basic operations are:

val new: (| consumes p)

  • > lock p

val acquire: (l: lock p)

  • > (| p * l @ locked)

val release: (l: lock p | consumes (p * l @ locked)) -> ()

All three are polymorphic in p. Quantifiers omitted.

77 / 91

slide-149
SLIDE 149

The key idea

From concurrent separation logic (O'Hearn, 2007). While the lock is unlocked, one can think of p as owned by the lock. The lock is shareable, since lock p is duplicable. Hence, a lock allows sharing and hiding mutable state.

78 / 91

slide-150
SLIDE 150

Hiding as a design pattern

The pattern of hiding a function's internal state can be encoded

  • nce and for all as a second-order function:

val hide : [a, b, p : perm] ( f : (a | p) -> b | consumes p ) -> (a -> b)

79 / 91

slide-151
SLIDE 151

Hiding as a design pattern

The pattern of hiding a function's internal state can be encoded

  • nce and for all as a second-order function:

val hide [a, b, p : perm] ( f : (a | p) -> b | consumes p ) : (a -> b) = let l : lock p = new () in . fun (x : a) : b =

. acquire l; .

let y = f x in . release l; . y

.

80 / 91

slide-152
SLIDE 152

Hiding as a design pattern

The pattern of hiding a function's internal state can be encoded

  • nce and for all as a second-order function:

val hide [a, b, p : perm] ( f : (a | p) -> b | consumes p ) : (a -> b) = let l : lock p = new () in . fun (x : a) : b =

. acquire l; .

let y = f x in . release l; . y

. .

l @ lock p

80 / 91

slide-153
SLIDE 153

Hiding as a design pattern

The pattern of hiding a function's internal state can be encoded

  • nce and for all as a second-order function:

val hide [a, b, p : perm] ( f : (a | p) -> b | consumes p ) : (a -> b) = let l : lock p = new () in . fun (x : a) : b =

. acquire l; .

let y = f x in . release l; . y

. .

l @ lock p because it is duplicable

80 / 91

slide-154
SLIDE 154

Hiding as a design pattern

The pattern of hiding a function's internal state can be encoded

  • nce and for all as a second-order function:

val hide [a, b, p : perm] ( f : (a | p) -> b | consumes p ) : (a -> b) = let l : lock p = new () in . fun (x : a) : b =

. acquire l; .

let y = f x in . release l; . y

. .

l @ lock p l @ locked p

80 / 91

slide-155
SLIDE 155

Hiding as a design pattern

The pattern of hiding a function's internal state can be encoded

  • nce and for all as a second-order function:

val hide [a, b, p : perm] ( f : (a | p) -> b | consumes p ) : (a -> b) = let l : lock p = new () in . fun (x : a) : b =

. acquire l; .

let y = f x in . release l; . y

. .

l @ lock p l @ locked p

80 / 91

slide-156
SLIDE 156

Hiding as a design pattern

The pattern of hiding a function's internal state can be encoded

  • nce and for all as a second-order function:

val hide [a, b, p : perm] ( f : (a | p) -> b | consumes p ) : (a -> b) = let l : lock p = new () in . fun (x : a) : b =

. acquire l; .

let y = f x in . release l; . y

. .

l @ lock p

80 / 91

slide-157
SLIDE 157

Rules of thumb

Regarding regions versus adoption and abandon,

  • they serve the same purpose, namely one-token-per-group;
  • use regions if possible, otherwise adoption and abandon.

Regarding locks,

  • they serve a different purpose, namely no-token-at-all;
  • they are typically used in conjunction with the above.
  • a lock protects a token that controls a group of objects.

81 / 91

slide-158
SLIDE 158

Outline

A first example and a few principles Algebraic data structures Sharing mutable data Conclusion

82 / 91

slide-159
SLIDE 159

Sources of inspiration

Mezzo draws inspiration from many sources. Most influential:

  • Linear and affine types (Wadler, 1990) (Plasmeijer et al., 1992).
  • not every value can be copied!
  • Alias types (Smith, Walker & Morrisett, 2000),

L3 (Ahmed, Fluet & Morrisett 2007).

  • copying a value is harmless,
  • but not every capability can be copied!
  • keep track of equations between values via singleton types.
  • Regions and focusing in Vault (Fähndrich & DeLine, 2002);
  • Separation logic (Reynolds, 2002) (O'Hearn, 2007).
  • ownership is in the eye of the beholder.
  • separation by default; local reasoning.
  • a lock owns its invariant.

83 / 91

slide-160
SLIDE 160

What distinguishes Mezzo?

It is a high-level programming language:

  • algebraic data types preferred to records and null pointers;
  • (tail) recursion preferred to iteration;
  • garbage collection, first-class functions, polymorphism, etc.
  • to some extent, lightweight types (i.e., no owner annotations).

84 / 91

slide-161
SLIDE 161

Shortcomings

It is far from perfect:

  • type inference can be unpredictable;
  • it takes a black belt to understand type errors;
  • there is currently no interoperability with OCaml.

85 / 91

slide-162
SLIDE 162

Food for thought

At the present time I think we are on the verge of discovering at last what programming languages should really be like. [...] My dream is that by 1984 we will see a consensus developing for a really good programming language [...] Donald E. Knuth, 1974.

86 / 91

slide-163
SLIDE 163

Food for thought

At the present time I think we are on the verge of discovering at last what programming languages should really be like. [...] My dream is that by 1984 we will see a consensus developing for a really good programming language [...] Donald E. Knuth, 1974.

86 / 91

slide-164
SLIDE 164

Food for thought

At the present time I think we are on the verge of discovering at last what programming languages should really be like. [...] My dream is that by 1984 we will see a consensus developing for a really good programming language [...] Donald E. Knuth, 1974.

86 / 91

slide-165
SLIDE 165

Thank you

More information online:

http://gallium.inria.fr/~protzenk/mezzo-lang/

87 / 91

slide-166
SLIDE 166

What distinguishes Mezzo?

Technically, some novel features of Mezzo are:

  • the permission discipline replaces the type discipline;
  • a new view of algebraic data types, with nominal and structural

permissions, and a new “tag update” instruction;

  • a new, lightweight treatment of the distinction between

duplicable and affine data;

  • adoption and abandon.

88 / 91

slide-167
SLIDE 167

Who we are

The project was launched in late 2011 and has involved

  • Jonathan Protzenko (Ph.D student, soon to graduate),
  • Thibaut Balabonski (post-doc researcher),
  • Henri Chataing, Armaël Guéneau, Cyprien Mangin (interns),
  • and myself (INRIA researcher).

89 / 91

slide-168
SLIDE 168

Where we are

We currently have:

  • a type soundness proof for a subset of Mezzo;
  • a working type-checker;
  • a “compiler” down to untyped OCaml.

90 / 91

slide-169
SLIDE 169

What next?

Many questions!

  • Can we improve type inference and type error reports?
  • Is this a good mix between static and dynamic mechanisms?
  • What about temporary read-only views of mutable objects?
  • Can we express complex object protocols?
  • What about specifications & proofs of programs?

91 / 91