2026-07-08Β·10 min readΒ·sota.io team

Deploy Chalice to Europe β€” K. Rustan M. Leino πŸ‡ΊπŸ‡Έ + Peter MΓΌller πŸ‡¨πŸ‡­ + ETH Zurich (2009), the Object-Oriented Research Language that First Implemented Implicit Dynamic Frames and Magic Wands, the Foundations of Viper, on EU Infrastructure in 2026

Before Viper had Silver. Before Prusti had #[requires]. Before Gobra had //@ acc(). There was a small research language at the intersection of Microsoft Research Redmond and ETH Zurich that first put all the pieces together.

Chalice is an object-oriented research programming language developed by K. Rustan M. Leino πŸ‡ΊπŸ‡Έ (Microsoft Research Redmond) and Peter MΓΌller πŸ‡¨πŸ‡­ (ETH Zurich), first presented publicly around 2009 at workshops on verification and then described in detail in the VSTTE 2010 paper "Chalice: A Language for Modular Programs with Shared Mutable State." Chalice was not designed to be a production language. It was designed to answer a research question: can a single, coherent permission model handle both sequential and concurrent heap reasoning, in a language that looks like a real object-oriented program?

The answer was yes. And the permission model Chalice invented became the theoretical core of an entire European verification ecosystem.

Why Chalice Matters: Two Inventions That Changed Verification

Chalice introduced two ideas that are now foundational to the Viper cluster β€” used in every Viper front-end but never explained directly in the front-end documentation.

1. Implicit Dynamic Frames (IDF)

Classical separation logic requires explicit heap footprints. Every method specifies precisely which memory cells it reads and writes β€” this.balance ↦ v β€” using syntactic heap constructs. This is sound and precise, but verbose and difficult to connect to object-oriented programs where the heap structure emerges at runtime.

Implicit Dynamic Frames, introduced by Smans, Jacobs, and Piessens (KU Leuven πŸ‡§πŸ‡ͺ, 2009) and independently operationalised in Chalice, replaces explicit heap terms with access permissions. Instead of writing "this method owns the heap cell for x.balance," you write:

method Deposit(amount: int)
  requires acc(this.balance) && amount >= 0
  ensures  acc(this.balance) && this.balance == old(this.balance) + amount
{
  this.balance := this.balance + amount
}

acc(this.balance) is not a heap term β€” it is a permission to read and write this.balance. The heap content is implicit behind the permission. If you hold acc(x.f), you may read and write x.f. If two permissions are in separating conjunction, the two heap regions are disjoint by construction.

The "dynamic" part: permissions can be transferred at runtime. You pass acc(x.f) to a called method; the method consumes it for its duration; you receive it back via ensures. The footprint evolves dynamically as your execution path changes β€” unlike static alias analysis or region-based type systems.

This is precisely the acc(x.f, p) you see in Viper, Prusti, Gobra, and Nagini. Every //@ requires acc(x.field) annotation in a Gobra Go program, every Acc(self.balance) call in a Nagini Python spec, every #[requires(acc(x.f))] in a Prusti Rust program traces its lineage to the IDF formalism Chalice first implemented.

2. Magic Wands

The second Chalice innovation was the magic wand β€” the separating implication operator A --* B.

A --* B means: if you provide me with a heap satisfying A, I will give you a heap satisfying B. This is not ordinary implication. The A and the current heap are combined in a separating-conjunctive sense: the heap the wand holds, plus the incoming A, together produce B. Wands let you reason about partial data structures β€” iterators, cursors, tree zippers, "holes" in a linked list.

The canonical use case: you are iterating over a linked list to sum its elements. At each step, you hold:

Without magic wands, you cannot verify iterators over recursive data structures without inlining the entire recursive predicate at each loop step. With wands, the hole in the data structure is a first-class resource.

Chalice was the first programming language to implement magic wands β€” not just in theory, but as a working verifier that could check programs using A --* B against a Boogie backend. The idea was later refined and added to Viper's Silver IL (A --* B appears in Viper specifications as acc(x.f) --* list(x)), where it is used by VerCors for concurrent data structure reasoning.

Chalice Language Semantics

Classes and Fields

Chalice is object-oriented with explicit field declarations:

class Account {
  var balance: int
  var owner: string

  invariant acc(balance) && acc(owner) && balance >= 0
}

The invariant clause is a monitor invariant β€” it holds whenever no thread holds the monitor lock. When a thread acquires the lock, it gains the invariant's permissions and obligations. When it releases, it must re-establish the invariant. This is the Chalice encoding of the lock-coupling concurrency idiom.

Fractional Permissions

Chalice generalises from binary (exclusive/none) ownership to fractional permissions:

method ReadBalance() returns (result: int)
  requires rd(this.balance)       // read permission β€” fractional, shareable
  ensures  rd(this.balance)
  ensures  result == this.balance
{
  result := this.balance
}

method WriteBalance(newVal: int)
  requires acc(this.balance)      // full permission β€” exclusive, non-shareable
  ensures  acc(this.balance)
  ensures  this.balance == newVal
{
  this.balance := newVal
}

rd(x.f) denotes a read permission β€” a fractional slice (0, 1) of the permission to x.f. Multiple threads can simultaneously hold rd(x.f), enabling safe concurrent reads. Only one thread can hold acc(x.f) (the full permission 1), enabling safe exclusive writes. This fractional permission discipline eliminates data races by proof, without locks, for read-dominant patterns.

The rd/acc split maps directly to what later became acc(x.f, 1/2) (read) and acc(x.f, 1) (write) in Viper β€” same semantics, cleaner syntax.

Predicates

Chalice predicates name heap regions, enabling abstraction over complex data structures:

predicate List(node: Node) {
  node != null ==>
    acc(node.val) && acc(node.next) && List(node.next)
}

method SumList(head: Node) returns (sum: int)
  requires List(head)
  ensures  List(head)
{
  unfold List(head)
  if (head == null) {
    sum := 0
  } else {
    var rest: int
    rest := SumList(head.next)
    sum := head.val + rest
  }
  fold List(head)
}

fold packs the unfolded heap region back into the abstract predicate. unfold does the inverse. This is exactly the fold/unfold mechanism in Viper's Silver IL β€” Chalice established the pattern, Viper productized it.

Channels and Permission Transfer

Chalice models Go-style message passing with permission annotations:

channel DataChannel(data: int)
  where acc(sender.buffer)

method Producer(ch: DataChannel, buf: int)
  requires acc(this.buffer)
{
  this.buffer := buf
  send ch(42) between acc(this.buffer) and emp
  // after send: no longer hold acc(this.buffer)
}

method Consumer(ch: DataChannel)
  ensures acc(receiver.buffer)
{
  var v: int
  receive v from ch
  // after receive: hold acc(receiver.buffer)
}

The between ... and ... clause on send transfers a heap permission from sender to receiver across the channel. This is the formal mechanism for safe message-passing concurrency: the sender gives up ownership of the data when it sends; the receiver acquires ownership when it receives. No data race is possible because ownership is exclusive.

Gobra's goroutine/channel permission transfer (also developed at ETH Zurich) implements the same principle using the Viper IDF model β€” ch.send(data) with //@ requires acc(data) in Gobra is the Chalice channel pattern translated to Go.

Fork, Join, and Thread Annotations

method ParallelSum(left: Node, right: Node) returns (sum: int)
  requires List(left) && List(right)
  ensures  List(left) && List(right)
{
  var t: thread
  var leftSum: int
  var rightSum: int

  fork t := SumList(left)           // transfers List(left) to new thread
  rightSum := SumList(right)        // main thread handles right
  join leftSum := t                 // reclaims List(left) from thread
  
  sum := leftSum + rightSum
}

fork t := method(args) creates a thread and transfers the permissions in method's precondition to the new thread. join leftSum := t blocks until the thread completes and reclaims the postcondition's permissions. This is exactly the share/unshare + fork/join pattern in VerCors for concurrent Java β€” same semantics, different syntax.

Magic Wand Example: Iterator

method FindFirst(head: Node, target: int) returns (found: bool)
  requires List(head)
  ensures  List(head)
{
  var curr: Node := head
  var wand: acc(List(curr)) --* List(head)  // wand: "give me List(curr), get back List(head)"
  
  while (curr != null)
    invariant List(curr) && wand
  {
    unfold List(curr)
    if (curr.val == target) {
      fold List(curr)
      apply wand                    // apply wand: consume List(curr), produce List(head)
      found := true
      return
    }
    var next: Node := curr.next
    // extend wand to cover curr's node
    wand := (acc(List(next)) --* List(head)) with {
      fold List(curr)
      apply wand
    }
    curr := next
  }
  apply wand
  found := false
}

The wand acc(List(curr)) --* List(head) captures the "hole" between the iterator's current position and the list head. At each step, the wand is extended to cover one more node. When the iterator is done, applying the wand reassembles the full list. No recursive unrolling is required β€” the wand is the recursive context, carried as a resource.

The ETH Zurich Lineage: From Chalice to Viper

The genealogy of the Viper ecosystem runs through Chalice:

Chalice (2009)
  β”‚  K. Rustan M. Leino + Peter MΓΌller (ETH Zurich πŸ‡¨πŸ‡­)
  β”‚  First IDF implementation. First magic wands. Boogie backend.
  β”‚  "Can one permission model handle both concurrent and sequential OO?"
  β–Ό
Viper / Silver IL (2016)
  β”‚  Peter MΓΌller + Malte Schwerhoff + Alexander Summers (ETH Zurich πŸ‡¨πŸ‡­)
  β”‚  Cleaned-up IDF. Productized infrastructure. Two backends: Silicon + Carbon.
  β”‚  "Can one IVL serve multiple source languages simultaneously?"
  β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Prusti     β”‚  Gobra      β”‚  Nagini     β”‚  VeriFast   β”‚  VerCors    β”‚
β”‚  Rust πŸ‡¨πŸ‡­   β”‚  Go πŸ‡¨πŸ‡­     β”‚  Python πŸ‡¨πŸ‡­  β”‚  C/Java πŸ‡§πŸ‡ͺ  β”‚  Java/C/GPU πŸ‡³πŸ‡±β”‚
β”‚  (2016)     β”‚  (2021)     β”‚  (2018)     β”‚  (2008)     β”‚  (2017)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Every acc(x.f, p) in Prusti, every //@ acc(x.field) in Gobra, every Acc(self.balance) in Nagini is the IDF permission model that first lived in Chalice. The fold/unfold predicates. The fractional rd/acc split. The channel permission transfer. The magic wand --*. All of it: Chalice first.

Peter MΓΌller is the connecting figure: Chalice co-author (Microsoft Research / ETH Zurich, 2009) β†’ Viper co-creator (ETH Zurich, 2016) β†’ Prusti co-creator (ETH Zurich, 2016) β†’ Gobra co-creator (ETH Zurich, 2021) β†’ Nagini co-author (ETH Zurich, 2018). One researcher, fifteen years, a complete EU verification ecosystem.

Backend: Boogie and the Shared Verification Infrastructure

Chalice uses Boogie as its verification backend β€” the same intermediate verification language that powers Viper's Carbon backend, Dafny, and SPARK Ada's GNATprove. Chalice β†’ Boogie translation:

  1. Permission tracking: Chalice permission assertions are translated to Boogie state variables tracking which thread holds which permission amount for which heap location.
  2. Heap encoding: Object fields become uninterpreted Boogie functions: Heap: ref Γ— Field β†’ Value, updated via Boogie map comprehensions.
  3. Monitor invariants: Lock acquisition/release becomes Boogie assume/assert pairs for the invariant.
  4. Magic wands: Encoded as existential witnesses over Boogie states β€” the wand "holds" a snapshot of the heap that will be provided to the consumer.

Boogie discharges the resulting verification conditions via Z3 β€” the same SMT solver used by Viper Silicon's carbon backend, Dafny, and all other tools in this lineage. The Chalice β†’ Boogie β†’ Z3 pipeline was the proof-of-concept that the Viper β†’ Silicon/Carbon β†’ Z3 productization later scaled to multiple front-end languages.

EU Regulatory Alignment

EU AI Act Art. 9 (High-Risk System Technical Documentation): Chalice's fractional permission model β€” where read-sharing (rd) is statically bounded and exclusive write (acc) is non-shareable β€” provides a formal language for documenting concurrent AI inference system correctness. A Chalice-verified inference server proves that parallel read access to model weights is safe (multiple rd(model.weights) permissions held simultaneously) while parameter updates require exclusive ownership (acc(model.weights)) β€” precisely the concurrent access pattern EU AI Act Art. 9 requires evidence for in high-risk Annex III systems.

EU AI Act Art. 10 (Data Governance for High-Risk Systems): Chalice channel semantics β€” between acc(data) and emp on send, receive transfers ownership β€” formally model data pipeline ownership. A Chalice specification of an ETL pipeline proves that personal data transferred across processing stages is owned by exactly one stage at a time: no aliasing, no unintended sharing across processing boundaries.

GDPR Art. 25 (Data Protection by Design): Chalice monitor invariants can encode data minimisation constraints as heap ownership invariants: invariant acc(this.personalData) && this.personalData.fields.length <= allowedFields. Any thread accessing personalData must hold the monitor lock, which enforces the invariant β€” structural GDPR Art. 25 compliance by construction.

Cyber Resilience Act (CRA, 2027): Chalice's race-freedom by permission proof (no two threads hold conflicting permissions over the same heap cell) provides CRA Article 13 evidence for concurrent software components in EU digital products.

IEC 62304 Class C (Medical Device Software): Concurrent Java pipelines in medical imaging systems (MRI reconstruction, CT image processing) involve producer-consumer patterns that map directly to Chalice's fork/join + channel permission model β€” formal proofs of freedom from data corruption in concurrent medical device software.

Deploy Chalice on EU Infrastructure with sota.io

Chalice runs as a research tool via its GitHub distribution. The Chalice verifier is available as a JAR with a Boogie + Z3 dependency:

# Install prerequisites: Java 11+, Boogie, Z3
sudo apt-get install default-jdk z3

# Install Boogie (via dotnet)
dotnet tool install --global boogie

# Clone Chalice
git clone https://github.com/viperproject/chalice
cd chalice

# Verify a Chalice program
java -jar chalice.jar BankAccount.chalice

# With explicit Boogie path
java -jar chalice.jar --boogie /usr/local/bin/boogie Account.chalice

A simple Chalice program demonstrating the permission model:

class BankAccount {
  var balance: int

  method Init()
    requires rd(balance)
    ensures  acc(balance) && balance == 0
  {
    balance := 0
  }

  method Deposit(amount: int)
    requires acc(balance) && amount >= 0
    ensures  acc(balance) && balance == old(balance) + amount
  {
    balance := balance + amount
  }

  method ReadBalance() returns (b: int)
    requires rd(balance)
    ensures  rd(balance) && b == balance
  {
    b := balance
  }
}

Deploy a Chalice-integrated verification CI pipeline on sota.io β€” the EU-native PaaS on German infrastructure, GDPR-compliant by default:

# sota.io deploy
sota deploy --region eu-central

sota.io provides:

Free tier available. No credit card required to start.

See Also