Controlling Mutation and Aliases with Fractional Permissions John Boyland University of Wisconsin- Milwaukee ECOOP 12
Outline of Session I. Fractional Permissions II. Applications III. Problems
I. Fractional Permissions
Structured Aliasing? My Thoughts
Structured Aliasing? My Thoughts Aliasing is relevant ONLY if mutable state.
Structured Aliasing? My Thoughts Aliasing is relevant ONLY if mutable state. For isolation/parallelism, we want tracking.
Structured Aliasing? My Thoughts Aliasing is relevant ONLY if mutable state. For isolation/parallelism, we want tracking. Fractions: don t copy tokens, split tokens.
Structured Aliasing? My Thoughts Aliasing is relevant ONLY if mutable state. For isolation/parallelism, we want tracking. Fractions: don t copy tokens, split tokens. Unify separation logic & ownership.
Fractional Permissions
Fractional Permissions Rough recipe:
Fractional Permissions Rough recipe: Separation Logic
Fractional Permissions Rough recipe: Separation Logic - Scalars
Fractional Permissions Rough recipe: Separation Logic - Scalars + Fractions
Fractional Permissions Rough recipe: Separation Logic - Scalars + Fractions + Nesting (AKA Adoption)
Separation Logic Linear heap predicates flow-sensitive state information Nonlinear scalar predicates Hoare logic, and e.g., y = x + z Linear and nonlinear connectives and, or, implication, existentials,... [Reynolds, O Hearn, Brookes, Parkinson...]
Minus Scalars Separation Logic intends (full) verification. We do verification of limited properties: aliasing, mutation, concurrency object structure, design patterns Thus we drop integer and boolean values: only interested in pointer values.
Plus Fractions We distinguish write (one) from read (less than one) permission to mutable state. Important for concurrency correctness. Some variants of SL have fractions in some limited situations. Fractions are endemic for us.
Plus Nesting Motivation: nonlinear (flow-insensitive) analysis is simpler. Add a way to express flow-insensitive ownership along with flow-sensitive uniqueness aka adoption [Fähndrich&DeLine 2002] Interesting interactions with fractions.
I. Outline Fractions Fractional Heaps Fractional Permission Logic
Fractions
Fractions Q + positive rational numbers (R + OK).
Fractions Q + positive rational numbers (R + OK). No zero (0 permission = no permission).
Fractions Q + positive rational numbers (R + OK). No zero (0 permission = no permission). Both addition and multiplication are total and permit cancellation.
Fractions Q + positive rational numbers (R + OK). No zero (0 permission = no permission). Both addition and multiplication are total and permit cancellation. Fractions can be arbitrarily small.
Fractions Q + positive rational numbers (R + OK). No zero (0 permission = no permission). Both addition and multiplication are total and permit cancellation. Fractions can be arbitrarily small. Fractions greater than one permitted in intermediate heaps.
Fractional Heaps A partial map from locations L to pairs of values V and fractions Q + L = O F, pointer and field V = O, pointers to objects. A memory has only fractions of one.
Fractional Heaps 1
Scaling Fractional Heaps 1 ½
Some Heaps Add 1
Some Heaps Add 1
Some Heaps Don t Add 1
Some Heaps Don t Add 1
Some Heaps Don t Add 1 Different Values Clash
Memory 1
Memory 1 whole fraction
Memory 1 not defined
Memory
Memory 1
Permission Logic o.f o 1 (o,f) o A unit permission (Wrigstad token )
Permission Logic 1 + 2 Heap for 1 Heap for 2
Permission Logic 1 + 2
Permission Logic Π 1 +Π 2 Heap for 1 Heap for 2
Permission Logic Π 1 +Π 2
Permission Logic 0 q Heap for
Permission Logic 0 q q = 3/2
Permission Logic Existential: r (o.f r)+π obligatory unit permission yields witness Conditional: Γ?Π 1 :Π 2 Here Γ is a boolean formula true or false
Boolean Formulae true conjunction negation comparison Γ 1 Γ 2 Γ o = o All these can be true, false or unknown
Boolean Formulae existential unrestricted predicates calls can be recursive x Γ p(...) Either can be true or unknown (not false).
Boolean Formulae nesting Π o.f ownership indicates that is available in : there is Π 0, o.f o by itself, as with all formulae, has no heap. Known nesting may be less that actual. Π 1 Π 2 Π 0 +Π 1 =Π 2
Unit Permission Again o.f o (o,f) o { Π o.f 1
Unit Permission Again o.f o (o,f) o 1{ Π o.f Π 1 o.f where 1 Π 1 Π
Example: Point permission to write o s x and y fields: ( r o.x r)+ ( r o.y r) preceding permission nested in o s All field ( r o.x r)+ ( r o.y r) o.all
Example: Point permission to write o s x and y fields: ( r o.x r)+ ( r o.y r) model field preceding permission nested in o s All field ( r o.x r)+ ( r o.y r) o.all
Example: Point A point is an object we know has x and y: Point(r) = ( r r.x r )+ ( r r.y r ) o is a Point Point(o) o is a Point and we can read its state: Point(o)+q(o.All 0) r.all
Nesting o.x o x + o.y o y + o.all 0 1 1 1
Nesting o.x o x + o.y o y + o.all 0 1 1 1 Point(o)+o.All 0 1
Carving Point(o)+q(o.All 0) (o,all) (o,x) (o,y) 1
Carving Point(o)+q(o.All 0) (o,all) (o,x) (o,y) 1 1
Carving Point(o)+q(o.All 0) (o,all) (o,x) (o,y) 1 1 1
Carving Point(o)+q(o.All 0) (o,all) (o,x) (o,y) q(o.x r x )+(q(o.x r x ) + q(o.all 0)) 1 1 1
In General: Invariants Invariant(o,...) nests state in o.all Invariant(o,...) + o.all 0 gives one access Carving permits breaking the invariant: o.x r x +((o.x r x ) + (o.all 0)) Until o.all 0 is needed again.
Linked-List Node Field data is a unique Point; Field next is a unique Node: Node(r) = rp r.data r p + r p.all 0 + Point(r p )+ r n r.next r n + r n.all 0 + Node(r n ) r.all
Linked-List Node Field data is a unique Point; Field next is a unique Node: Node(r) = rp r.data r p + r p.all 0 + Point(r p )+ r n r.next r n + r n.all 0 + Node(r n ) r.all Can you spot the problem?
Linked-List Node Field data is a unique Point; Field next is a unique Node: Node(r) = rp r.data r p + r p.all 0 + Point(r p )+ r n r.next r n + r n.all 0 + Node(r n ) r.all
Linked-List Node Field data is a unique Point; Field next is a unique Node: Node(r) = rp r.data r p + r p.all 0 + Point(r p )+ r n r.next r n + r n.all 0 + Node(r n ) r.all Every node points to a new next node!
Linked-List Node Node(r) = r p r.data r p + r p.all 0 + Point(r p )+ r n r.next r n + r.all r n =0?0 : r n.all 0 + Node(r n ) 0
Linked-List Node Node(r) = r p r.data r p + r p.all 0 + Point(r p )+ r n r.next r n + r.all r n =0?0 : r n.all 0 + Node(r n ) not null unique 0
Linked-List Node Node(r) = r p r.data r p + r p.all 0 + Point(r p )+ r n r.next r n + r.all r n =0?0 : r n.all 0 + Node(r n ) maybe null unique 0
Two Element List (n 1,All) (n 1,data) (p 1,All) (p 1,x) (p 1,y) (n 1,next) (n 2,All) (n 2,data) (p 2,All) (p 2,x) (p 2,y) (n 2,next) Node(n 1 )+ n 1.All 0 p 1 n 2 p 2 1
Two Element List (n 1,All) (n 1,data) (p 1,All) (p 1,x) (p 1,y) (n 1,next) (n 2,All) (n 2,data) (p 2,All) (p 2,x) (p 2,y) (n 2,next) Node(n 1 )+ n 1.All 0 p 1 n 2 p 2 1 1 Node(n 1 )+ = + 2 (n 1.All 0) Node(n 1 )+ 1 2 (n 1.All 0) 1 1
Effective Uniqueness Read permission does not guarantee unique: n 1.data = n 2.data is possible DETOUR 1
1 Effective Uniqueness Read permission does not guarantee unique: n 1.data = n 2.data is possible
Node(l)+ l.all 0 Node n = l; Iteration while (n!= null) { Point p = n.data; p.x += 3; n = n.next; }
Node(l)+ l.all 0 Node n = l; Iteration while (n!= null) { Point p = n.data; p.x += 3; n = n.next; } Node(l)+(n = l)+ l.all 0
Node(l)+ l.all 0 Node n = l; Iteration while (n!= null) { Point p = n.data; p.x += 3; n = n.next; } Node(l)+(n = l)+ l.all 0 Node(n)+n.All 0+ n.all 0 + l.all 0
Node(l)+ l.all 0 Node n = l; Iteration while (n!= null) { Point p = n.data; p.x += 3; n = n.next; } Node(l)+(n = l)+ l.all 0 Node(n)+n.All 0+ n.all 0 + l.all 0 Point(p)+p.All 0+ p.all 0 + n.all 0+ n.all 0 + l.all 0
Node(l)+ l.all 0 Node n = l; Iteration while (n!= null) { Point p = n.data; p.x += 3; n = n.next; } Node(l)+(n = l)+ l.all 0 Node(n)+n.All 0+ n.all 0 + l.all 0 p.x r x + p.x r x + p.all 0+ p.all 0 + n.all 0+ n.all 0 + l.all 0
Node(l)+ l.all 0 Node n = l; Iteration while (n!= null) { Point p = n.data; p.x += 3; n = n.next; } Node(l)+(n = l)+ n =0?0:n.All 0 + Node(n)+ l.all 0 Node(n)+n.All 0+ n.all 0 + l.all 0 (n =0?0:n.All 0 + Node(n)) + l.all 0
II. Applications
Annotations as Sugar (maybe null) unique field s value nested with o.all 0 method effects are lent permissions writes(x.f) x.f r reads(x.f) z(x.f r) passed in on entry; passed out on exit
Annotations as Sugar (maybe null) unique field s value nested with o.all 0 method effects are lent permissions writes(x.f) x.f r reads(x.f) z(x.f r) passed in on entry; passed out on exit Fraction variable
Annotations as Sugar Shorthand: r.all r.all 0 Ownership: one object s state in another: owned(o)r r.all o.all
Independent Iterators interface Iterator { boolean hasnext(); Object next(); void remove(); }
Independent Iterators interface Iterator { reads(all) boolean hasnext(); writes(all) Object next(); writes(all) void remove(); }
Why Independent? 1. Utility methods can use iterator alone. 2. Iterators can be concatenated 3. Iteration creation can be delegated.
Why Independent? #1 class Util { writes(it.all) static int count(iterator it, Object x) { int c = 0; while (it.hasnext()) { if (x == it.next()) ++c; } return c; } } UTILITY
Why Independent? #2 class AppendIterator implements Iterator { unique Iterator it1; unique Iterator it2; AppendIterator(unique Iterator i1, unique Iterator i2) { it1 = i1; it2 = i2; } reads(all) boolean hasnext() { return it1.hasnext() it2.hasnext(); }... CONCATENATION
Why Independent? #3 class SpecializedCollection { Object[] data;... Iterator iterator() { return Arrays.asList(data).iterator(); }... } DELEGATION
Can Iterators be Independent? Collections are affected by iterators: Can remove using iterator. Iterators are affected by collections: If collection modified, iterators invalid.
Aliasing Iterator and collection share mutable state: Iterator Collection
Interference R-W; W-R; W-W 1. access through iterator 2. access through collection R-W: iterator becomes invalid W-R: collection results different W-W: both
Avoiding Interference 1 R-R is OK If we can prevent interference: we can pretend no state overlap Iterator Collection
Avoiding Interference 2 prevent collection writes while iterators in use prevent collection reads while remove iterator in use
Avoiding Interference 2 prevent collection writes while iterators in use prevent collection reads while remove iterator in use creating iterator requires read access creating remove iterator requires write access
Avoiding Interference 3 prevent collection writes while iterators in use prevent collection reads while remove iterator in use creating iterator encumbers read access creating remove iterator encumbers write access
List Representation class LinkedList<T, Object o> { class Node { T data; Node next; }
List Representation class LinkedList<T, Object o> { class Node { owned(o) T data; unique Node next; }
List Representation class LinkedList<T, Object o> { class Node { owned(o) T data; unique Node next; } r d r.data r d + (r d =0?0:r d.all r o.all) + r n r.next r n + (r n =0?0:r n.all + Node(r n,r o )) Node(r, r o )= r.all
List Representation class LinkedList<T, Object o> { class Node { owned(o) T data; unique Node next; } unique Node head;
List Representation class LinkedList<T, Object o> { class Node { owned(o) T data; unique Node next; } unique Node head; r h r.head r h + (r h =0?0:r h.all + Node(r h,r o )) LinkedList(r, r o )= r.all
Iterator Representation class LinkedList<T, Object o> { class Iterator<fraction q> { Node pre; }
Iterator Representation class LinkedList<T, Object o> { class Iterator<fraction q> { Node pre; } Iterator(r, r o,z,r l )= r p r.pre r p + r p =0?0:zr p.all + Node(r p,r o )+ (r p =0?0:zr p.all + Node(r p,r o )) + zr l.all r.all
Iterator Creation class LinkedList<T, Object o> { Iterator<z> iterator() { return new Iterator(); }
Iterator Creation class LinkedList<T, Object o> { Iterator<z> iterator() { return new Iterator(); } this In: zr t.all
Iterator Creation class LinkedList<T, Object o> { Iterator<z> iterator() { return new Iterator(); } In: zr t.all Out: r r.all + (r r.all + zr t.all + v) result
Iterator Creation class LinkedList<T, Object o> { Iterator<z> iterator() { return new Iterator(); } In: zr t.all Out: r r.all + (r r.all + zr t.all + v) some leftover permissions
Iterator Heaps LinkedList(l, o)+ 1 2 l.all 0 (l,all) (l,head) (n 1,All) (n 1,data) (n 1,next) (n 2,All) (n 2,data) (n 2,next) n 1 n 2 BEFORE 1 1
Iterator Heaps LinkedList(l, o)+ 1 2 l.all 0 (l,all) (l,head) (n 1,All) (n 1,data) (n 1,next) (n 2,All) (n 2,data) (n 2,next) n 1 n 2 Iterator(i, o, 1 2,l)+i.All + (i.all + 1 l.all + v) 2 BEFORE 1 SOMETIME AFTER 1
Iterator Heaps LinkedList(l, o)+ 1 2 l.all 0 (l,all) (l,head) (n 1,All) (n 1,data) (n 1,next) (n 2,All) (n 2,data) (n 2,next) n 1 n 2 Iterator(i, o, 1 2,l)+i.All + (i.all + 1 l.all + v) 2 BEFORE 1 SOMETIME AFTER 1
Iterator Heaps LinkedList(l, o)+ 1 2 l.all 0 (l,all) (l,head) (n 1,All) (n 1,data) (n 1,next) (n 2,All) (n 2,data) (n 2,next) n 1 n 2 Iterator(i, o, 1 2,l)+i.All + (i.all + 1 l.all + v) 2 n 2 (i,all) (i,pre) BEFORE 1 SOMETIME AFTER 1
Iterator Heaps LinkedList(l, o)+ 1 2 l.all 0 (l,all) (l,head) (n 1,All) (n 1,data) (n 1,next) (n 2,All) (n 2,data) (n 2,next) n 1 n 2 Iterator(i, o, 1 2,l)+i.All + (i.all + 1 l.all + v) 2 n 1 n 2 (i,all) (i,pre) BEFORE 1 SOMETIME AFTER 1
Concurrency Non-interacting parallelism: Divide permissions between threads. Mutual exclusion locks ( synchronized ): Protected permissions nested in lock. Volatile fields, can be accessed anytime: State of value nested in known location.
Noninteracting Parallelism 1
Noninteracting Parallelism 1 1 1
Noninteracting Parallelism 1
Synchronization
Synchronization Objects serving as locks are owned by Lock: l.all G.Lock
Synchronization Objects serving as locks are owned by Lock: l.all G.Lock State protected by a lock is nested in it: e.g. l.val l.all
Synchronization Objects serving as locks are owned by Lock: l.all G.Lock State protected by a lock is nested in it: e.g. l.val l.all Synchronization provides l.all: requires l.all or l after last locked.
Synchronization synchronized (p) { } p.x += 1;
Synchronization Point(r p ) (r p.all G.Lock) (l <r p ) synchronized (p) { p.x += 1; }
Synchronization Point(r p ) (r p.all G.Lock) (l <r p ) synchronized (p) { } p.x += 1; New formula that compares lock order. Here l is the previously locked location.
Synchronization Point(r p ) (r p.all G.Lock) (l <r p ) synchronized (p) { p.x += 1; } r p.all
Synchronization Point(r p ) (r p.all G.Lock) (l <r p ) synchronized (p) { } p.x += 1; r p.all r p.x+(r p.x + r p.all)
Synchronization THREAD GLOBAL synchronized (p) { } p.x += 1; 1 1
Synchronization THREAD GLOBAL synchronized (p) { } p.x += 1; 1 1
Synchronization THREAD GLOBAL synchronized (p) { } p.x += 1; 1 1
Synchronization synchronized (p) { } p.x += 1;
Synchronization REENTRANCY synchronized (p) { } p.x += 1;
Synchronization REENTRANCY r p.all synchronized (p) { p.x += 1; }
Synchronization synchronized (p) { p.x += 1; } REENTRANCY r p.all r p.all
Synchronization REENTRANCY r p.all synchronized (p) { } p.x += 1; r p.all r p.x+(r p.x + r p.all)
Synchronization REENTRANCY r p.all synchronized (p) { } p.x += 1; r p.all r p.x+(r p.x + r p.all) r p.all
Volatile Fields Can be assigned at any time. Value assigned should have state nested... 1. In known owner, or 2. In G.Lock, or 3. Fractionally in G.Immutable.
Volatile Fields (1) THREAD1 p = new Point(1,2); // nest p in thread2 THREAD2 Thread Object x.v = p; 1 1
Volatile Fields (1) THREAD1 THREAD2 p = new Point(1,2); // nest p in thread2 x.v = p; 1 1
Volatile Fields (1) THREAD1 THREAD2 p = new Point(1,2); // nest p in thread2 x.v = p; 1 1
Volatile Fields (1) THREAD1 THREAD2 p = new Point(1,2); // nest p in thread2 x.v = p; 1 1
Volatile Fields (2) THREAD1 GLOBAL p = new Point(1,2); // make p a lock x.v = p; 1 1
Volatile Fields (2) THREAD1 GLOBAL p = new Point(1,2); // make p a lock x.v = p; 1 1
Volatile Fields (2) THREAD1 GLOBAL p = new Point(1,2); // make p a lock x.v = p; 1 1
Volatile Fields (2) THREAD1 GLOBAL p = new Point(1,2); // make p a lock x.v = p; 1 1
Immutability Nest any fraction of r.all in G.Immutable: z (zr.all G.Immutable) Implicitly: Every thread nests a fraction of G.Immutable Every method gets a fraction of G.Immutable Fractions can get arbitrarily small.
Volatile Fields (3) THREAD1 THREAD2 p = new Point(1,2); // make p immutable x.v = p; (G,Immutable) (G,Immutable) 1 1
Volatile Fields (3) THREAD1 THREAD2 p = new Point(1,2); // make p immutable x.v = p; (G,Immutable) (G,Immutable) 1 1
Volatile Fields (3) THREAD1 THREAD2 p = new Point(1,2); // make p immutable x.v = p; (G,Immutable) (G,Immutable) 1 1
Volatile Fields (3) THREAD1 THREAD2 p = new Point(1,2); // make p immutable x.v = p; (G,Immutable) (G,Immutable) 1 1
III. Problems
AWT/Swing (J)Component state can only be accessed from Swing Event Threads If an Event Thread blocks on input (e.g. using a JDialog), new thread is created/reused. Runnable instances can be passed to Swing.
Java Concurrency wait() releases lock temporarily Thread.start(), Thread.join() Anyone can join a thread, multiple times java.util.concurrent.reentrantlock can lock/unlock independently
Related Work Ownership Reads Linearity
Related Work Ownership Reads [Clarke 01] Linearity
Related Work Ownership Reads [Clarke & Drossopoulou 02] Linearity
Related Work Ownership Reads [Müller & Rudich 07] Linearity
Related Work Ownership Reads Linearity
Related Work Ownership Reads Linearity [Ishtiaq & O Hearn 01]
Related Work Ownership Reads [Parkinson & Bierman 12] Linearity
Related Work Ownership Reads [Fähndrich & Deline 02] Linearity
Related Work Ownership Reads [Bornat et al 05/Brookes 06] Linearity
Related Work Ownership Reads Linearity
Related Work Ownership [Bierhoff & Aldrich 07] Reads Linearity