Swift Memory and Concurrency model

The goal of this writeup is to provide a safe and efficient way to model, design, and implement concurrent applications in Swift.  It is believed that it will completely eliminate data races and lock deadlock issues from swift apps, and will allow for important performance wins as well.  This happens by eliminating shared mutable data, locks, and the need for most atomic memory accesses.  The model is quite different from what traditional unix folks are used to though. :-)

As usual, Swift will eventually support unsafe constructs, so if it turns out that the model isn't strong enough for some particular use case, that coder can always fall back to the hunting for concurrency with arrows and bone knives instead of using the native language support.  Ideally this will never be required though.

This starts by describing the basic model, then talks about issues and some possible extensions to improve the model.
 
Language Concepts

The unit of concurrency in Swift is an Actor.  As an execution context, an actor corresponds to a (light weight) thread or dispatch queue that can respond to a declared list of asynch messages.  Each actor has its own private set of mutable data, and can communicate to other actors by sending them asynch messages.  Only one message can be active at a time (you can also extend the model to allow read/writer locks as a refinement) in an actor.  Through the type system, it is impossible for an actor to read or change another actor's mutable data: all mutable data is private to some actor.  When a message is sent from one actor to another, the argument is deep copied to ensure that two actors can never share mutable data.

To achieve this, there are three important kinds of memory object in Swift.  Given a type, it is obvious what kind it is from its definition. These kinds are:

1. Immutable Data - Immutable data (which can have a constructor, but whose value cannot be changed after it completes) is sharable across actors, and it would make sense to unique them where possible.  Immutable data can (transitively) point to other immutable data, but it isn't valid (and the compiler rejects) immutable data that is pointing to mutable data.  For example:

struct [immutable,byref] IList ( data : int, next : List) 
...
  var a = IList(1, IList(2, IList(3, nil)))
...
  a.data = 42   // error, can't mutate field of immutable 'IList' type!
  a.next = nil    // error, same deal
  a = IList(2, IList(3, nil)) // ok, "a" itself is mutable, it now points to something new.
  a = IList(1, a)                 // ok

Strings are a very important example of (byref) immutable data.

2. Normal mutable data - Data (structs, simple values like int, objects, etc) is mutable by default, and are thus local to the containing actor.  Mutable data can point to immutable data with no problem, and since it is local to an actor, no synchronization or atomic accesses are ever required for any mutable data.  For example:

struct [byref] MEntry (x : int, y : int, list : IList)
...
  var b = MEntry(4, 2, IList(1, nil))
  b.x = 4        // ok
  b.y = 71      // ok
  b.list = nil   // ok
  b.list = IList(2, nil) // ok
  b.list.data = 42      // error, can't change immutable data.

As part of mutable data, it is worth pointing out that mutable "global variables" in Swift are not truly global, they are local to the current actor (somewhat similar to "thread local storage", or perhaps to "an ivar on the actor").  Immutable global variables (like lookup tables) are simple immutable data just like today.  Global variables with "static constructors / initializers" in the C++ sense have their constructor lazily run on the first use of the variable.

Not having mutable shared state also prevents race conditions from turning into safety violations.  Here's the go issue I was referring to: http://research.swtch.com/2010/02/off-to-races.html

3. Actors - In addition to being the unit of concurrency, Actors have an address and are thus memory objects.  Actors themselves can have mutable fields, one actor can point to another, and mutable data can point to an actor.  However, any mutable data in an actor can only be directly accessed by that actor, it isn't possible for one actor to access another actor's mutable data.  Also note that actors and objects are completely unrelated: it is not possible for an object to inherit from an actor, and it is not possible for an actor to inherit from an object.  Either can contain the other as an ivar though.

Syntax for actors is TBD and not super important for now, but here is a silly multithreaded mandelbrot example, that does each pixel in "parallel", to illustrate some ideas:

func do_mandelbrot(x : float, y : float) -> int {
   // details elided. Not a great example that needs lots of local data :-)
}

actor MandelbrotCalculator {
  func compute(x : float, y : float, Driver D) {
    var num_iters = do_mandelbrot(x, y)
    D.collect_point(x, y, num_iters)
  } 
}

actor Driver {
  var result : image;      // result and numpoints are mutable per-actor data.
  var numpoints : int;
  func main() {
     result = new image()
     foreach i in -2.0 .. 2.0 by 0.001 {
        // Arbitrarily, create one MandelbrotCalculator for each row.
        var MC = new MandelbrotCalculator()
        foreach j in -2.0 .. 2.0 by 0.001 {
           MC.compute(i, j, self)
           ++numpoints;
        }
      }
  }

  func collect_point(x : float, y : float, num_iters : int) {
    result.setPoint(x, y, Color(num_iters, num_iters, num_iters))
    if (--numpoints == 0)
       draw(result)
  }
}

Though actors have mutable data (like 'result' and 'numpoints'), there is no need for any synchronization on that mutable data.

One of the great things about this model (in my opinion) is that it gives programmers a way to reason about granularity, and the data copy/sharing issue gives them something very concrete and understandable that they can use to make design decisions when building their app.  While it is a common pattern to have one class that corresponds to a thread in C++ and ObjC, this is an informal pattern -- baking this into the language with actors and giving a semantic difference between objects and actors makes the tradeoffs crisp and easy to understand and reason about.

Memory Ownership Model

Within an actor there is a question of how ownership is handled.  It's not in the scope of this document to say what the "one true model" is, but here are a couple of interesting observations:

1. Automated reference counting would be much more efficient in this model than in ObjC, because the compiler statically knows whether something is mutable data or is shared.  Mutable data (e.g. normal objects) can be ref counted with non-atomic reference counting, which is 20-30x faster than atomic adjustments.  Actors are shared, so they'd have to have atomic ref counts, but they should be much much less common than the normal objects in the program.  Immutable data is shared (and thus needs atomic reference counts) but there are optimizations that can be performed since the edges in the pointer graph can never change and cycles aren't possible in immutable data.

2. Garbage collection for mutable data becomes a lot more attractive than in ObjC for four reasons:  1) all GC is local to an actor, so you don't need to stop the world to do a collection.   2) actors have natural local quiescent points: when they have finished servicing a message, if their dispatch queue is empty, they go to sleep.  If nothing else in the CPU needs the thread, it would be a natural time to collect.  3) GC would be fully precise in Swift, unlike in ObjC, no conservative stack scanning or other hacks are needed.  4) If GC is used for mutable data, it would make sense to still use reference counting for actors themselves and especially for immutable data, meaning that you'd have *no* "whole process" GC.

3. Each actor can use a different memory management policy: it is completely fine for one actor to be GC and one actor to be ARC, and another to be manually malloc/freed (and thus unsafe) because actors can't reach each other's pointers.  However, realistically, we will still have to pick "the right" model, because different actors can share the same code (e.g. they can instantiate the same objects) and the compiled code has to implement the model the actor wants.

Issues with this Model

There are two significant issues with this model: 1) the amount of data copying may be excessive if you have lots of messages each passing lots of mutable data that is deep copied, and 2) the awkward nature of asynch programming for some (common) classes of programming.  For example, the "branch and rejoin" pattern in the example requires a counter to know when everyone rejoined.  Getters are a pain to implement, etc.

I'd advocate implementing the simple model first, but once it is there, there are several extensions that can help with these two problems:

No copy is needed for some important cases: If you can prove (through the type system) that an object graph has a single (unique) pointer to it, the pointer value can be sent in the message and nil'd out in the sender.  In this way you're "transferring" ownership of the subgraph from one actor to the other.  It's not fully clear how to do this though.  Another similar example: if we add some way for an actor to self destruct along with a message send, then it is safe for an actor to transfer any and all of its mutable state to another actor when it destroys itself, avoiding a copy.

Getters for trivial immutable actor fields: If an actor has an ivar with an immutable type, then we can make all stores to it atomic, and allow other actors to access the ivar.  Silly example:

actor Window {
  var title : string;  // string is an immutable by-ref type.
...
}

... 
  var x = new Window;
  print(x.title)   // ok, all stores will be atomic, an (recursively) immutable data is valid in all actors, so this is fine to load.
...

Actor messages with return values: Strict actor messages are one-way and thus always have to "return void".  However, it would be straightforward to allow them to return a value, and have the invoker specify a "block" to run with the return value.  This block would be executed on the sending actor after the result is computed (and copied back to the sender).  I don't have a good syntax for this off-hand, but something like this should get the idea across:

struct MyInfo (value : int, ...)

actor Whatever {
  func getWhatever() -> MyInfo { ... }
}

actor SomethingElse {
  var x : Whatever = ...
  var sum : int
 
  func SomeMessage() {
    ... abc ...
     [ var info <= x.getWhatever()
       sum += info.value
     ]
    ...  def ...
  }
}

We'd need special syntax for this (yes, I know the proposal above is awful) because the semantics are surprising: the implementation of this sends a message to the "whatever" actor with a "self" pointer and a block to run after the message is completed.  When "getWhatever" completes, it posts a message back to the "something else" actor and that message executes the "sum += line".  I think that something like this would be incredibly useful, but we need some way to indicate that the block will run *after the current 'somemessage' message completes* (thus, after "def"), and that *other messages on SomethingElse* may be serviced before the block is run.

Some blue sky kinds of random thoughts:

Distributed Programming - Since deep copy is part of the language and "deep copy" is so similar to "serialization", it would be easy to do a simple "Distributed Objects" implementation.  At first glance, it seems that the only new thing it would "require" is for message sends to be able to fail, which is highly related to exception handling.

Immutable Data w/Synch and Lazy Faulting - Not a fully baked idea, but if you're heavily using immutable data to avoid copies, a "distributed objects" implementation would suffer because it would have to deep copy all the immutable data that the receiver doesn't have, defeating the optimization.  One approach to handling this is to treat this as a data synch problem, and have the client fault pieces of the immutable data subgraph in on demand, instead of eagerly copying it.

OpenCL Integration with this model could be really natural: the GPU is an inherently asynch device to talk to.