Deductive Verification of Concurrent Programs with Why3



[ In these notes we refer to https://haslab.github.io/MFP/VF/2324/aula2 for reference. Recall that the focus of that class is the specification in TLA+, and verification with TLC, of concurrent algorithms. ]

Verification Method

Sequential algorithms entail deterministic transition relations and are for this reason easier to verify. In Why3 we do it without having to explicitly mention the transition relation (see first semester MFES course).

Concurrent algorithms are not deterministic. We model their execution by isolating atomic instructions, and allowing for interleaving between instructions of different processes/threads (we use the two words interchangeably). It is useful to consider configurations containing a program counter for each process; the transition relation will be generated by a number of actions that:
  • correspond to atomic instructions ;
  • are enabled by the program counter of the process that will execute the instruction ;
  • advance the program counter of that process



Example: Counter 


Consider the concurrent counter example. Each thread executes a single instruction (without looping), incrementing a shared counter. 

   var x : integer = 0

   procedure increment;
   begin
0:   x := x + 1 
1: end;

   increment || ... || increment


We will begin by using a simple (but unrealistic) model, which assumes atomic updates, i.e. that the instruction at label 0 is executed atomically, i.e. the value of the counter is read and written (incremented) in a single atomic step. 

Each thread executes a single transition, with the PC advancing from label 0 to label 1.

The correctness property of interest is that when all threads terminate, the value of the counter equals the number of threads.  We will be able to verify this property for an unbounded number of processes. 

module Counter_Atomic_Updates
  type state = Zero | One
  type thread = int
  val constant n_threads : int
  axiom n_threads_pos : n_threads > 0

  (* System configurations *)
  type world = { pc : map thread state ; x : int }

  (* Initial configurations *)
  predicate initWorld_p (w:world) = w.pc = const Zero /\ w.x=0 

  (* System action: guard predicate and action function *)