Lambda

Input language

Lambda reads a profile of VMT-LIB: standard SMT-LIB 2 with annotations that mark the parts of a transition system, extended for parameterized systems. The main extension is that state variables can be functions from uninterpreted index sorts into a codomain, and formulas can quantify over the index sorts. Lambda checks that a safety property holds for every cardinality of the index sorts.

The model

A system consists of:

index sorts uninterpreted sorts, (declare-sort Process 0); the parameters of the system
state variables functions from index sorts into a codomain, each paired with a next-state copy
rigid symbols constants, functions and relations that never change (topologies, quorums, orders)
inputs constants that take a fresh value at every step
initial formula the initial states
transition formula T = (⋁ actions) ∧ (⋀ trans constraints)
axioms background assumptions that hold in every state
property the safety property to prove

Sorts

Quantifiers range over the index sorts only, never over integers or reals; any nesting and alternation of ∀ and ∃ is allowed. The codomain of a state variable or rigid function is one of:

Index sorts and enumerations support equality only. Bit-vectors, strings, the SMT array theory and non-enumerative datatypes are not supported; use functions from index sorts instead of arrays.

Symbols

State variables are declared as a pair of symbols, the current and the next copy, tied by annotating the current one with :next. A state variable of arity 0 is a scalar:

(declare-fun Ticket (Process) Int)
(declare-fun Ticket.next (Process) Int)
(define-fun Ticket.sv ((p Process)) Int (! (Ticket p) :next Ticket.next))

Rigid symbols of any arity are marked with :global (or :rigid true):

(declare-fun member (node quorum) Bool)
(define-fun member.sv ((n node) (q quorum)) Bool (! (member n q) :global true))

A symbol without :next and without :global is rigid if its arity is at least 1, and an input if it is a constant: it then takes an unconstrained value at every step, for example the time elapsed by a transition. A constant that must keep its value has to be marked :global.

Formulas

Annotation Meaning
:init true initial states (no next-state symbols); several are conjoined
:action NAME a transition rule; the actions are the disjuncts of the transition formula
:trans true a two-state constraint that holds on every step; several are conjoined
:axiom or :invar true a background assumption that holds in every state
:invar-property N the safety property; with several, choose one with --vmt-property N

At least one :action or :trans is required. Frames are explicit: each action must state the next value of every state variable, including the ones it does not change, for example with (ite (= p q) new old). Lambda never adds frame conditions.

Example

The bakery protocol, from benchs/collection/misc/paper/vmt/bakery.vmt: processes take increasing tickets, and the process whose ticket is served may enter the critical section.

(declare-sort Process 0)                     ; unboundedly many processes
(declare-datatypes ((Location 0))            ; control location of a process
  (((Idle) (Waiting) (Critical))))
(declare-fun State (Process) Location)       ; state variables: one per process
(declare-fun State.next (Process) Location)
(declare-fun Ticket (Process) Int)
(declare-fun Ticket.next (Process) Int)
(declare-const NextTicket Int)               ; scalars: 0-ary state variables
(declare-const NextTicket.next Int)
(declare-const Serving Int)
(declare-const Serving.next Int)
(define-fun State.sv ((p Process)) Location  ; :next pairs a symbol with its
  (! (State p) :next State.next))            ; next-state copy: this is what
(define-fun Ticket.sv ((p Process)) Int      ; makes it a state variable
  (! (Ticket p) :next Ticket.next))
(define-fun NextTicket.sv () Int (! NextTicket :next NextTicket.next))
(define-fun Serving.sv () Int (! Serving :next Serving.next))
(define-fun init () Bool                     ; both counters start at one and
  (! (and (= NextTicket 1) (= Serving 1)     ; every process is idle, holding
          (forall ((p Process))              ; no ticket
            (and (= (State p) Idle) (= (Ticket p) 0)))) :init true))
(define-fun request () Bool                  ; an idle process takes a ticket
  (! (exists ((p Process))                   ; p is the process that moves
       (and (= (State p) Idle)
            (= NextTicket.next (+ NextTicket 1))
            (= Serving.next Serving)
            (forall ((q Process))            ; frame condition: every other
              (and (= (State.next q)         ; process q keeps its location
                      (ite (= p q) Waiting (State q)))
                   (= (Ticket.next q)        ; and its ticket
                      (ite (= p q) NextTicket (Ticket q)))))))
     :action true))                          ; one disjunct of T
(define-fun enter () Bool                    ; a waiting process whose ticket
  (! (exists ((p Process))                   ; is served becomes critical
       (and (= (State p) Waiting) (= (Ticket p) Serving)
            (= NextTicket.next NextTicket) (= Serving.next Serving)
            (forall ((q Process))
              (and (= (State.next q)
                      (ite (= p q) Critical (State q)))
                   (= (Ticket.next q) (Ticket q)))))) :action true))
(define-fun leave () Bool                    ; a critical process goes idle and
  (! (exists ((p Process))                   ; hands the turn to the next one
       (and (= (State p) Critical)
            (= NextTicket.next NextTicket)
            (= Serving.next (+ Serving 1))
            (forall ((q Process))
              (and (= (State.next q)
                      (ite (= p q) Idle (State q)))
                   (= (Ticket.next q)
                      (ite (= p q) 0 (Ticket q))))))) :action true))
(define-fun safety () Bool                   ; mutual exclusion: no two
  (! (forall ((p Process) (q Process))       ; distinct processes are
       (=> (and (= (State p) Critical)       ; critical at the same time
                (= (State q) Critical))
           (= p q))) :invar-property 0))
python3 -m lambda_pmc --show-witness -l vmt benchs/collection/misc/paper/vmt/bakery.vmt

Other formats

MCMT files are read natively (-l mcmt). mypyvy and IC3PO models are translated to this profile (-l mypyvy, -l ic3po), and Ivy models through Ivy's convert_to_mypyvy. All translators emit explicit frames, and the converters in lambda_pmc/translators/ write the VMT file, which can then be inspected or edited.