Authors: Kwing Hei Li, Alejandro Aguirre, Simon Oddershede Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, Lars Birkedal
Paper Content:
Page 1:
Modular Reasoning about Error Bounds for Concurrent
Probabilistic Programs
KWING HEI LI, Aarhus University, Denmark
ALEJANDRO AGUIRRE, Aarhus University, Denmark
SIMON ODDERSHEDE GREGERSEN, New York University, USA
PHILIPP G. HASELWARTER, Aarhus University, Denmark
JOSEPH TASSAROTTI, New York University, USA
LARS BIRKEDAL, Aarhus University, Denmark
We present Coneris, the first higher-order concurrent separation logic for reasoning about error probability
bounds of higher-order concurrent probabilistic programs with higher-order state. To support modular
reasoning about concurrent (non-probabilistic) program modules, state-of-the-art program logics internalize
the classic notion of linearizability within the logic through the concept of logical atomicity .
Coneris extends this idea to probabilistic concurrent program modules. Thus Coneris supports modular
reasoning about probabilistic concurrent modules by capturing a novel notion of randomized logical atomicity
within the logic. To do so, Coneris utilizes presampling tapes and a novel probabilistic update modality to
describe how state is changed probabilistically at linearization points. We demonstrate this approach by means
of smaller synthetic examples and larger case studies.
All of the presented results, including the meta-theory, have been mechanized in the Rocq proof assistant
and the Iris separation logic framework.
1 Introduction
Probabilistic data structures, such as approximate counters, skip lists, or Bloom filters are widely
used in concurrent programming. These data structures can improve time and space efficiency
compared to their deterministic counterparts. However, some probabilistic data structures may
return wrong results with a small probability. Analyzing and ensuring this probability of error is
sufficiently small is essential for using these data structures. But this analysis is challenging because
probabilistic programs often have unintuitive behaviors, which are only made more complicated
when probabilistic behaviors are combined with concurrency. Because randomness and concurrency
both introduce non-determinism, any analysis must take into account the large range of possible
outcomes that can arise from this non-determinism.
For just concurrency alone without randomness, a number of verification techniques have
been developed that abstract away from concurrent non-determinism. For example, Concurrent
Separation Logic (CSL) [OโHearn 2007] allows for threads in a concurrent program to be verified in
alocal way, without having to consider the effects of interference from other threads at every step
of execution. A key feature of modern concurrent separation logics is support for proving that data
structures are logically atomic [da Rocha Pinto et al .2014; Jacobs and Piessens 2011; Jung et al .2015;
Svendsen et al .2013]. Logical atomicity allows clients to reason as if a concurrent data structure
had a logical state that is updated atomically at a single point in time during each operation.
This internalizes the idea of linearizability , a standard notion of correctness for concurrent data
structures, and enables modular and compositional proofs.
It is natural to consider whether similar techniques can be applied to reason about randomized
concurrent data structures. Prior work has explored extending concurrent separation logic to
Authorsโ Contact Information: Kwing Hei Li, Aarhus University, Denmark, hei.li@cs.au.dk; Alejandro Aguirre, Aarhus
University, Denmark, alejandro@cs.au.dk; Simon Oddershede Gregersen, New York University, USA, s.gregersen@nyu.edu;
Philipp G. Haselwarter, Aarhus University, Denmark, pgh@cs.au.dk; Joseph Tassarotti, New York University, USA, jt4767@
cs.nyu.edu; Lars Birkedal, Aarhus University, Denmark, birkedal@cs.au.dk.arXiv:2503.04512v1 [cs.LO] 6 Mar 2025
Page 2:
2 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
the randomized setting [Fesefeldt et al .2022; Tassarotti and Harper 2019; Zilberstein et al .2024].
However, none of these prior logics support compositional reasoning about data structures because
they lack support for reasoning about logical atomicity. Indeed, even developing a suitable notion
of what logical atomicity would look like in the presence of randomization is challenging. Whereas
prior efforts developing non-randomized logical atomicitiy could draw inspiration and intuition
from the notion of linearizability, there is no widely-accepted analogue of linearizability that is
suitable for randomized data structures. Indeed, prior work has shown ways in which standard
notions of linearizability do not suffice when clients of a data structure can make randomized
choices [Golab et al. 2011].
In this paper, we develop an appropriate notion of randomized logical atomicity in the context
of Coneris, a new concurrent separation logic. Coneris is a probabilistic higher-order concurrent
separation logic for reasoning about error bounds of ConcRandML programs, a ML-like language
with support for discrete random sampling, unstructured concurrency, higher-order functions, and
higher-order dynamically-allocated local state.
On the surface level, Coneris retains all the standard rules of higher-order separation logic for
concurrent programs, along with modern features like expressive impredicative invariants and
custom ghost resources. From the probabilistic side, Coneris inherits two kinds of separation logic
resources from previous logics for reasoning about probabilities, specifically presampling tapes first
introduced in Clutch [Gregersen et al .2024], and error credits first introduced in Eris [Aguirre et al .
2024]. The former are used to reason about random choices that a program will take in the future,
while the latter are used to track an upper bound on the probability of some error occurring during
execution.
Using its higher-order features, Coneris encodes randomized logical atomicity by adapting the
earlier HOCAP [Svendsen et al .2013] approach to logical atomicity. To do so, we introduce a
novel probabilistic update modality , written as|โโ๐. Intuitively, the modality is used to describe a
probabilistic logical update to a piece of ghost state of the program. In particular, we can use it to
reason as if the randomness that the program uses is atomically drawn at one single point in time,
which is crucial in writing and proving modular specifications.
We demonstrate the flexibility of our approach by verifying a selection of data structures. For
example, we verify the correctness of a concurrent hash function. The hash module is subsequently
used in an efficient concurrent implementation of a Bloom filter, and we derive strict error bound
for a client program that uses the Bloom filter. These examples utilize rich language features such
as higher-order functions and local state, and display non-trivial interactions between concurrency
and probability. As far as we are aware, even verifying the simpler examples are out-of-scope for
previous techniques, let alone reasoning about them modularly .
Contributions. In summary, we make the following contributions:
โขThe first concurrent and probabilistic higher-order separation logic for reasoning about
error bounds of programs written in ConcRandML , a probabilistic concurrent higher-order
programming language with higher-order references.
โขA novel probabilistic update modality that is used to capture a probabilistic notion of logical
atomicity.
โขAn extension to the probabilistic setting of the HOCAP approach, that allows us to write
and prove modular specifications of randomized concurrent data structures.
โขA selection of case studies showcasing our approach to modular verification of higher-order
concurrent probabilistic data structures.
Page 3:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 3
โขFull mechanization of all results in the Rocq proof assistant [Bertot and Castรฉran 2013],
using the Iris separation logic framework [Jung et al .2018] and the Coquelicot real analysis
library [Boldo et al. 2013].
Outline. In ยง2, we demonstrate how Coneris is used to reason about programs that feature
both concurrency and probability, and highlight some of the key challenges that arise from this
combination. We then present the syntax and semantics of our language ConcRandML in ยง3. In ยง4,
we present a collection of program logic rules and we show how to apply them to reason about a
concurrent randomized counter module in ยง5. Subsequently in ยง6, we showcase Coneris on a range
of concurrent probabilistic data structure examples. Finally, we discuss related work and conclude
with ideas for future work in ยง8 and ยง9, respectively.
2 Motivation and Technical Challenges
We first recall the features of the Eris logic [Aguirre et al .2024] for reasoning about error bounds
of sequential programs, then we discuss how Coneris extends these ideas to the concurrent setting,
illustrating some of the challenges that arise when reasoning about error bounds in the presence of
concurrency.
Sequential Error Reasoning with Error Credits. Eris is a separation logic that introduces a
new assertion called error credits written E(๐), where 0โค๐โค1is a real number. This assertion
represents a budget upper bounding the probability that a specification can fail to hold. In particular,
an Eris Hoare triple of the form {๐โE(๐)}๐{๐ฅ.๐}implies that if we execute ๐in a state satisfying
๐, then the probability that ๐crashes or returns a value ๐ฅthat violates ๐is at most๐.
To illustrate how the E(๐)assertion is used, consider the following program as a simple example:
twoAddโlet๐=ref0in๐โ(!๐+rand 3);๐โ(!๐+rand 3); !๐
Here rand๐is a probabilistic construct that samples a value uniformly from {0,...,๐}. In particular,
rand 3returns a random number from the set {0,..., 3}with probability 1/4each. The twoAdd
program first allocates a reference ๐initialized to 0, then adds the result of a probabilistic sampling
rand 3to the value in ๐twice, and concludes by reading the number in ๐.
Suppose we want to prove an upper bound on the probability that the program returns 0. This
happens only if both calls to rand 3return 0, which occurs with probability at most 1/4ยท1/4=1/16.
We can capture this as a specification in Eris by proving {E(1/16)}twoAdd{๐ฅ.๐ฅ>0}.
Eris provides 3 key rules for working with error credits:1
err-split
E(๐1+๐2)
E(๐1)โE(๐2)= = = = = = = = = = = = = = = = = = = = =ht-rand-exp
E๐๐[F]โค๐
{E(๐)}rand๐{๐.E(F(๐))โ๐โ{0..๐}}err-1
E(1)
False
The first rule says that error credits can be split and joined together. The second rule says that
when the program makes a randomized choice, we can re-distribute error credits along different
branches of the randomized outcome, so long as the expected value oraverage amount of error
credit does not increase. Finally, the third rule says that an error credit of 1implies False ,i.e., we
can deduce anything, which intuitively follows from the idea that an upper bound of probability 1
is trivial. Figure 1 shows a proof outline using these rules to derive the Hoare triple stated above
fortwoAdd . The key steps in this proof are to apply the ht-rand-exp rule twice to reason about
the two rand 3statements in the proof. The first time the rule is applied, we start with E(1/16),
and instantiateFin the rule to be the function F(๐)โ1
4ยท[๐=0]where[๐]evaluates to 1if๐(๐)
is true and to 0otherwise. That is, we end up with E(1/4)in the case where rand 3returned 0, and
1We write inference rules with a double horizontal line to mean the rule can be applied in either direction.
Page 4:
4 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
{E 1
16}
let๐=ref0in
{๐โฆโ0โE 1
16}
๐โ(!๐+rand 3); (apply ht-rand-exp usingF(๐ฅ)โ1
4ยท[๐ฅ=0])
{โ๐.๐โฆโ๐โ (๐=0โงE 1
4)โจ๐โ 0}
๐โ(!๐+rand 3); (apply ht-rand-exp usingFโฒ(๐ฅ)โ[๐=0โง๐ฅ=0])
{โ๐.๐โฆโ๐โ((๐=0โงE(1))โจ๐โ 0)}
{โ๐.๐โฆโ๐โ๐โ 0} (discharge case ๐=0using err-1 from E(1))
!๐
{๐ฅ.๐ฅ>0}
Fig. 1. Proof outline for the Hoare triple {E(1/16)}twoAdd{๐ฅ.๐ฅ>0}.
E(0)otherwise. For the latter cases, the remainder of the proof is trivial, since the value in ๐is
already greater than 0. For the former case, on the next call to rand 3, we again apply ht-rand-exp ,
settingFโฒ(๐)โ[๐=0โง๐=0]. Thus, when rand 3again returns 0, we end up with E(1). In this
case,๐still contains 0, but we can use err-1 to finish the proof. For the other cases, ๐will be greater
than 0, so the postcondition follows directly.
Concurrent Error Bounds in Coneris. Coneris generalizes Erisโs error credit reasoning to
the concurrent setting. To illustrate some of the key ideas and challenges in doing so, consider the
following concurrent variation of the twoAdd example:
conTwoAddโlet๐=ref0in(faa๐(rand 3)|||faa๐(rand 3)); !๐
Here|||represents parallel composition of two threads, and faa๐๐is a fetch-and-add command
that atomically adds ๐to the value stored in ๐and returns the value prior to the addition. Intuitively,
no matter which order the faacommands execute in the two threads, the probability that the final
!๐at the end of the program returns 0is again bounded above by 1/16.
Coneris allows us to show this by proving a Hoare triple of a similar form as the one we saw
fortwoAdd . More precisely in, Coneris, the intuitive meaning of a Hoare triple {๐โE(๐)}๐{๐}is
that, โ for all possible schedulings of thread steps , if๐holds, then the probability that ๐reaches an
error state or returns a value that violates ๐is at most๐โ.
All of Erisโs proof rules for error credits also hold in Coneris. To reason about parallel execution,
Coneris additionally has the familiar parallel composition rule from concurrent separation logic:
{๐1}๐1{๐ฃ1.๐1๐ฃ1} {๐2}๐2{๐ฃ2.๐2๐ฃ2}
{๐1โ๐2}๐1|||๐2{(๐ฃ1,๐ฃ2).๐1๐ฃ1โ๐2๐ฃ2}ht-par-comp
To apply this rule we have to divide up the precondition into two separate parts, ๐1and๐2, and
show that they suffice as preconditions for the two threads ๐1and๐2, respectively. We already saw
with err-split that we can split error credits, so for the conTwoAdd example we might try to split
the initial error budget of E(1/16)into two, giving each thread E(1/32). However, we would soon
run into two issues:
(1)As in the sequential case, we want to apply ht-rand-exp to try to distribute all of the credits
to the cases where the rand 3commands return 0. However, if each thread has E(1/32)and
applies ht-rand-exp to reason about the rand 3it executes, then after applying the rule, it
Page 5:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 5
can have at most E(1/8)for the case where the rand 3returns 0. Combining two E(1/8)in
the post-condition of the parallel composition rule, we would end up with a total of E(2/8)
for the case where both threads add 0to the counter. But this is not enough to apply err-1 ,
and so we would not be able to prove the intended postcondition.
(2)Both threads need to modify the shared location ๐, so they both need to have โownershipโ of
the points-to assertion for ๐. However,๐โฆโ0โฌ๐โฆโ0โ๐โฆโ0, so we cannot pass ownership
of this assertion in the precondition of both threads when applying ht-par-comp .
The second problem has a well-known solution in CSL, namely invariant assertions . Fortunately, as
we will see, it turns out that invariants also provide a solution to the first problem.
Coneris provides Iris-style invariant assertions of the form ๐ผ, which states that an assertion ๐ผis
an invariant of program execution. These assertions support the following rules:
ht-inv-alloc
๐ผโ๐
๐{๐}
{๐ผโ๐}๐{๐}inv-dup
๐ผ
๐ผโ๐ผht-inv-open
๐atomic{๐ผโ๐}๐{๐ผโ๐}
๐ผโ๐
๐
๐ผโ๐
The first rule ht-inv-alloc says that we can allocate an invariant assertion ๐ผif we know that ๐ผ
holds in the precondition. Invariant assertions are duplicable , meaning that we can produce multiple
copies using inv-dup , so that when applying ht-par-comp , each thread can have a copy of ๐ผin its
precondition. Finally, threads can access the assertion ๐ผinside of the invariant using ht-inv-open ,
which requires that the invariant is restablished after the atomic expression ๐finishes executing.
An expression ๐isatomic if it steps to a value in a single execution step.
By putting the ๐โฆโโ assertion inside of an invariant ๐ผ, we can thus allow both threads to access
๐by using ht-inv-open during the FAA step. A standard technique in the CSL literature is to use
ghost state to encode a kind of protocol in the invariant assertion that tracks how ๐can evolve
through the shared access by the two threads [Jung et al .2015]. We omit the exact details of how
this ghost state encoding works, but at a high level, this invariant assertion would have a format
like:
๐ผโ(๐โฆโ0โ...)| {z }
no thread has addedโจ(โ๐ฃ.๐โฆโ๐ฃโ...)| {z }
one thread has addedโจ(โ๐ฃ.๐โฆโ๐ฃโ...)| {z }
both threads have added
where threads use ghost state to track which case of this disjunction they are in.
Our key observation is that if we now also include error credits in the invariant, then we can
additionally resolve the first issue mentioned above related to not having a sufficient number of
error credits. For example, by setting the invariant to a form like
๐ผโ (๐โฆโ0โ...)| {z }
no thread has addedโจ(โ๐ฃ.๐โฆโ๐ฃโ...)| {z }
one thread has addedโจ โ๐ฃ.๐โฆโ๐ฃโ๐ฃ>0โ...
| {z }
both threads have added
โ E(1/16)โ...
| {z }
no thread has sampledโจ E(1/4)โ...
| {z }
one thread has sampled 0โจ E(1)โ...
| {z }
both threads sampled 0
We initially have that the invariant owns the whole error credit E(1/16). The first thread to sample
will use this E(1/16)with ht-rand-exp , ending up with E(1/4)in the case it samples 0, which
it uses to reestablish the invariant. The second thread to sample can then use this E(1/4)with
ht-rand-exp to get E(1)in the case that it also samples 0, at which point we can use err-1 to
exclude this case, just as we did in the original sequential example. Of course, additional ghost
state is needed to track this more complex protocol, but modern CSLs like Iris already provide
sophisticated tools for encoding these kinds of protocols.
Page 6:
6 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
createCntrโ๐_.ref0
readCntrโ๐๐.!๐
incrCntrโ๐๐.faa๐(rand 3)conTwoAddโlet๐=createCntr()in
(incrCntr๐|||incrCntr๐);
readCntr๐
Fig. 2. Refactored conTwoAdd code.
Although this example seems simple, to the best of our knowledge, Coneris is the first unary
program logic for randomized concurrent programs that can prove this bound of 1/16forconTwoAdd .
Prior concurrent separation logics, even those that are specific to first-order languages [Fesefeldt
et al.2022; Zilberstein et al .2024] lack logical facilities necessary for expressing this non-trivial
protocol on the shared state.
Modularity and Randomized Logical Atomicity. While placing error credits in a shared
invariant solved the problems discussed in the previous part, the solution we have shown so
far is not modular. To see this issue, imagine that we refactored the code of conTwoAdd as in
Figure 2. Here we introduce intermediate functions that encapsulate the operations performed on
the location ๐. Ideally, we ought to be able to derive separate specifications for these operations,
and then use only their specifications to prove the same Hoare triple we had before for conTwoAdd .
For example, we might introduce a predicate of the form counter๐๐โ๐โฆโ๐capturing the value
of the counter. Then, a specification for incrCntr might look like
E๐3[F]โค๐
{counter๐๐ฅโE(๐)}incrCntr๐{โ๐.counter๐(๐ฅ+๐)โE(F(๐))โ๐โ{0..3}}
which expresses the effects of adding values to the counter and also allows for averaging error
credits across the different outcomes, similarly to ht-rand-exp . However, this specification for
incrCntr is not sufficient for verifying conTwoAdd . As we saw previously for that example, we must
put the points-to assertion for ๐(corresponding to counter ) and the E(1/16)in an invariant. But we
cannot use ht-inv-open to open this invariant when using the above specification for incrCntr ,
because incrCntr๐is not an atomic expression.
Fornon-probabilistic concurrent programs, a standard solution to this problem is to derive a
specification that captures that incrCntr behaves as if it were atomic when incrementing the value
in the counter. Although several different techniques have been proposed for encoding what it
means for a function to be logically atomic, in Coneris we adapt the HOCAP [Svendsen et al .2013]
approach. At its core, the idea behind HOCAP is to observe that what makes a physically atomic
expression special, in terms of the rules of the logic, is that we can open an invariant around a
physically atomic step. Thus, to capture that an operation behaves as if it is atomic, we need a
specification style that allows for opening an invariant at the logical point where an operation
takes effect. In Iris, this is captured through the update modality , written|โ๐which holds when ๐
can be derived by opening invariants. By deriving a specification for incrCntr in which the counter
assertion occurs under such an update modality in the pre-condition, we can enable incrCntr to
open invariants to get this counter assertion at the moment when it performs the faa.
To extend this idea to the probabilistic setting, Coneris introduces a new modality, called the
probabilistic update modality |โโ, which additionally allows for error credits to be updated in an
expectation preserving way, in the style of ht-rand-exp . Intuitively,|โโ๐holds if we can make an
instantaneous probabilistic update of our resources such that the outcome satisfies ๐. Compared to
the standard update modality |โ, the probabilistic update modality can additionally redistribute
errors credits through a logical operation called tape presampling that allows clients to reason
Page 7:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 7
about future probabilistic choices. By using this new modality in HOCAP-style specifications, we
are able to capture randomized logical atomicity, enabling modular reasoning about concurrent
probabilistic libraries. Because this technique requires more advanced rules of Coneris, we postpone
demonstrating it to ยง5.1, and first present more of the formal details of Coneris and the semantics
of ConcRandML, the concurrent programming language used to express our examples.
3 Preliminaries
In ยง3.1, we first recall various definitions from probability theory. We then present the syntax of
ConcRandML in ยง3.2 and its operational semantics in ยง3.3.
3.1 Probability Theory
To account for possibly non-terminating behavior of programs, we define our operational semantics
using probability sub-distributions. A discrete subdistribution (henceforth simply distribution ) on a
countable set ๐ดis a function ๐:๐ดโ[0,1]such thatร
๐โ๐ด๐(๐)โค1. The collection of distributions
on๐ดis denoted byD(๐ด). The null distribution 0:D(๐ด)is the constant function ๐๐ฅ.0. We let
{๐..๐}denote the set{NโN|๐โค๐โค๐}and for๐โฅ0we let ๐๐:D(N)denote the
(uniform) distribution that returns 1/(๐+1)for every๐โ{0..๐}and0otherwise. The expected
value of๐:๐ดโ[0,1]with respect to ๐is defined as E๐[๐]โร
๐โ๐ด๐(๐)ยท๐(๐). The mass of๐is
E๐[๐๐ฅ.1]. Given a predicate ๐on๐ด, the Iverson bracket[๐]evaluates to 1if๐(๐)is true and to 0
otherwise, and the probability of ๐w.r.t.๐isPr๐[๐]โE๐[[๐]]. Distributions form a monad; we
write๐โซ=๐forbind(๐,๐), which is defined as follows.
ret:๐ดโD(๐ด) bind:(๐ดโD(๐ต))รD(๐ด)โD(๐ต)
ret(๐)(๐โฒ)โ[๐=๐โฒ] bind(๐,๐)(๐)โโ๏ธ
๐โ๐ด๐(๐)ยท๐(๐)(๐)
3.2 The ConcRandML Language
Our examples are written in the ConcRandML language, which is an ML-style programming
language extended with probabilistic sampling and fork-based concurrency. The syntax of the
language is defined by the grammar below:
๐ฃ,๐คโVal::=๐งโZ|๐โB|()|โโLoc|rec f x =๐|(๐ฃ,๐ค)|inl๐ฃ|inr๐ฃ|
๐โExpr ::=๐ฃ|x|rec f x =๐|๐1๐2|๐1+๐2|๐1โ๐2|...|if๐then๐1else๐2|(๐1,๐2)|fst๐|...
ref๐1|!๐|๐1โ๐2|๐1[๐2]|rand๐|fork๐|faa๐1๐2|rand๐1
๐โState ::=LocfinโโVal
๐โCfg::=List(Expr)รState
The syntax is mostly standard, for example, the expressions ref๐,!๐, and๐1โ๐2allocate, load
from, and store into a reference, respectively. An array ๐1can be accessed at offset ๐2(for load or
store) via๐1[๐2]andrand๐samples from the uniform distribution on {0,...,๐}.
Concurrency is supported via fork๐, which executes ๐in a new thread, and atomic operations
which provide synchronization between threads. For example, the atomic fetch-and-add faa๐1๐2
instruction adds the integer ๐2to the value ๐ฃstored at location ๐1and returns ๐ฃ.2
A program configuration ๐โList(Expr)รState is given by a pair containing the list of currently
executing threads and the heap modeled as a finite map from locations to values. A configuration ๐
isfinal if the first expression in the thread list is a value.
2ConcRandML also supports other atomic instructions such as atomic exchange and compare-and-swap, which we omit
here for brevity.
Page 8:
8 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
3.3 Operational Semantics
The operational semantics of ConcRandML programs is given in stages: expressions take a single
execution step, which gets lifted to thread pools by schedulers, and finally these steps are chained
together to obtain full program execution.
Expressions. Thestep function takes an expression (representing the currently active thread)
and the current state and produces a distribution over the new expression, new state, and a (possibly
empty) list of newly spawned threads. ConcRandML has a standard call-by-value semantics where
steps can occur under evaluation contexts. Deterministic language constructs like if-then-else (1)
orfork๐(2)step deterministically by using the return of the distribution monad. The rand๐
instruction (3) uniformly associates probability 1/(๐+1)to any integer ๐between 0 and ๐.
step :(Expr,State)โD( Expr,State,List(Expr))
step(if true then๐1else๐2,๐)=ret(๐1,๐,[]) (1)
step(fork๐,๐)=ret((),๐,[๐]) (2)
step(rand๐,๐)=๐(๐,๐,[]).1
๐+1if๐โ{0,...,๐}and 0otherwise (3)
Thread Pools and Schedulers. The operational semantics of a configuration ๐=(ยฎ๐,๐)is
then given simply by indicating which thread amongst ยฎ๐should step, i.e., by specifying an index
๐โ[0,|ยฎ๐|โ1]and applying the step function to (๐๐,๐):
tpStep(ยฎ๐,๐)(๐)โ๏ฃฑ๏ฃด๏ฃด๏ฃด ๏ฃฒ
๏ฃด๏ฃด๏ฃด๏ฃณ0 if(ยฎ๐,๐)is final,
ret(ยฎ๐,๐) if๐๐is a value,
step(๐๐,๐)โซ=๐(๐โฒ
๐,๐โฒ,ยฎ๐โฒ).ret(ยฎ๐[๐โฆโ๐โฒ
๐]++ยฎ๐โฒ,๐โฒ)otherwise.
If๐is final, it does not step. If ๐๐is a value, we take a stutter step. Otherwise, we update the ๐-th
thread with the stepped expression ๐โฒ
๐and append the newly spawned threads ยฎ๐โฒto the thread pool.
Ascheduler decides which thread in a configuration to step next. Formally, a (probabilistic,
stateful) scheduler is given by a transition function ๐:(SchedStรCfg)โD( SchedStรN), which
takes in an internal state ฮโSchedSt and a configuration ๐, and returns a distribution on its new
internal state and the index of the thread in ๐to step next.
Given a configuration ๐, a scheduler ๐, and a scheduler state ฮ, we can now define the single
scheduler-step reduction function schStep๐(ฮ,๐)โD( SchedStรCfg)as follows:
schStep๐(ฮ,๐)โ๐(ฮ,๐)โซ=๐(ฮโฒ,๐).tpStep(๐,๐)โซ=๐๐โฒ.ret(ฮโฒ,๐โฒ)
Intuitively, schStep๐(ฮ,๐)first evaluates ๐(ฮ,๐)to get a new state ฮโฒand index๐, steps the๐-th
thread to obtain the configuration ๐โฒ, and returns the new scheduler state and configuration.
The notion of scheduler we consider is quite strong. Firstly, the scheduler is probabilistic and can
update its internal state and choose the next thread to step probabilistically instead of deterministi-
cally. Secondly, the update decision of a scheduler can depend not only on its internal state, but
also on the entire view of the thread pool and the memory state. These two design choices provide
more power to the scheduler and enable us to reason about the error bounds of algorithms under a
larger and richer class of schedulers than, say, deterministic schedulers.
Page 9:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 9
Program Execution. We next define ๐-step program execution with respect to a scheduler ๐as
the following recursive function exec๐,๐:(SchedStรCfg)โD( Val).
exec๐,๐(ฮ,๐)โ๏ฃฑ๏ฃด๏ฃด๏ฃด ๏ฃฒ
๏ฃด๏ฃด๏ฃด๏ฃณret๐ฃ if๐is final and๐=(๐ฃยทยฎ๐,๐)for some๐ฃโVal,
0 if๐=0and๐is not final,
schStep๐(ฮ,๐)โซ=exec๐,๐โ1otherwise.
One can read exec๐,๐(ฮ,๐)(๐ฃ)as the probability of returning ๐ฃin the first thread after at most ๐steps
of๐under the scheduler ๐initialized with the scheduler state ฮ. Finally, full program execution is
defined as the limit of exec๐,๐, which exists by monotonicity and continuity:
exec๐(ฮ,๐)โlim๐โโexec๐,๐(ฮ,๐)
We simply write exec๐๐if the result is the same for all initial program and scheduler states.
Traditionally, a program ๐issafeif it never gets stuck during execution, i.e., any partial program
execution starting from ๐is either a value or it can make progress. To define the appropriate notion
of safety for the probabilistic setting of ConcRandML (see Theorem 4.2), we need the following
auxiliary definition of partial program execution pexec๐,๐:(SchedStรCfg)โD( SchedStรCfg).
pexec๐,๐(ฮ,๐)=(
ret(ฮ,๐) if๐is final or๐=0,
schStep๐(ฮ,๐)โซ=pexec๐,๐โ1otherwise.
We can view pexec as a relaxation of exec which keeps probability mass on configurations that are
not final, whereas the latter only considers final configurations.
4 Logic
In this section, we dive into the rules of Coneris. We start with a glance of the syntax of the logic
and its adequacy theorem. Then, we explore the general program logic rules before discussing
presampling tapes and the probabilistic update modality.
4.1 Introduction to Coneris
The Coneris logic is built on top of the Iris base logic [Jung et al .2018] and inherits all of the basic
propositions and their associated proof rules. This includes the later modality โฒ, the persistence
modality and the points-to connective โโฆโ๐ฃthat asserts exclusive ownership of the location โ
storing value ๐ฃ. A selection of Coneris propositions are shown below.
๐,๐โiProp ::=True|False|๐โง๐|๐โจ๐|๐โ๐|โ๐ฅ.๐|โ๐ฅ.๐|๐โ๐|๐โ๐|โฒ๐|๐|
๐๐|๐๐พ| |โE1E2๐|โโฆโ๐ฃ|{๐}๐{๐}E|E(๐)|๐
โฉโ(๐,ยฎ๐)| |โโE1E2๐|...
Coneris is a separation logic and propositions denote sets of resources. ๐โ๐holds for resources
that can be decomposed into two disjoint pieces satisfying ๐and๐. The separating implication
๐โ๐is the right adjoint of โ, in the sense that ๐โ(๐โ๐)โข๐. While omitted in ยง2, note
that invariant assertions ๐๐are annotated with an identifying name ๐which is used to prevent
the prover from opening the same invariant twice (which is unsound). For bookkeeping purposes,
Hoare triples are annotated with the set of invariant names that the specification relies on; we omit
thismask annotation when considering the set of all invariant names โค.
As mentioned in ยง2, we internalize error bounds using the error credit assertion E(๐). The
presampling tape assertion๐
โฉโ(๐,ยฎ๐)is a probabilistic connective that we adapt from Clutch
[Gregersen et al .2024] which plays a key role in deriving certain modular specifications. The
probabilistic update modality |โโE1E2๐is a novelty of the Coneris logic. We further discuss these
three connectives and their role in the following section.
Page 10:
10 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
The meaning of the Coneris Hoare triple is captured by the adequacy theorem shown below.
Theorem 4.1 (Adeqacy). If{E(๐)}๐{๐}, then for all schedulers ๐,Prexec ๐๐[ยฌ๐]โค๐.
The theorem says that by proving a Hoare triple for the expression ๐, assuming initial ownership
ofE(๐)error credits, then for all schedulers ๐, the probability of the program ๐returning a value
notsatisfying the proposition ๐is smaller than or equal to ๐.
In addition, we have another safety theorem that provides an upper bound on the probability of
the expression getting stuck.
Theorem 4.2 (Safety). If{E(๐)}๐{True}, then for all schedulers ๐with mass 1and integers ๐,
the mass of pexec๐,๐(ฮ,([๐],๐))is greater or equal to 1โ๐.
Intuitively, this theorem states that proving {E(๐)}๐{True}in Coneris implies that the probability
of๐getting stuck is at most ๐for all schedulers3.
4.2 Rules of Coneris
Program-Logic Rules. Coneris satisfies all the usual structural and computational rules present
in Iris-based separation logics. For example, Coneris satisfies the bind rule ( ht-bind ), the frame
rule ( ht-frame ), and the usual computational rules of for interacting with the heap ( e.g.,ht-load ).
ht-bind
{๐}๐{๐ฃ.๐}โ๐ฃ.{๐}๐พ[๐ฃ]{๐
}
{๐}๐พ[๐]{๐
}ht-frame
{๐}๐{๐}
{๐โ๐
}๐{๐โ๐
}ht-load
{๐โฆโ๐ฃ}!๐{๐ค.๐ค=๐ฃโ๐โฆโ๐ฃ}
Invariants can be allocated by giving up ownership of the corresponding resources ( ht-inv-
alloc ). If we own an invariant, we can temporarily, for one atomic step, get access to its contents
(ht-inv-open ). The later modality โฒis important for soundness but can otherwise be ignored [Jung
et al. 2018].
ht-inv-alloc
๐๐โ๐
๐{๐
}E
{๐โ๐}๐{๐
}ht-inv-open
๐atomic{โฒ๐ผโ๐}๐{โฒ๐ผโ๐}E
๐ผ๐โ๐
๐{๐}Eโ{๐}
The Update Modality. The update modality |โE1E2is the primary primitive for manipulating
ghost resources and interacting with invariants in the Iris base logic. As we alluded to earlier in ยง2,
a key idea behind the HOCAP approach to modular specification is to use this modality as a way to
assert that a proposition could be proven by opening invariants.
The update modality |โE1E2is annotated with two sets of invariants. We write |โEwhen
E1=E2=Eand|โwhenE=โค, the set of all names. Intuitively, the assertion |โE1E2๐denotes a
resource that, together with the resources from the invariants in E1, can be updated and split into
two disjoint pieces: one satisfying ๐and one satisfying the invariants in E2. That is, we can use
the update modality to specify resource updates and invariant access ( inv-open ) as an assertion
in the logic rather than just as a primitive rule of the program logic. The update modality can be
eliminated ( ht-fupd-elim ) at any suitable time during program verification.
inv-open
๐๐โฒ๐โ|โE1E2(โฒ๐โ๐)
|โE1โ{๐} E 2โ{๐}๐ht-fupd-elim
๐atomic{๐โ๐}๐
|โE2E1๐
E2 |โE1E2๐โ๐
๐{๐
}E1
A key idea behind the approach we apply in ยง5 is to parameterize program specifications by a
proposition of the shape ๐โ|โE1E2๐, a so-called view shift , that is eliminated at the linearization
3The condition that ๐must have mass 1 (for all scheduler states) rules out the pathological situation where a configuration
is โstuckโ because ๐has probability less than 1 to pick anythread to step next. The assumption is only used in Theorem 4.2.
Page 11:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 11
point of the module operation. By providing a view shift as an argument, the client can specify how
they wish for their logical state (their โviewโ) to evolve when the operation physically takes place.
Presampling Tapes. Reminiscent of how prophecy variables [Abadi and Lamport 1988, 1991;
Jung et al .2019] allow us to talk about the future, presampling tapes give us the means to talk
about the outcome of sampling statement in the future. Presampling tapes were introduced in
Clutch [Gregersen et al .2024] to address an alignment issue in refinement proofs, but as we later
see in ยง5 they also play a crucial role in modularizing (unary) proofs about concurrent probabilistic
programs through probabilistic view shifts.
Intuitively, presampling tapes allow us in the logic to presample the outcome of future sampling
statements. Formally, they appear both operationally and in the logic. In the programming language,
presampling tapes appear as two new ghost code constructs, tape๐andrand๐1๐2, that are used to
allocate a new presampling tape and sample from a tape, respectively.
๐ฃโVal::=...|๐
โLabel
๐โExpr ::=...|tape๐|rand๐1๐2๐โStateโ(LocfinโโVal)ร( LabelfinโโTape)
๐กโTapeโ{(๐,ยฎ๐)|๐โNโงยฎ๐โNโ
โค๐}
In the operational semantics, allocation of a fresh presampling tape (4)viatape๐determin-
istically associates a fresh label ๐
to the empty tape ๐. A labelled rand๐
๐ with an empty tape
samples uniformly (5),i.e., it behaves like an unlabelled rand๐. If, on the other hand, the tape ๐
is
non-empty, rand๐ ๐
deterministically pops the first value ๐from the tape (6). Note that no step in
the operational semantics writes contents to a presampling tape. In fact, tapes and label annotations
do not in any way alter the behavior of the program and can be entirely erased [Gregersen et al .
2024]. However, as we later see, the probabilistic update modality allows us to reason as if a
presampling step could asynchronously pre-populate a tape with a random sample at any point in
time.
step(tape๐,๐)=ret(๐
,๐[๐
:=(๐,๐)],[]) (where๐
is fresh w.r.t. ๐) (4)
step(rand๐
๐,๐)=๐(๐,๐,[]).1
๐+1if๐[๐
]=(๐,๐)โง๐โ{0,...,๐}and 0otherwise (5)
step(rand๐
๐,๐[๐
:=๐ยทยฎ๐])=ret(๐,๐[๐
:=ยฎ๐],[]) (6)
Now, the logical assertion ๐
โฉโ(๐,ยฎ๐)denotes ownership of the presampling tape ๐
with bound
๐and contentsยฎ๐, analogously to how the points-to connective for the heap denotes ownership of a
location and its contents. The two rules ht-alloc-tape andht-rand-tape reflects the operational
behavior of equations (4) and (6) in the logic.
ht-alloc-tape
{True}tape๐{๐
.๐
โฉโ(๐,๐)}ht-rand-tape
{๐
โฉโ(๐,๐ยทยฎ๐)}rand๐
๐{๐ฅ.๐ฅ=๐โ๐
โฉโ(๐,ยฎ๐)}
Probabilistic Update Modality. Previous work [Aguirre et al .2024; Gregersen et al .2024;
Haselwarter et al .2025] introduce presampling tapes for different purposes but, common for all
instantiations, presampling is only supported as a rule in the program logic. Similar to how ht-
inv-open only allows us to reason about invariants using the program logic, presampling is only
supported as a primitive program-logic rule. This is notsufficient for the modular specifications we
set out to prove. Intuitively, we need a way to specify updates to presampling tapes as an assertion ,
just like the update modality enables us to specify invariant access and resource updates as an
assertion.
To this end, we introduce the probabilistic update modality |โโE1E2๐. This modality satisfies all
the same rules as the update modality; for example, it can be used to open invariants (hence the
Page 12:
12 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
invariant masksE1andE2) and update resources, and it is monadic ( pupd-ret andpupd-bind )
and can be derived from the update modality ( pupd-fupd ).
pupd-ret
๐
|โโE๐pupd-bind
|โโE1E2๐ ๐โ|โโE2E3๐
|โโE1E3๐pupd-fupd
|โE1E2๐
|โโE1E2๐
pupd-presample-exp
E๐๐[F]โค๐ E(๐)๐
โฉโ(๐,ยฎ๐)
|โโE(โ๐.๐
โฉโ(๐,ยฎ๐ยท๐)โE(F(๐)))pupd-err
|โโE(โ๐.0<๐โE(๐))
The key novelty of the probabilistic modality is its ability to populate presampling tapes as
shown in pupd-presample-exp . The rule says that if we own a presample tape we can populate the
tape with a freshly sampled value ๐. Similar to ht-rand-exp , it also allows re-distributing error
credits along different branches of the randomized outcome, so long as the expected value of the
error credit does not increase. We showcase the probabilistic update modality on an example in
ยง5.4.
As a somewhat orthogonal property, the probabilistic update modality also internalizes the
notion of continuity of probabilities within the logic. Specifically, it permits synthesizing some
arbitrarily small error credit out of thin air as seen in pupd-err . This principle enables induction by
error amplification [Aguirre et al. 2024] as we showcase in ยง5.4.
5 Modular Specifications of Concurrent Randomized Modules
In this section, we first provide an overview of how HOCAP-style specifications capture logically
atomicity of concurrent data structures. Next, we explain how we extend the approach to capture
randomized logical atomicity and present a modular specification for the randomized counter
module. We also describe how the specification is strong enough to verify clients that use the
randomized counter module concurrently. Subsequently, we present three different implementations
of the concurrent randomized counter module and discuss how to show that they satisfy the
specification. Later in ยง6 we discuss how we verify a series of larger case studies.
5.1 Modular Specifications of Concurrent Randomized Modules: Overview
Before considering the randomized setting, we showcase our specification style on a non-randomized
example: a concurrent counter module with functions for creating a counter, (deterministically)
incrementing by one, and reading.
As alluded to in ยง4.2, the high level idea is to parameterize specifications by a view shift that
captures how the logical state of the counter evolves at the linearization point. Our specification of
the non-randomized counter module is shown in Figure 3.
{True}createCntr(){๐.โ๐.counter๐๐โcfrag 1 0}
โE,๐,๐,๐.
counter๐๐โ(โ๐ง.cauth๐งโ|โEcauth(๐ง+1)โ๐๐ง)
incrCntr๐{๐ง.๐๐ง}Eโ{๐}
โE,๐,๐,๐.
counter๐๐โ(โ๐ง.cauth๐งโ|โEcauth๐งโ๐๐ง)
readCntr๐{๐ง.๐๐ง}Eโ{๐}
Fig. 3. Specification for a (non-randomized) concurrent counter module.
When creating a counter, one obtains ownership of two resources: counter๐๐andcfrag 1 0. The
counter๐๐resource captures that ๐is a counter with an associated invariant name ๐. Intuitively,
Page 13:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 13
this invariant contains the internal state of the counter but the details are unknown to clients. The
predicate is persistent, i.e.,counter๐๐โฃโขcounter๐๐โcounter๐๐and can hence be freely shared.
The predicates cauth andcfrag provide authoritative andfragmental views of the counter. In-
tuitively, cauth provides the counter moduleโs view of the counter and cfrag denotes the clientโs
view. A fragmental view cfrag๐ ๐denotes a๐-fractional view that the counter is at least the
value๐. The cfrag๐ ๐resource can be split and combined, i.e.,cfrag(๐1+๐2)(๐1+๐2) โฃโข
cfrag(๐1,๐1)โcfrag(๐2,๐2)and thus shared. The fragmental view is guaranteed to be consis-
tent with the authoritative view, i.e.,cauth๐โcfrag๐๐โข๐โค๐andcauth๐โcfrag 1๐โข๐=๐,
and updated accordingly, i.e.,cauth๐โcfrag๐๐โข|โEcauth(๐+๐)โcfrag๐(๐+๐).
The specification for the increment and read functions are parameterized by a view shift that gives
(temporary) access to the moduleโs view. This is one of the key ideas of HOCAP-style specifications.
From the clientโs perspective, the view shift is a proof obligation. For the increment function,
proving this view shift requires having ownership of a fragmental view (to update the resources),
but the fragmental view can be provided by opening an invariant using the update modality. The
client-chosen predicate ๐lets the client derive information as part of the view shift. For example,
they can pick ๐๐งโcfrag๐(๐+1)โง๐ง=(๐+1).
Probabilistic Concurrent modules with Error Redistribution. Now, consider the random-
ized concurrent counter module from ยง2 where the increment function increments the counter by a
value chosen uniformly at random from 0 to 3. For the client to be able to redistribute error credits
as part of the random sampling, we parameterize the specification of the increment function by
another view shift as shown in Figure 4.
โE,๐,๐,๐.
counter๐๐โ |โE โ
โ๐,F.E(๐)โ(E๐3[F]โค๐)โโ๐ฅโ{0..3}.E(F(๐ฅ))โ |โโ
E(โ๐ง.cauth๐งโ|โEcauth(๐ง+๐ฅ)โ๐๐F๐ฅ๐ง)
incrCntr๐
{๐ง.โ๐,F,๐ฅ.๐๐F๐ฅ๐ง}Eโ{๐}
Fig. 4. Specification of incrCntr for a concurrent randomized counter module
Notice that in the precondition the client now has to prove a view shift which is split into two
parts. We begin by looking at the second part (the line at the bottom). This is analogous to the
deterministic case, except that now the abstract state cauth๐งgets incremented by some uniformly
sampled๐ฅโ{0..3}. This operation is randomized, so we also let the client update their error credits
along this distribution, which is the first part of the view shift. After opening all invariants in E,
the client chooses some ๐and an error distribution function F, gives up E(๐), gets back E(F(๐ฅ)),
re-establishes all invariants in E, and goes on to prove the second part. Notice that the specification
allows the client to retrieve error credits from an invariant. Intuitively, these two parts of the view
shift capture two separately logically atomic actions of the increment operation. The first being
the random operation where we re-distribute errors, and the second being the actual increment,
where we increase the counter by the sampled value. If all these preconditions are satisfied, then at
the end of incrCntr , we return some value ๐งwhich satisfies ๐๐F๐ฅ๐งfor some๐,F, and๐ฅ. The
specification for creating and reading the counter are unchanged as no randomization is involved.
Probabilistic Concurrent Modules with Error Redistribution and Presampling. One
limitation of the previous specification is that the sampling operation is fixed to take place within
the function call incrCntr . As a result, the only point at which randomness can be generated for the
Page 14:
14 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
module, and errors can be distributed, is at the invocation of the increment operation. However, it
is sometimes useful to reason about the probabilistic part of the operation asynchronously.
In Clutch [Gregersen et al .2024] presampling tapes are used to generate randomness asyn-
chronously and facilitate refinement proofs. In a concurrent setting, there is also an asynchronous
component arising from the order in which randomized operations are physically resolved, and we
propose the use of presampling and tapes to resolve them in advance and independently from this
order.
In the previous specification, the view shift consisted of a probabilistic part ( i.e., spending and
distribution of error credits) and a deterministic part (updating the abstract state). Presampling
allows us to decouple these two parts and reason about them separately, resulting in a more
expressive HOCAP-style specification. We demonstrate that by exposing tapes and presampling
operations in the module specifications, clients can perform presampling for an abstract randomized
operation. This is an indispensable technique for verifying certain concurrent modules, and we
show an example in ยง5.2.
The new and final specification of the probabilistic counter module, which includes not only
error redistribution, but also a (ghost) method for creating an abstract presampling tape and a
(ghost) operation for sampling on a tape, see Figure 5. This specification has a new predicate ctape
โE,๐,F,ยฎ๐,๐
.(E(๐)โ(E๐3[F]โค๐)โctape๐
ยฎ๐โ|โโEโ๐โ{0..3}.E(F(๐))โctape๐
(ยฎ๐ยท[๐]))
โ๐,๐.{counter๐๐}createCtape(){๐
.ctape๐
๐}
โE,๐,๐,๐,ยฎ๐,๐.counter๐๐โctape๐
(๐ยทยฎ๐)โ
(โ๐ง.cauth๐งโ|โEcauth(๐ง+๐)โ๐๐ง)
incrCntr๐๐
๐ง.ctape๐
ยฎ๐โ๐๐ง
Eโ{๐}
Fig. 5. Specification for a randomized counter module with presampling tapes.
that stores the presampled randomness for the random counter. Note that ctape is an abstract
predicate which might be realized in multiple ways besides using primitive tape predicates, which
allows us to hide the details of how different implementations of the counter module physically
generate randomness. By exposing the abstract presampling tape explicitly, we aim to capture
more of the proof principles for a concrete randomized operation. (In ยง5.2, we demonstrate that
this specification which exposes abstract tapes is in fact more general than the previous one)
Compared to the previous randomized specification, reasoning about randomness of the incre-
ment operation is now extracted into a separate condition that utilizes the probabilistic update
modality (|โโ๐), which says that we can presample onto the ctape and distribute errors in a
expectation-preserving manner. With this change, clients can allocate their own local tapes via
createCtape and reason about randomness locally. The incrCntr function takes a non-empty ctape
predicate as argument, and acts in a (logically) deterministic manner, by reading and consuming
the first element ๐of the tape, and incrementing the abstract state of the counter by ๐.
Now that we have shown an expressive specification (Figure 5), in the following sections, we show
how this specification suffices to verify clients. We also show that three different implementations
of the probabilistic random counter module all meet this specification. These three implementations
exhibit different numbers of sampling operations, but yet they all meet the same abstract module
specification. In other words, the randomization of the increment operation acts โlogically atomicโ
as expressed by a single probabilistic update, even if in reality, it is not. From the perspective of
a client, random sampling within the increment operation appears to behave as if it is simply a
single rand 3. We refer to this as randomized logical atomicity .
Page 15:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 15
5.2 Verifying Clients of Randomized Counter Module
We now describe how the specification with error re-distribution and presampling tapes shown in
Figure 5 can be used to verify concurrent clients. We also show how the HOCAP-style specification
that exposes abstract tapes is more general than the one that does not.
Revisiting conTwoAdd .We begin with the conTwoAdd client example introduced in ยง 2. Since the
new specification of the randomized concurrent counter utilizes tapes, we annotate the conTwoAdd
client to use the abstract tapes exposed in the specification:
conTwoAddโlet๐=createCntr()in
let๐
=createCtape()in
incrCntr๐๐
|||let๐
=createCtape()in
incrCntr๐๐
;
readCntr๐
Recall that we expect the return value to be 0with a probability 1/16. We state this through the
following Coneris Hoare triple: {E(1/16)}conTwoAdd{๐ฃ.๐ฃ>0}.
We present here a high level of the proof, and defer the details to Appendix A. Most of this proof
is similar to the one sketched in ยง2 where we allocate an invariant that encodes a protocol that
tracks both the available amount of error credits and the ghost state of both threads and describes
how they can evolve. In the case where both threads sampled 0, we are able to obtain E(1)from
the invariant at the end and derive a contradiction with err-1 .
The difference between this proof and that from ยง2 are twofold. Firstly, the randomness is
generated asynchronously using the presampling rule and the abstract tapes. The probabilistic
update modality allows us to open the invariant, obtain error credits from it, presample onto our
abstract tapes, redistribute the error credits, and close the invariant again, all in an atomic manner.
Secondly, to apply incrCntr andreadCntr , we need to prove the view shifts in the precondition of
their corresponding Hoare triple specifications.
An important detail of this proof is that we do not need to place any ctape predicate within the
invariant. This is because each thread uses a separate, local tape (which means that tapes need
not be shared in an invariant). This kind of โlocal tapeโ principle lets each thread โownโ its own
randomness and this helps simplify the proof since we need not worry how the state of ctape is
changed by other external concurrent threads.
Advantage of Exposing Abstract Tapes. Recall that in ยง5.1, we presented a simpler speci-
fication of the randomized counter module in Figure 4 that does not expose presampling tapes
as abstract predicates. To see why that specification is not as general as that in Figure 5 and that
it is useful to expose the presampling tapes in the specification, consider the following twoIncr
program and its specification in Figure 6. The sequential program twoIncr first creates a new ran-
domized counter and allocates a tape for the counter. It then performs two incrCntr andreadCntr
pair operations successively, to read the exact values ๐ฃ1and๐ฃ2incremented into the counter. At
the end it returns 4โ๐ฃ1+๐ฃ2. Because both ๐ฃ1and๐ฃ2are sampled uniformly from {0,..., 3}, the
return value is uniformly distributed between {0,..., 15}. This is captured by the Hoare triple in
Figure 6, where we are able to split some amount of error credits across the 16possibilities in an
expectation-preserving way. Note that the view shift of the Hoare triple captures the fact that this
re-distribution of error credits happens in a logically-atomic manner.
Proving the specification of twoIncr with the more general specification (Figure 5) is relatively
straightforward. After applying the specification for creating the counter and the tape, we perform
two consecutive presamples onto ctape with the presampling specification of the counter module.
These two presampling operations are combined into one atomic operation with the pupd-bind
Page 16:
16 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
twoIncr _โlet๐=createCntr()in
let๐
=createCtape()in
incrCntr๐๐
;
let๐ฃ1=readCntr๐in
incrCntr๐๐
;
let๐ฃ2=readCntr๐โ๐ฃ1in
4โ๐ฃ1+๐ฃ2โ๐๐E.๏ฃฑ๏ฃด๏ฃด ๏ฃฒ
๏ฃด๏ฃด๏ฃณ( |โE โ
โ๐F.
E(๐)โ(E๐15[F]โค๐)โ
(โ๐ฅ.E(F(๐ฅ))โ|โโ
E๐๐F๐ฅ))๏ฃผ๏ฃด๏ฃด ๏ฃฝ
๏ฃด๏ฃด๏ฃพ
twoIncr(){๐ง.โ๐F.๐๐F๐ง}Eโ{๐}
Fig. 6. Code and Specification of twoIncr
rule, allowing us to use the view shift provided in the precondition to split the error credits for the
16possibilities. The rest of the proof follows directly by applying the specification for incrementing
the counter with the tape and reading from it twice.
However, the specification without the presampling tapes exposed (Figure 4) is not strong enough
to prove this Hoare triple. This is because this specification restricts the error redistribution to only
occur within the incrCntr call, and we are unable to combine the two separate error redistribution
operations in each incrCntr call into one atomic action. On the other hand, the more general
specification allows us to โpullโ the randomized operation out of the incrCntr call, and lets us
perform the randomized operation in advance via the presampling operation of the abstract tapes.
5.3 Three Implementations of the Randomized Counter Module
Recall from ยง5.1 that the randomized counter module of the specification from Figure 5 must
provide four methods: createCntr for creating the counter, createCtape for creating a tape, incrCntr
for incrementing the counter with a random value chosen uniformly from the set {0,..., 3}(sampled
from the tape), and readCntr for reading the value of the counter.
To emphasize the point about modularity, we consider three implementations that we show
meet the same specification, which we refer to as ๐ผ1,๐ผ2, and๐ผ3, respectively. They only differ in
the way they implement the createCtape andincrCntr method โ the implementations of createCntr
andreadCntr are the same in the three implementations and look as follows:
createCntrโ๐_.ref0 readCntrโ๐๐.!๐
Internally, the counter is represented by a pointer to a number and the read method simply
dereferences the pointer. The three implementations of the create tape and increment methods
are shown in Figure 7. In ๐ผ1, the increment method simply increments the counter value stored at
createCtape1โ๐().tape 3
createCtape2โ๐().tape 1
createCtape3โ๐().tape 4incrCntr 1โ๐๐๐
. faa๐(rand๐
3)
incrCntr 2โ๐๐๐
. let๐
=tape 1in
faa๐(rand๐
1โ2+rand๐
1)
incrCntr 3โrec๐ ๐๐
=let๐ฅ=rand๐
4in
if๐ฅ<4then faa๐๐ฅelse๐ ๐๐
Fig. 7. Implementation of the counter module.
the location by a rand 3chosen value between 0and3using a fetch-and-add atomic instruction
Page 17:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 17
(faa). The function hence creates a tape with bound 3. In๐ผ2, the increment method is implemented
using two coin-flips (i.e., calls to rand 1), and in๐ผ3, we use a recursive rejection sampler that, in
order to simulate rand 3, repeatedly samples from rand 4until it gets a value within {0,...3}. The
createCtape function for both implementations creates a tape with bound 1and4, respectively.
For๐ผ2and๐ผ3in particular, it is interesting that even though the implementations do not sample
randomness atomically (e.g., ๐ผ3can possibly execute any number of rand 4operations), they still
meet the specification where the presampling of a single value onto the abstract tape is described
by a single probabilistic update modality (we show this in the next subsection). In other words, we
capture randomized logically atomicity of the module, in the sense that externally, there appears to
be a single randomized transition within the incrCntr function.
5.4 Proving that ๐ผ1,๐ผ2, and๐ผ3satisfy the HOCAP-style Specification with Error
Redistribution and Presampling Tapes
We now show how the three randomized counter implementations meet the specification with error
redistribution and presampling tapes. We start by giving concrete definitions for the three abstract
predicates. For the three implementations, it turns out that the counter predicate counter , and the
cauth andcfrag predicates are defined identically; the persistent counter predicate counter๐๐is
defined asโ๐,๐.๐=๐โ๐โฆโ๐โcauth๐๐, and the cauth andcfrag predicates are defined with a
standard authoritative-fractional resource algebra. We show the exact definition of ctape for each
of the three implementations below.
ctape1๐
ยฎ๐โ๐
โฉโ(3,ยฎ๐)
ctape2๐
ยฎ๐โ๐
โฉโ(1,expandยฎ๐)โForall(๐๐ฅ.๐ฅ <4)ยฎ๐
ctape3๐
ยฎ๐โโยฎ๐.filter(๐๐ฅ.๐ฅ <4)ยฎ๐=ยฎ๐โ๐
โฉโ(4,ยฎ๐)
Forctape1, since the first implementation uses a rand 3to sample from 0to3directly, we define
ctape1with the presampling tape ๐
โฉโ(3,ยฎ๐). For ctape2, since we are sampling from 0to3via
tworand 1s, the predicate is defined by expanding the tape elements into its binary representation.
The function expand takes in a list of numbers and rewrites them into binary representation while
keeping the list โflattenedโ. For example expand([2; 3; 1; 0])returns[1; 0; 1; 1; 0; 1; 0; 0]. Finally,
for the third rejection sampler-like implementation, if we are logically storing ยฎ๐with our ctape
predicate, the concrete tape stores some list ยฎ๐such thatยฎ๐is equal toยฎ๐with all 4s removed from it.
It suffices to show that the functions createCntr ,createCtape ,incrCntr , and readCntr satisfy the
specification and that ctape satisfies its presampling probabilistic update specification, i.e., we can
logically append a new element into the ctape while redistributing errors. The specification of the
functions are not too complicated. As an example, consider the incrCntr specification for ๐ผ3.
counter๐๐โctape๐
(๐ยทยฎ๐)โ
(โ๐ง.cauth๐งโ|โEcauth(๐ง+๐)โ๐๐ง)
(rec๐ ๐๐
=let๐ฅ=rand๐
4in
if๐ฅ<4then faa๐๐ฅelse๐ ๐๐
)๐๐
{๐ง.ctape๐
ยฎ๐โ๐๐ง}Eโ{๐}
After unfolding the definition of the abstract predicates for ๐ผ3, we repeatedly loop through the
recursive function until we reach a value ๐in the tape that is smaller than 4by structural induction
on the tape or Lรถb induction. During the atomic faaoperation, we open the invariant with ht-inv-
open and eliminate the view shift in the precondition. The specification of the other functions can
be proven similarly.
Page 18:
18 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
We now focus on showing that for each of the ctape definitions, they satisfy the presampling
specification. For ctape1, we see after unfolding its definition, the statement of the presampling
specification is the same as that of pupd-presample-exp and hence holds directly. For ctape2, it
suffices to prove the following probabilistic update:
โยฎ๐.(E๐3[F]โค๐)โ๐
โฉโ(1,ยฎ๐)โE(๐)โ
|โโEโ๐ฃ1๐ฃ2.E(F(๐ฃ1โ2+๐ฃ2))โ๐
โฉโ(1,ยฎ๐ยท[๐ฃ1,๐ฃ2])(7)
This probabilistic update is valid because we can do two presamples consecutively via pupd-bind .
In detail, we first apply pupd-presample-exp to presample the first bit, choosing the first error
splitting functionF๐โ๐๐.if๐=1thenF(2)+F( 3)elseF(0)+F( 1). We then do a case split to
check which bit we really presampled. If it is a 0, we apply pupd-presample-exp again, choosing
the error splitting function to be F๐โ๐๐.if๐=1thenF(1)elseF(0). Otherwise, we instantiate
F๐โ๐๐.if๐=1thenF(3)elseF(2).
Forctape3we want to show that we can repeatedly presample enough values onto the tape such
that the last element is smaller than 4and all values beforehand are 4, while distributing the error
credit according to the final value, which can be shown by the following lemma:
โยฎ๐.(E๐3[F]โค๐)โ(โยฎ๐.filter(๐๐ฅ.๐ฅ <4)ยฎ๐=ยฎ๐โ๐
โฉโ(4,ยฎ๐))โE(๐)โ
|โโEโ๐.0โค๐<4โE(F(๐))โ(โยฎ๐.filter(๐๐ฅ.๐ฅ <4)ยฎ๐=(ยฎ๐ยท[๐])โ๐
โฉโ(4,ยฎ๐))(8)
We prove this probabilistic update through induction by error amplification . We first apply pupd-bind
andpupd-err to obtain some positive error credit E(๐โฒ)to get the following:
(E๐3[F]โค๐)โ๐โฒ>0โE(๐โฒ)โ(โยฎ๐.filter(๐๐ฅ.๐ฅ <4)ยฎ๐=ยฎ๐โ๐
โฉโ(4,ยฎ๐))โE(๐)โ
|โโEโ๐.0โค๐<4โE(F(๐))โ(โยฎ๐.filter(๐๐ฅ.๐ฅ <4)ยฎ๐=(ยฎ๐ยท[๐])โ๐
โฉโ(4,ยฎ๐))
Now we apply the induction by error amplification rule below (see Eris [Aguirre et al .2024] for
more details):
ind-err-amp
๐1>0๐>1 E(๐1)โ๐2.(E(๐ยท๐2)โ๐)โE(๐2)โ๐
๐
Morally this states that in order to prove ๐we can assume it holds guarded by an amount of credits
amplified by a factor ๐strictly greater than 1. We choose the amplification factor ๐โ5. It suffices
to show (with the induction hypothesis highlighted):
(E๐3[F]โค๐)โ๐โฒ>0โ E(5ยท๐โฒ)โ(โยฎ๐.filter(๐๐ฅ.๐ฅ <4)ยฎ๐=ยฎ๐โ๐
โฉโ(4,ยฎ๐))โ...โ
E(๐โฒ)โ(โยฎ๐.filter(๐๐ฅ.๐ฅ <4)ยฎ๐=ยฎ๐โ๐
โฉโ(4,ยฎ๐))โE(๐)โ...
We can now combine E(๐)โE(๐โฒ)with err-split and apply pupd-presample-exp with E(๐+๐โฒ)as
the initial error budget. We choose the distribution function to be ๐๐ฅ.if๐ฅ<4thenF(๐ฅ)else๐+5ยท๐โฒ.
After a single presampling step, we do a case split on whether the presampled value is 4or not. If it
is, then we establish the conclusion with the induction hypothesis since we successfully amplified
the error credit E(๐โฒ)by a factor of 5. Otherwise, we presampled an โacceptedโ value, and we can
directly establish the goal via pupd-ret .
6 Case Studies
In this section, we present several other case studies that we have verified using Coneris.
Page 19:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 19
6.1 Thread-Safe Hash Functions
Hash functions are often assumed to behave uniformly [Bellare and Rogaway 1993]. That is, a
hash function โfrom a set of keys ๐พto a set of values ๐behaves as if, for each key ๐, the hash
โ(๐)is randomly sampled from a uniform distribution over ๐independently of all other keys. This
assumption can be modeled using an idealized hash function that uses a mutable map, which serves
as a cache of hashes computed so far [Mittelbach and Fischlin 2021]. If the key has already been
hashed, we return the value stored in the map, otherwise we sample a fresh value uniformly, store
it in the cache, and return it. In the concurrent setting, however, this does not suffice: if two threads
concurrently attempt to hash the same key ๐, they may end up with different hash values. If one
thread gets preempted by the scheduler right after sampling, a second thread could overtake and
sample a different value before the first thread stores its value to the cache.
We implement a thread-safe idealized hash function using a lock. To hash a value, one first
acquires the lock, then samples the key and stores it to the cache, before releasing the lock again.
While the implementation is uninteresting, its specification is not. In particular, we give a specifica-
tion that offers exclusive ownership of each key ๐and the ability to presample the hash โ(๐). As
we later see in ยง6.2, this ability can greatly simplify the probabilistic analysis of concurrent data
structures that use hashing.
ThehashInit function initializes a new hash function and satisfies the specification below.
{True}hashInit()n
โ.โ๐พ.hashFun๐พโโโ๐โ๐พhashKey๐พ๐โo
Here,๐พis an abstract ghost name identifier used for tracking ghost state. The abstract predicate
hashFun๐พโis duplicable, i.e.,hashFun๐พโโฃโขhashFun๐พโโhashFun๐พโ, while hashKey๐พ๐โ
represents that key ๐has not yet been hashed, and is exclusive, i.e.,hashKey๐พ๐โโhashKey๐พ๐โโข
False . When invoking a hash function on a key with an undecided value, a fresh value ๐ฃโ๐is
sampled and hashKey๐พ๐๐ฃis returned. The predicate hashKey๐พ๐๐ฃis duplicable and each subsequent
invocation is guaranteed to return ๐ฃ.
n
hashFun๐พโโhashKey๐พ๐โo
โ๐n
๐ฃ.โ๐ฃโ๐.hashKey๐พ๐๐ฃo
n
hashFun๐พโโhashKey๐พ๐๐ฃo
โ๐{๐ค.๐ค=๐ฃ}
However, hash values can also be presampled and error credits redistributed across the possible
outcomes of the presampling using the probabilistic update below.
hashFun๐พ๐โhashKey๐พ๐โโ E(๐)โ
|โโโคโ๐ฃโ๐.hashKey๐พ๐๐ฃโ(๐ฃโ๐โE(๐1))โจ(๐ฃโ๐โE(๐0))
Here๐โ๐is some set of hash values and ๐1,๐0โ[0,1]such that๐1ยท|๐|+๐0ยท(|๐|โ|๐|)โค๐ยท|๐|.
For example, by picking ๐1โ1,๐0โ0, and๐โ|๐|/|๐|one can spend ๐error credits to avoid the
outcomes in ๐when determining the hash โ(๐).
We show the specification by allocating a fresh presampling tape for each key in K. A similar idea
is used in previous work [Gregersen et al .2024] to show refinement of lazy and eager hash functions.
In our specification, intuitively, hashKey๐พ๐โdenotes exclusive ownership of ๐โs presampling tape
which is transferred to an invariant after presampling. This invariant captures that, for all keys ๐,
either๐has been presampled onto ๐โs tape or๐has been stored at entry ๐in the hash functionโs
cache.
Page 20:
20 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
bfInit()โ
let hfs =List.init๐(ฮป_.hash _init())in
let arr =Array.init๐false in
(hfs,arr)bfAdd bfl๐ฅโ
let(hfs,arr)=bfl in
List.Iter(ฮปโ.let๐=โ๐ฅin
arr[๐]โ true)hfs
bfLookup bfl ๐ฆโ
let(hfs,arr)=bfl in
let res =ref true in
List.Iter(ฮปโ.let๐=โ๐ฆin
resโ!res&&arr[๐])hfs;
!resbfMain xs yโ
let bfl=bfInit()in
(rec๐zs=
match zs with
|[]โ()
|z::zsโฒโ(bfAdd bfl z)|||( f zsโฒ)
end)ks;
bfLookup bfl k
Fig. 8. Implementation of Concurrent Bloom Filter
6.2 Bloom Filter
Bloom filters are approximate data structures to represent sets, with operations for inserting
elements and querying for membership. In their most basic, sequential presentation, a Bloom filter
consists of an array of bits of a fixed size ๐, initially set to 0, and a list of hash functions (โ1,...,โ๐)
of some fixed length ๐. When inserting an element ๐ฅ, we compute(โ1(๐ฅ)mod๐,...,โ๐(๐ฅ)mod๐)
and set those indices to 1. When checking if an element ๐ฆis in the set, we also compute (โ1(๐ฆ)
mod๐,...,โ๐(๐ฆ)mod๐), and look up those indices in the array. If they are all set to 1, we answer
positively, otherwise we answer negatively. Thus, when checking the membership of an element
that is not in the set there exists a small probability of observing a false positive if there are hash
collisions with previously inserted elements. Computing this probability is challenging and requires
involved combinatorial reasoning, in fact Bloomโs original analysis [Bloom 1970] gave the wrong
bound.
An efficient concurrent implementation of a Bloom filter allows parallel insertions, since con-
current writes to the same entry in the array would both set the entry to 1. In this case study, we
implement a concurrent Bloom filter and prove a bound on the probability of observing a false
positive result on a membership query. We use the concurrent hash module presented in ยง6.1 to
implement this concurrent Bloom filter example (see Figure 8).
First consider ๐sequential insertions ๐ฅ1,...,๐ฅ๐followed by checking membership for some
๐ฆโ{๐ฅ1,...,๐ฅ๐}. From a mathematical perspective, the probability of false positive corresponds to
the following experiment: first sample a batch of ๐ยท๐integers uniformly at random in {0,...,๐โ1}.
Now sample a second batch of ๐integers in the same manner. What is the probability that they
areallin the first batch? The exact bound was first calculated by Bose et al .[2008], and in later
work, Gopinathan and Sergey [2020] mechanized the proof.
Now suppose that the insertions ๐ฅ1,...,๐ฅ๐happen in๐parallel threads. Intuitively, concurrent
implementations of Bloom filters should have the same probability of false positive, since parallel
queries to hash functions are independent. Using our logic, we can make this intuition concrete,
and prove that the bound in the concurrent setting indeed corresponds to the sequential one.
Page 21:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 21
Our modular approach allows us to simplify the mathematical reasoning within the proof of the
specification and defer all complex combinatiorial reasoning to the meta-level. The proof crucially
relies on both the stateful representation of error probabilities ( i.e., error credits) as well as the
notion of randomized logical atomicity, which allows us to presample all randomness in advance.
The key observation is that the probability of false positive follows a simple recurrence. Let
Efp(๐,๐)be the probability of observing a false positive for a single membership query after setting
๐uniformly selected indices to 1in an array that already contains ๐bits set to 1.
Efp(0,๐)โ(๐
๐)๐
Efp(๐+1,๐)โ๐
๐ยทEfp(๐,๐)+๐โ๐
๐ยทEfp(๐,๐+1)
Our analysis can therefore assume that every time we hash, we start with E Efp(๐+1,๐)for some
๐, where๐is the number of distinct hash outputs that have been observed so far, and then obtain
either E Efp(๐,๐)orE Efp(๐,๐+1)depending on whether or not the output of the hash is a new
one or not. This means that the decision on how to distribute credits can be done locally everytime
we hash a new element.
With this in mind, we can prove the following spec:
NoDup(xs)โyโxsโE Efp(kยท|xs|,0)
bfMain๐ฅ๐ ๐ฆ{๐ฃ.๐ฃ=false} (9)
i.e., the probability of false positive is at most Efp(๐ยท|๐ฅ๐ |,0), which corresponds to the theoretical
bound given by Bose et al .[2008] for the sequential setting4. In order to simplify reasoning about
concurrent hashing, we presample the hash outcomes for every key in ๐ฅ๐ in advance, using the
hash specification in ยง6.1. It is at this point that most reasoning about probabilities takes place,
and that we do the distribution of error credits. After this phase, we have E Efp(0,๐ต)for some๐ต,
as well as predicates of the form hashKey๐พ๐๐๐ฃ๐for every key and every hash, and we know that
the set of presampled hash outcomes has cardinality ๐ต. Then we execute all insertions, with an
invariant that ensures that the array never has more than ๐ตelements set to 1. Finally, we can do a
lookup, and use our error credits E Efp(0,๐ต)to ensure that at least one of the indices we look up
is set to 0, which guarantees that the query returns false .
To the best of our knowledge, we are the first to prove a tight bound on the probability of false
positives for a concurrent Bloom filter (9). For more details on the analysis, we refer the reader to
our Rocq development.
6.3 Other Case Studies
Other case studies demonstrating the flexibility of our approach in verifying concurrent randomized
data structures can be found in Appendix C. We first define a Rand module that captures the
operation of sampling from a uniform distribution atomically and we provide three implementations
that satisfy it (similar to the implementations in the randomized counter module from ยง5.3). We
then use this Rand module to implement a lazy random sampler that features a specification that
utilizes the probabilistic update modality in its view shift and we provide an example to show
why this feature is important. We also implement a concurrent collision-free hash data structure
and show that it meets an amortized error specification; the error required for each presampling
operation is fixed as it is amortized across a fixed number of insertions.
4Note that their bound is given as a closed mathematical expression and we have not mechanized that it corresponds to our
recursive definition.
Page 22:
22 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
7 Semantic Model and Soundness
Coneris is implemented on top of the Iris [Jung et al .2018] base logic, which in isolation, is simply
a higher-order separation logic not tied to any specific programming language. In this section, we
define the semantic model of Coneris and explain how to prove the soundness of the program logic.
7.1 Model
Weakest Precondition. The Coneris Hoare triple is defined in terms of a weakest precondition
predicate as follows:
{๐}๐{๐}โ(๐โwp๐{๐})
Expressing Hoare triples in term of a weakest precondition is standard for defining program
logics [Jung et al .2018], especially for other similar Iris non-probabilistic logics. The definition of the
weakest precondition is however novel, which we detail below. Note that the weakest precondition
is defined as a guarded fixed point: the recursive occurrences of the weakest precondition appear
under the later modality โฒon the last line.
wp๐1{ฮฆ}โโ๐1,๐1.๐(๐1,๐1)โ|โโค โ
sstep๐1๐1{๐2,๐2.
๐1โValโ |โโ
โค๐(๐2,๐2)โฮฆ(๐1)โจ
๐1โValโpstep(๐1,๐2)๐2{๐2,๐3,๐,๐ 3.
โฒsstep๐3๐3{๐4,๐4.|โโ
โค๐(๐4,๐4)โwp๐2{ฮฆ}โโ๐โฒโ๐wp๐โฒ{True}}}}
One can intuitively understand wp๐{ฮฆ}as a proposition that describes that ๐issafe, meaning it
does not get stuck, and that for every possible return value ๐ฃ, the postcondition ฮฆ(๐ฃ)holds.
We now explain the definition of the weakest precondition in detail. At the beginning, we assume
the ownership of a state interpretation ๐(๐1,๐1)for some state ๐1and error value ๐1. This state
interpretation ๐:StateโRโฅ0โiProp gives meaning to the ownership of references โโฆโ๐ฃ, tapes
๐
โฉโ(๐,ยฎ๐), and error credits E(๐). The resource algebra used to instantiate the state interpretation
is standard, and we refer the readers to Aguirre et al. [2024] for more details.
After that, we perform a view shift through a fancy update modality |โโค โ
, which intuitively
means we open all invariants temporarily and that we have access to the resources of all invari-
ants we defined previously. Following the view shift, we need to prove a state step precondition
sstep๐1๐1{...}. The exact definition of the state step precondition is explained later. For now,
we can think of the state step precondition as the modality that allows instantaneous probability-
preserving updates supported by the probabilistic update modality |โโ๐. Given state ๐1and error
budget๐1, we can perform any number of probability-preserving updates to step to resulting state
๐2and leftover error budget ๐2, which must satisfy the rest of the continuation.
The next part of the weakest precondition depends on a case split on the expression ๐1. In the
first case, where ๐1is a value, we do a view shift |โโ
โค where we re-establish allinvariants, return
the updated state interpretation and show that the return value ๐1satisfies the postcondition ฮฆ.
Otherwise, if ๐1is not a value, we have to prove a program step precondition pstep(๐1,๐2)๐2{...}.
We later explain the specifics of this precondition modality, but for now, one can loosely understand
the connective as somewhat similar to the state step precondition, where we take an actual step
on the configuration (๐1,๐2), instead of performing a probabilistic update on ๐2. After the single
step to resulting expression ๐2, state๐3, forking the list of expressions ๐with leftover error budget
๐3, we prove another state step precondition, which we can ignore here.5Finally, we re-establish
all invariants after the view shift |โโ
โค , return the state interpretation, show that wp๐2{ฮฆ}holds,
andwp๐โฒ{True}holds for all ๐โฒin the forked list ๐.
5This extra state step precondition is only used to validate certain invariant opening properties not discussed in this paper.
Page 23:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 23
state-step-err-1
1โค๐
sstep๐๐{ฮฆ}state-step-ret
ฮฆ(๐,๐)
sstep๐๐{ฮฆ}state-step-continuous
โ๐โฒ.๐<๐โฒโsstep๐๐โฒ{ฮฆ}
sstep๐๐{ฮฆ}
state-step-exp
E๐[F]โค๐ schErasable(๐,๐ 1) โ๐2.0<๐(๐2)โsstep๐2(F(๐2)){ฮฆ}
sstep๐1๐{ฮฆ}
Fig. 9. Inductive Definition of the State Step Precondition sstep ๐๐{ฮฆ}.
State and Program Step Preconditions. The state step precondition is defined inductively by
four inference rules presented in Figure 9. Firstly, if the error budget ๐is larger or equal to 1, the
precondition holds trivially as all sub-distributions have mass smaller or equal to 1(state-step-
err-1 ). If the predicate ฮฆholds for the current state and error budget, the precondition also holds
(state-step-ret ). The third rule state-step-continuous states that the precondition holds if for
error budget ๐โฒlarger than the ๐(in particular, ๐โฒcan be arbitrarily close to ๐), then the precondition
does in fact hold for ๐as well. This is the main rule that allows us to create error credit from thin
air (pupd-err ), letting us exploit the fact that the real numbers are complete at the level of Coneris.
Lastly, state-step-exp is the main interesting rule, which relies on the following auxiliary
definition. It relies on the following auxiliary definition.
Definition 7.1. A distribution on states ๐is ascheduler erasable state update of๐โState , written
asschErasable(๐,๐), if for all schedulers ๐, scheduler states ฮ, thread poolsยฎ๐, and any number of
execution steps ๐, we have
(๐โซ=(๐๐โฒ.pexec๐,๐(ฮ,(ยฎ๐,๐โฒ)))).tp=(pexec๐,๐(ฮ,(ยฎ๐,๐))).tp
where we writeโ.tpfor the function that projects out the thread pool component from a distribution
on configurations.
A distribution ๐is thus a scheduler erasable state update of ๐if the probability of executing ยฎ๐
from state๐to any particular list of threads is the same if we first update the state with respect
to๐and then executeยฎ๐. Recall that the operational semantics requires schedulers to be invariant
under changes to presampling tapes; such changes thus constitute scheduler erasable state updates.
The state-step-exp rule then states that if we can find a function F:Stateโ[ 0,1]and a
distribution ๐:D(State)such that (1) the expectation of Fwith respect to ๐is at most๐, (2)๐
is scheduler erasable with respect to ๐1, and (3) for all ๐2, the continuation sstep๐2(F(๐2)){ฮฆ}
holds, then sstep๐1๐{ฮฆ}holds. This is the rule that allows us to do presampling on tapes, since
the presampling action is a scheduler erasable operation.
The program step precondition is defined by a single inference rule prog-step-exp . It is similar
to that of state-step-exp , except that we take exactly one step of the configuration (๐1,๐1). In
detail, pstep(๐1,๐1)๐{ฮฆ}holds if the configuration (๐1,๐1)is reducible and there exists some
functionF:ExprรStateรList(Expr)โ[ 0,1]whose expectation with respect to step(๐1,๐1)is
smaller or equal to ๐, and for all(๐2,๐2,๐), the continuation ฮฆ(๐2,๐2,๐,F(๐2,๐2,๐))holds.
prog-step-exp
red(๐1,๐1)Estep(๐1,๐1)[F]โค๐
โ(๐2,๐2,๐).0<step(๐1,๐1)(๐2,๐2,๐)โฮฆ(๐2,๐2,๐,F(๐2,๐2,๐))
pstep(๐1,๐1)๐{ฮฆ}
Page 24:
24 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
Probabilistic Update Modality. Recall that the probabilistic update modality |โโE1E2๐depends
on two masksE1andE2. These extra mask parameters are used to track the opening of invariants
and prevent us from opening the same invariant twice (which is unsound). One can understand
|โโE1E2๐as that we can perform a probability-preserving update to get the resources ๐with the
possibility of accessing resources of invariants in the mask E1and reestablishing resources of
invariants in the mask E2in the end.
|โโE1E2๐โโ๐1,๐1.๐(๐1,๐1)โ|โE1โ
sstep๐1๐1{๐2,๐2.|โโ
E 2๐(๐2,๐2)โ๐}
The implementation of the probabilistic update modality somewhat resembles a simplified version
of the weakest precondition, where we only perform a single state step. Specifically, |โโE1E2๐
holds if after assuming some state interpretation ๐(๐1,๐1), we can open all invariants in E1through
the view shift|โE1โ
, and prove a state step precondition with the input parameters ๐1and๐1.
Given resulting state ๐2and error budget ๐2after the state step precondition, we re-establish all
invariants in the mask E2with the view shift |โโ
E 2and give back the state interpretation and
prove the resource ๐.
7.2 Soundness
The soundness of Coneris comes in two flavours, the correctness adequacy theorem Theorem 4.1
and the safety theorem Theorem 4.2. We now briefly describe the overall structure proof of the
correctness adequacy theorem; the proof of the safety theorem is similar and is omitted.
We first prove an intermediate lemma:
Lemma 7.2 (Adeqacy Simplified). IfE(๐)โขwp๐{๐}โโ๐โฒโยฎ๐โฒwp๐โฒ{True}, then for all sched-
ulers๐, states๐, and natural numbers ๐,Prexec ๐,๐(๐ยทยฎ๐โฒ,๐)[ยฌ๐]โค๐.
This lemma is proven by induction on ๐and structural induction on the state step precondition
fixed point. For each step, we unfold the definition of exec to determine which thread the scheduler
chooses to step next. We unfold the definition of the corresponding weakest precondition proposi-
tion (the one that matches the thread chosen to step), and show that the sstep andpstep modalities
satisfies monadic composition, allowing us to compose the errors. Finally by taking ยฎ๐โฒto be the
empty list of threads, and taking the limit of ๐, we are able to show Prexec ๐๐[ยฌ๐]โค๐, which is the
goal of the adequacy theorem.
8 Related Work
Approximate Reasoning. There are various approaches for tracking error probabilities in
probabilistic programs. The approximate Hoare logic [Barthe et al .2016b] uses a grading on
Hoare triples to approximate error probabilities. Expectation-based logics such as that of Batz et al .
[2019]; Morgan et al .[1996] are defined with a weakest-precondition-style quantitative predicate
transformer that computes the expected value of a programโs postcondition, which can be used to
derive approximate correctness bounds. Compared to our work, these logics are usually restricted
to sequential, first-order imperative programs. Our method of using error credits to track error
bounds is first used in Eris [Aguirre et al .2024] to prove error bounds of sequential higher-order
probabilistic programs.
Various other logics also considered reasoning about approximate correctness in the relational
setting. The logic apRHL [Barthe et al .2016a, 2013] relates the probability distribution of two
programs through approximate probabilistic couplings, which can then be used to prove differential
privacy. Also inspired by Eris, error credits are used in the relational logic Approxis [Haselwarter
et al. 2025] to prove approximate equivalences of higher-order programs.
Page 25:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 25
Concurrent Probabilistic Program Logics. One of the first program logics developed for
concurrent probabilistic programs is the probabilistic rely-guarantee calculus [McIver et al .2016]
(that extends the rely-guarantee logic [Jones 1983]) that verifies the quantitative correctness of a
probabilistic concurrent programs without local state. Later, Concurrent Quantitative Separation
Logic [Fesefeldt et al .2022] extends Quantitative Separation Logic [Batz et al .2019] to reason about
the lower bounds of probability to realize the postcondition of concurrent, heap-manipulating,
randomized imperative programs. Compared to our work, it cannot establish strict error bounds
that arise between the interleaving of threads (see the conTwoAdd example in ยง2) and cannot reason
about programs in a (procedure-)modular way.
The relational logic Polaris [Tassarotti and Harper 2019] establishes refinements between con-
current probabilistic programs and a monadic representation via probabilistic couplings inspired
by the pRHL logic [Barthe et al .2017a,b, 2012]. The simpler monadic model can then be studied to
derive properties of the original programs, such as bounds on its expected value. However, Polaris
does not demonstrate how to compose refinements of different data structures and did not develop
an approach for reasoning about logical atomicity.
Lohse and Garg [2024] developed ExpIris, a variant of Iris that supports establishing bounds on
the expected cost of concurrent higher-order programs with mutable state. In ExpIris, an upper
bound budget on the number of steps a program can take is written as an additional parameter of
weakest preconditions, called a potential . On randomized steps, this potential can be updated in an
expectation-preserving way, similar to ht-rand-exp . However, because potentials are a parameter
of the weakest precondition, instead of a separation logic resource like error credits, there is no
way to share them in an invariant, as we saw was necessary for obtaining tight analyzes in ยง2.
Recently, Probabilistic Concurrent Outcome Logic [Zilberstein et al .2024] extends Demonic
Outcome Logic [Zilberstein et al .2025] to reason about the distributions of outcomes from con-
current probabilistic programs. Although this logic is able to prove other probabilistic properties
beyond the scope of Coneris such as independence and conditioning, the programs considered are
restricted to those without dynamically allocated state or higher-order functions, and the logic
does not support defining ghost state.
Internalization of Linearizability. There is a long line of research on internalizing lineariz-
ability as a reasoning principle within concurrent program logic specifications. Jacobs and Piessens
[2011] first extended the resource-invariants-based method from Owicki and Gries [Owicki and
Gries 1976] allowing users to parameterize the specification of concurrent functions with ghost
code. Later, Svendsen et al .[2013] further extended their idea and proposed a new style of speci-
fication using higher-order concurrent abstract predicates (HOCAP) (which was built on top of
CAP [Dinsdale-Young et al .2010]). At around the same time, da Rocha Pinto et al .[2014] introduced
a different logic called TaDa, which proposed the use of atomic triples to capture logical atomicity of
programs. There has also been much research in encoding logically atomic specifications within the
Iris separation logic [Jung et al .2019, 2015]. In our work of Coneris, we take inspiration from these
logics, especially HOCAP, to capture randomized logical atomicity within probabilistic concurrent
programs.
9 Conclusion
We presented Coneris, the first concurrent and probabilistic higher-order separation logic for error
bound reasoning. Coneris captures randomized logical atomicity through the novel probabilistic
update modality, enabling modular verification of concurrent programs that is out-of-scope for
previous techniques. We demonstrated the flexibility of Coneris by verifying various examples
Page 26:
26 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
modularly, most of which involve local state and intricate reasoning over randomness that arise
from concurrency.
There are various directions for extending Coneris. Firstly, we would like to extend Coneris to
enable verifying strict error bounds of concurrent probabilistic programs under restricted schedulers,
such as those that cannot view the configuration of the program. It is also interesting to explore
whether ideas from Approxis [Haselwarter et al .2025] can be used to extend Coneris into the
relational setting to establish approximate bounds between concurrent probabilistic programs.
Lastly, we would like to consider integrating cost credits from Tachis [Haselwarter et al .2024] into
Coneris to reason about both the expected work and span time costs of concurrent probabilistic
programs.
Acknowledgments
The first author would like to thank Amin Timany for enlightening discussions regarding HOCAP-
style specifications.
This work was supported in part by the National Science Foundation, grant no. 2338317, the
Carlsberg Foundation, grant no. CF23-0791, a Villum Investigator grant, no. 25804, Center for Basic
Research in Program Verification (CPV), from the VILLUM Foundation, and the European Union
(ERC, CHORDS, 101096090). Views and opinions expressed are however those of the author(s) only
and do not necessarily reflect those of the European Union or the European Research Council.
Neither the European Union nor the granting authority can be held responsible for them.
References
M. Abadi and L. Lamport. 1988. The existence of refinement mappings. In [1988] Proceedings. Third Annual Symposium on
Logic in Computer Science . 165โ175. https://doi.org/10.1109/LICS.1988.5115
Martรญn Abadi and Leslie Lamport. 1991. The existence of refinement mappings. Theoretical Computer Science 82, 2 (1991),
253โ284. https://doi.org/10.1016/0304-3975(91)90224-P
Alejandro Aguirre, Philipp G. Haselwarter, Markus de Medeiros, Kwing Hei Li, Simon Oddershede Gregersen, Joseph Tas-
sarotti, and Lars Birkedal. 2024. Error Credits: Resourceful Reasoning about Error Bounds for Higher-Order Probabilistic
Programs. Proc. ACM Program. Lang. 8, ICFP, Article 246 (Aug. 2024), 33 pages. https://doi.org/10.1145/3674635
Gilles Barthe, Thomas Espitau, Benjamin Grรฉgoire, Justin Hsu, and Pierre-Yves Strub. 2017a. Proving uniformity and
independence by self-composition and coupling. In LPAR-21. 21st International Conference on Logic for Programming,
Artificial Intelligence and Reasoning (EPiC Series in Computing, Vol. 46) , Thomas Eiter and David Sands (Eds.). EasyChair,
385โ403. https://doi.org/10.29007/vz48
Gilles Barthe, Noรฉmie Fong, Marco Gaboardi, Benjamin Grรฉgoire, Justin Hsu, and Pierre-Yves Strub. 2016a. Advanced
Probabilistic Couplings for Differential Privacy. In Proceedings of the 2016 ACM SIGSAC Conference on Computer and
Communications Security (Vienna, Austria) (CCS โ16) . Association for Computing Machinery, New York, NY, USA, 55โ67.
https://doi.org/10.1145/2976749.2978391
Gilles Barthe, Marco Gaboardi, Benjamin Grรฉgoire, Justin Hsu, and Pierre-Yves Strub. 2016b. A Program Logic for Union
Bounds. In 43rd International Colloquium on Automata, Languages, and Programming (ICALP 2016) (Leibniz International
Proceedings in Informatics (LIPIcs), Vol. 55) , Ioannis Chatzigiannakis, Michael Mitzenmacher, Yuval Rabani, and Davide
Sangiorgi (Eds.). Schloss Dagstuhl โ Leibniz-Zentrum fรผr Informatik, Dagstuhl, Germany, 107:1โ107:15. https://doi.org/
10.4230/LIPIcs.ICALP.2016.107
Gilles Barthe, Benjamin Grรฉgoire, Justin Hsu, and Pierre-Yves Strub. 2017b. Coupling proofs are probabilistic product
programs. In Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages (Paris, France)
(POPL โ17) . Association for Computing Machinery, New York, NY, USA, 161โ174. https://doi.org/10.1145/3009837.3009896
Gilles Barthe, Benjamin Grรฉgoire, and Santiago Zanella Bรฉguelin. 2012. Probabilistic Relational Hoare Logics for Computer-
Aided Security Proofs. In Mathematics of Program Construction , Jeremy Gibbons and Pablo Nogueira (Eds.). Springer
Berlin Heidelberg, Berlin, Heidelberg, 1โ6.
Gilles Barthe, Boris Kรถpf, Federico Olmedo, and Santiago Zanella-Bรฉguelin. 2013. Probabilistic Relational Reasoning for
Differential Privacy. ACM Trans. Program. Lang. Syst. 35, 3, Article 9 (Nov. 2013), 49 pages. https://doi.org/10.1145/2492061
Kevin Batz, Benjamin Lucien Kaminski, Joost-Pieter Katoen, Christoph Matheja, and Thomas Noll. 2019. Quantitative
separation logic: a logic for reasoning about probabilistic pointer programs. Proc. ACM Program. Lang. 3, POPL, Article
34 (Jan. 2019), 29 pages. https://doi.org/10.1145/3290347
Page 27:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 27
Mihir Bellare and Phillip Rogaway. 1993. Random oracles are practical: a paradigm for designing efficient protocols. In
Proceedings of the 1st ACM Conference on Computer and Communications Security (Fairfax, Virginia, USA) (CCS โ93) .
Association for Computing Machinery, New York, NY, USA, 62โ73. https://doi.org/10.1145/168588.168596
Yves Bertot and Pierre Castรฉran. 2013. Interactive theorem proving and program development: CoqโArt: the calculus of inductive
constructions . Springer Science & Business Media.
Burton H. Bloom. 1970. Space/Time Trade-offs in Hash Coding with Allowable Errors. Commun. ACM 13, 7 (1970), 422โ426.
https://doi.org/10.1145/362686.362692
Sylvie Boldo, Catherine Lelay, and Guillaume Melquiond. 2013. Coquelicot: A User-Friendly Library of Real Analysis for
Coq. (Sept. 2013). https://inria.hal.science/hal-00860648 working paper or preprint.
Prosenjit Bose, Hua Guo, Evangelos Kranakis, Anil Maheshwari, Pat Morin, Jason Morrison, Michiel H. M. Smid, and Yihui
Tang. 2008. On the false-positive rate of Bloom filters. Inf. Process. Lett. 108, 4 (2008), 210โ213. https://doi.org/10.1016/J.
IPL.2008.05.018
Pedro da Rocha Pinto, Thomas Dinsdale-Young, and Philippa Gardner. 2014. TaDA: A Logic for Time and Data Abstraction.
InECOOP 2014 โ Object-Oriented Programming , Richard Jones (Ed.). Springer Berlin Heidelberg, Berlin, Heidelberg,
207โ231.
Thomas Dinsdale-Young, Mike Dodds, Philippa Gardner, Matthew J. Parkinson, and Viktor Vafeiadis. 2010. Concurrent
Abstract Predicates. In ECOOP 2010 โ Object-Oriented Programming , Theo DโHondt (Ed.). Springer Berlin Heidelberg,
Berlin, Heidelberg, 504โ528.
Ira Fesefeldt, Joost-Pieter Katoen, and Thomas Noll. 2022. Towards Concurrent Quantitative Separation Logic. In 33rd
International Conference on Concurrency Theory (CONCUR 2022) (Leibniz International Proceedings in Informatics (LIPIcs),
Vol. 243) , Bartek Klin, Sลawomir Lasota, and Anca Muscholl (Eds.). Schloss Dagstuhl โ Leibniz-Zentrum fรผr Informatik,
Dagstuhl, Germany, 25:1โ25:24. https://doi.org/10.4230/LIPIcs.CONCUR.2022.25
Wojciech M. Golab, Lisa Higham, and Philipp Woelfel. 2011. Linearizable implementations do not suffice for randomized
distributed computation. In Proceedings of the 43rd ACM Symposium on Theory of Computing, STOC 2011, San Jose, CA,
USA, 6-8 June 2011 , Lance Fortnow and Salil P. Vadhan (Eds.). ACM, 373โ382. https://doi.org/10.1145/1993636.1993687
Kiran Gopinathan and Ilya Sergey. 2020. Certifying Certainty and Uncertainty in Approximate Membership Query
Structures. In Computer Aided Verification - 32nd International Conference, CAV 2020, Los Angeles, CA, USA, July 21-24,
2020, Proceedings, Part II (Lecture Notes in Computer Science, Vol. 12225) , Shuvendu K. Lahiri and Chao Wang (Eds.).
Springer, 279โ303. https://doi.org/10.1007/978-3-030-53291-8_16
Simon Oddershede Gregersen, Alejandro Aguirre, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal. 2024.
Asynchronous Probabilistic Couplings in Higher-Order Separation Logic. Proc. ACM Program. Lang. 8, POPL, Article 26
(Jan. 2024), 32 pages. https://doi.org/10.1145/3632868
Philipp G. Haselwarter, Kwing Hei Li, Alejandro Aguirre, Simon Oddershede Gregersen, Joseph Tassarotti, and Lars Birkedal.
2025. Approximate Relational Reasoning for Higher-Order Probabilistic Programs. Proc. ACM Program. Lang. 9, POPL,
Article 41 (Jan. 2025), 31 pages. https://doi.org/10.1145/3704877
Philipp G. Haselwarter, Kwing Hei Li, Markus de Medeiros, Simon Oddershede Gregersen, Alejandro Aguirre, Joseph
Tassarotti, and Lars Birkedal. 2024. Tachis: Higher-Order Separation Logic with Credits for Expected Costs. Proc. ACM
Program. Lang. 8, OOPSLA2, Article 313 (Oct. 2024), 30 pages. https://doi.org/10.1145/3689753
Bart Jacobs and Frank Piessens. 2011. Expressive modular fine-grained concurrency specification. SIGPLAN Not. 46, 1 (Jan.
2011), 271โ282. https://doi.org/10.1145/1925844.1926417
Cliff Jones. 1983. Specification and Design of (Parallel) Programs. Proceedings Of Ifip Congress โ 83, 321โ332.
Ralf Jung, Robbert Krebbers, Jacques-Henri Jourdan, Ales Bizjak, Lars Birkedal, and Derek Dreyer. 2018. Iris from the
ground up: A modular foundation for higher-order concurrent separation logic. J. Funct. Program. 28 (2018), e20.
https://doi.org/10.1017/S0956796818000151
Ralf Jung, Rodolphe Lepigre, Gaurav Parthasarathy, Marianna Rapoport, Amin Timany, Derek Dreyer, and Bart Jacobs. 2019.
The future is ours: prophecy variables in separation logic. Proc. ACM Program. Lang. 4, POPL, Article 45 (Dec. 2019),
32 pages. https://doi.org/10.1145/3371113
Ralf Jung, David Swasey, Filip Sieczkowski, Kasper Svendsen, Aaron Turon, Lars Birkedal, and Derek Dreyer. 2015. Iris:
Monoids and Invariants as an Orthogonal Basis for Concurrent Reasoning. In Proceedings of the 42nd Annual ACM
SIGPLAN-SIGACT Symposium on Principles of Programming Languages, POPL 2015, Mumbai, India, January 15-17, 2015 .
637โ650. https://doi.org/10.1145/2676726.2676980
Janine Lohse and Deepak Garg. 2024. An Iris for Expected Cost Analysis. arXiv:2406.00884 [cs.PL]
Annabelle McIver, Tahiry Rabehaja, and Georg Struth. 2016. Probabilistic rely-guarantee calculus. Theoretical Computer
Science 655 (2016), 120โ134. https://doi.org/10.1016/j.tcs.2016.01.016 Quantitative Aspects of Programming Languages
and Systems (2013-14).
Arno Mittelbach and Marc Fischlin. 2021. The Theory of Hash Functions and Random Oracles - An Approach to Modern
Cryptography . Springer. https://doi.org/10.1007/978-3-030-63287-8
Page 28:
28 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
Carroll Morgan, Annabelle McIver, and Karen Seidel. 1996. Probabilistic predicate transformers. ACM Trans. Program. Lang.
Syst. 18, 3 (May 1996), 325โ353. https://doi.org/10.1145/229542.229547
Susan Owicki and David Gries. 1976. Verifying properties of parallel programs: an axiomatic approach. Commun. ACM 19,
5 (May 1976), 279โ285. https://doi.org/10.1145/360051.360224
Peter W. OโHearn. 2007. Resources, concurrency, and local reasoning. Theoretical Computer Science 375, 1 (2007), 271โ307.
https://doi.org/10.1016/j.tcs.2006.12.035 Festschrift for John C. Reynoldsโs 70th birthday.
Kasper Svendsen, Lars Birkedal, and Matthew Parkinson. 2013. Modular Reasoning about Separation of Concurrent Data
Structures. In Programming Languages and Systems , Matthias Felleisen and Philippa Gardner (Eds.). Springer Berlin
Heidelberg, Berlin, Heidelberg, 169โ188.
Joseph Tassarotti and Robert Harper. 2019. A separation logic for concurrent randomized programs. Proc. ACM Program.
Lang. 3, POPL, Article 64 (Jan. 2019), 30 pages. https://doi.org/10.1145/3290377
Noam Zilberstein, Dexter Kozen, Alexandra Silva, and Joseph Tassarotti. 2025. A Demonic Outcome Logic for Randomized
Nondeterminism. Proc. ACM Program. Lang. 9, POPL, Article 19 (Jan. 2025), 30 pages. https://doi.org/10.1145/3704855
Noam Zilberstein, Alexandra Silva, and Joseph Tassarotti. 2024. Probabilistic Concurrent Reasoning in Outcome Logic:
Independence, Conditioning, and Invariants. arXiv:2411.11662 [cs.LO] https://arxiv.org/abs/2411.11662
Page 29:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 29
A Modular Proof of conTwoAdd
In this section, we show in more detail how to prove conTwoAdd with the HOCAP-style specifica-
tions of the randomized counter module (see Figure 5).
Before we proceed, we present a selection of side conditions of the abstract predicates in Figure 10
which we previously omitted in Figure 5. The first side condition expresses that the counter
representation predicate is persistent, which means that it is duplicable so that clients can share
it among several threads. We then have a series of side conditions regarding the cauth andcfrag
abstract predicates, which are used to keep track of the abstract state of the counter. The first
condition states that cfrag abstract predicates can be combined by adding their arguments together.
The next condition states that if we hold both the cauth andcfrag resource and the fraction of the
cfrag is exactly 1, the value from both predicates agree. The last side condition describes how we
can update the abstract state of a counter: if we have a cauth and a cfrag predicate with the same
ghost name, we can update the predicates by incrementing the values of both by a constant ๐ฅ.
๐ถ๐๐พ๐โ๐ถ๐๐พ๐
cfrag๐พ ๐ ๐งโcfrag๐พ ๐โฒ๐งโฒโcfrag๐พ(๐+๐โฒ)(๐ง+๐งโฒ)
cauth๐พ๐งโcfrag๐พ1๐งโฒโ๐งโฒ=๐ง
cauth๐พ๐งโcfrag๐พ ๐ ๐งโฒโ|โcauth๐พ(๐ง+๐ฅ)โcfrag๐พ ๐(๐งโฒ+๐ฅ)
Fig. 10. Selection of Side Conditions on Abstract Predicates
Recall that since the new specification of the randomized concurrent counter utilizes tapes, the
conTwoAdd client is annotated to use the abstract tapes.
conTwoAddโlet๐=createCntr()in
let๐
=createCtape()in
incrCntr๐๐
|||let๐
=createCtape()in
incrCntr๐๐
;
readCntr๐
We now prove that the return value is 0with a probability of 1/16, with the Coneris Hoare triple:
{E(1/16)}conTwoAdd{๐ฃ.๐ฃ>0}.
We first consider the invariant used to track the change in shared state during the parallel
composition. We use two states ๐0and๐1(๐)(of some inductive type ๐) to track the state of the
threads, with ๐0representing the state where the thread has not sampled a value yet and ๐1(๐)
representing it sampled ๐. Note that we do not need an additional state to track whether the sampled
value has been added into the counter, because that can be tracked by the resource cfrag . We use
the invariant ๐ผshown below to capture the shared state of the two threads. Notice that the invariant
๐ผmakes use of the exclusive-authoritative ghost resource algebra, which consists of the authoritative
partโข๐ฅand the fragment partโฆ๐ฅ. We omit the definition and properties of this resource and we
Page 30:
30 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
refer readers to Jung et al. [2018] for more information.
sampled๐ โmatch๐ with๐0โNone|๐1(๐)โ Some๐end
onePositive ๐ 1๐ 2โโ๐.๐>0โง(sampled๐ 1=Some๐โจsampled๐ 2=Some๐)
๐ผ(๐พ1,๐พ2)โโ(๐ 1๐ 2:๐).โข๐ 1๐พ1โโข๐ 2๐พ2โ
if onePositive ๐ 1๐ 2then E(0)
else E
4(bool_to_nat(sampled๐ 1=Some 0)+bool_to_nat(sampled๐2=Some 0)โ2)
We now show how to prove the specification of conTwoAdd using the invariant previously defined.
After stepping through the code up until the parallel composition component, and allocating the
necessary resources and invariant, we arrive at the following proof obligation:
(
๐ถ๐๐พ๐โcfrag๐พ1 0โ๐ผ(๐พ1,๐พ2)๐โฒ
โ
โฆ๐0๐พ1โโฆ๐0๐พ2)
let...|||let...;
readCntr๐{๐ฃ.๐ฃ>0}
We can apply the side condition of cfrag to split it between the two threads and apply the rule for
parallel composition which leaves us with the following three obligations:
n
๐ถ๐๐พ๐โ๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โโฆ๐0๐พ1o
let...
โ๐.cfrag๐พ0.5๐โโฆ๐1(๐)๐พ1
(10)
n
๐ถ๐๐พ๐โ๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โโฆ๐0๐พ2o
let...
โ๐.cfrag๐พ0.5๐โโฆ๐2(๐)๐พ1
(11)
(
๐ถ๐๐พ๐โcfrag๐พ1(๐1+๐2)โ๐ผ(๐พ1,๐พ2)๐โฒ
โ
โฆ๐1(๐1)๐พ1โโฆ๐1(๐2)๐พ2)
readCntr๐{๐ฃ.0<๐ฃ} (12)
Let us first focus on Equation (10). We first apply the specification for createCtape to create an
empty tape resource and we arrive at the following obligation.
(
๐ถ๐๐พ๐โ๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โ
โฆ๐0๐พ1โctape๐
๐)
incrCntr๐๐
โ๐.cfrag๐พ0.5๐โโฆ๐1(๐)๐พ1
Now that we have a ctape predicate on our hands, we can presample a value onto it so that it can
be used for the incrCntr method later. Specifically, we perform the following probabilistic update:
๐ผ(๐พ1,๐พ2)๐โฒ
โโฆ๐0๐พ1โctape๐
๐โ|โโโคโ๐.โฆ๐1(๐)๐พ1โctape๐
[๐]
This probabilistic update proposition is proven by first applying the probabilistic update modality
version of inv-open where we access the resources within the invariant, and subsequently updating
the authoritative resource pairs from the state ๐0to๐1(๐)to track the value ๐presampled onto the
tape. After this probabilistic update, we are left with the following obligation:
(
๐ถ๐๐พ๐โ๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โ
โฆ๐1(๐)๐พ1โctape๐
[๐])
incrCntr๐๐
โ๐.cfrag๐พ0.5๐โโฆ๐1(๐)๐พ1
The rest of the proof then follows almost directly by applying the new specification for incrCntr
and choosing ๐๐งโcfrag๐พ0.5๐. The second obligation (Equation (11)), representing the behavior
of the second thread, is proven in an almost identical fashion.
Let us now focus on the last obligation (Equation (12)). To prove that the return value is positive,
we apply the specification of readCntr , choosing๐๐ฃโ๐ฃ>0, leaving us with the following view
Page 31:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 31
shift obligation for the precondition:
๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ1(๐1+๐2)โโฆ๐1(๐1)๐พ1โโฆ๐1(๐2)๐พ2โ
(โ๐ง.cauth๐พ๐งโ|โโค\๐cauth๐พ๐งโ๐ง>0)
We do a case split on the values of ๐1and๐2. If they are both 0, we can open the invariant ๐ผ(๐พ1,๐พ2)
to access a E(1)error credit to derive a contradiction with err-1 . Otherwise, using the rules for
cauth andcfrag , we can show that the values in the cauth andcfrag predicates coincide, i.e. they
are both๐1+๐2and must be positive, which completes the proof.
B HOCAP-style Specification with Error Redistribution
In ยง5.1, we presented a HOCAP-style specification that does not expose presampling tapes as
an abstract predicate (see Figure 4). Although in ยง5.2 we showed that it is less general than the
specification with ctape shown in Figure 5, in this section, we briefly explain how to use the
specification to prove clients of the module and how to show various implementations meet the
specification.
B.1 Implementation
We first show three possible implementations of the module that mirror those shown in ยง5.3.
For๐ผ1, we do not need to allocate any tapes and we sample from rand 3directly. However, note
incrCntr 1โ๐๐.faa๐(rand 3)
incrCntr 2โ๐๐.let๐
=tape 1in
faa๐(rand๐
1โ2+rand๐
1)
incrCntr 3โ๐๐.let๐
=tape 4in
(rec๐ ๐
=
let๐ฅ=rand๐
4in
if๐ฅ<4then faa๐๐ฅelse๐ ๐
)๐
Fig. 11. Three Implementations of Increment
that for๐ผ2and๐ผ3, we have to create a tape internally and sample from it. This is because the
randomization within the two implementations occurs over various steps even if it acts โlogically
atomicโ. By adding extra ghost code that utilizes tapes, we are able to reason about the randomness
asynchronously, which we demonstrate in later subsections.
B.2 Verification of Client of HOCAP-style Specification with Error Redistribution
We now verify the conTwoAdd example in ยง5.2 with the specification from Figure 4. Since tapes are
not exposed in this specification, conTwoAdd is written without explicit allocation of ctape .
conTwoAddโlet๐=createCntr()in
(incrCntr๐|||incrCntr๐);
readCntr๐
As before, we want to verify that the final read value is positive, with error probability 1/16,
which we show with the following Coneris Hoare triple.
{E(1/16)}conTwoAdd{๐ฃ.๐ฃ>0}
Page 32:
32 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
In fact the proof works almost identically to that presented in ยง5.2. For example, the states and
invariants used to track the shared state of the two parallel threads are identical to the ones used
before.
We begin by stepping through the code up until the parallel composition component, and after
allocating the necessary resources and invariant, we arrive at the following proof obligation:
n
๐ถ๐๐พ๐โcfrag๐พ1 0โ๐ผ(๐พ1,๐พ2)๐โฒ
โโฆ๐0๐พ1โโฆ๐0๐พ2o
incrCntr๐|||incrCntr๐;
readCntr๐
{๐ฃ.๐ฃ>0}
We can apply the side condition of cfrag to split it between the two threads and apply the rule for
parallel composition, leaving us with the following three obligations:
n
๐ถ๐๐พ๐โ๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โโฆ๐0๐พ1o
incrCntr๐
โ๐.cfrag๐พ0.5๐โโฆ๐1(๐)๐พ1
(13)
n
๐ถ๐๐พ๐โ๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โโฆ๐0๐พ2o
incrCntr๐
โ๐.cfrag๐พ0.5๐โโฆ๐2(๐)๐พ1
(14)
n
๐ถ๐๐พ๐โcfrag๐พ1(๐1+๐2)โ๐ผ(๐พ1,๐พ2)๐โฒ
โโฆ๐1(๐1)๐พ1โโฆ๐1(๐2)๐พ2o
readCntr๐{๐ฃ.0<๐ฃ}(15)
We focus only on the first obligation; the second obligation follows similarly and the third obligation
is similar to the proof of Equation (12) in ยง5.2. From Equation (13), we apply the specification of
incrCntr directly, choosing ๐ ๐F๐๐งโcfrag๐พ0.5๐โโฆ๐1(๐)๐พ1. It then suffices to prove the
following view shift for the precondition of the specification:
๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โโฆ๐0๐พ1โ
|โE โ
โ๐F.E(๐)โ(E๐3[F]โค๐)โ
(โ๐ฅ.0โค๐ฅ<4โE(F(๐ฅ))โ|โโ
E
(โ๐ง.cauth๐พ๐งโ|โE\๐cauth๐พ(๐ง+๐ฅ)โcfrag๐พ0.5๐ฅโโฆ๐1(๐ฅ)๐พ1))
We first open our invariant ๐ผ(๐พ1,๐พ2)while stripping away the |โE โ
mask, which allows us to access
the error credit stored in the invariant. After choosing the right Fbased on a case analysis on the
state of the right thread (which we omit for brevity), we update the authoritative resource pairs
fromโข๐0๐พ1โโฆ๐0๐พ1toโข๐1(๐ฅ)๐พ1โโฆ๐1(๐ฅ)๐พ1and re-establish the invariant ๐ผwhile removing the
|โโ
E mask, leaving us with the following state:
๐ผ(๐พ1,๐พ2)๐โฒ
โcfrag๐พ0.5 0โโฆ๐1(๐ฅ)๐พ1โ
cauth๐พ๐งโ|โE\๐cauth๐พ(๐ง+๐ฅ)โcfrag๐พ0.5๐ฅโโฆ๐1(๐ฅ)๐พ1
After incrementing both the cauth andcfrag components by exactly ๐ฅthrough the|โE\๐mask
(which is possible by the side conditions of cauth andcfrag ), we can then directly establish the final
goal.
B.3 Proving that ๐ผ1,๐ผ2, and๐ผ3Satisfy the HOCAP-style Specification with Error
Redistribution
We now briefly describe how each of the three randomized counter implementations meets the
specification with error redistribution in Figure 4. The concrete definitions for the abstract predicates
are actually identical to those used in the proof of ยง5.2. For example, the counter predicate is still
defined as
โ(๐:Loc)(๐:nat).๐=๐โ๐โฆโ๐โcauth๐พ๐๐
Page 33:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 33
It then suffices to show that the functions createCntr,incrCntr , and readCntr satisfy the HOCAP-
style specification. We focus on the incrCntr specification since it is the most complicated; the other
two functions can be verified in a similar, if not easier, fashion.
For๐ผ1, after symbolically stepping through the program, we are left with the following obligation:
๏ฃฑ๏ฃด๏ฃด ๏ฃฒ
๏ฃด๏ฃด๏ฃณ๐ถ๐๐พ๐โ( |โE โ
โ๐F.E(๐)โ(E๐3[F]โค๐)โ
(โ๐ฅ.0โค๐ฅ<4โE(F(๐ฅ))โ|โโ
E
(โ๐ง.cauth๐พ๐งโ|โE\๐cauth๐พ(๐ง+๐ฅ)โ๐๐F๐ฅ๐ง)))๏ฃผ๏ฃด๏ฃด ๏ฃฝ
๏ฃด๏ฃด๏ฃพ
faa๐(rand 3){๐ง.โ๐F๐ฅ.๐๐F๐ฅ๐ง}E
We first open the |โE โ
mask around the atomic rand 3operation. After opening the first view shift,
we are given some E(๐)and someFsuch that the expected sum of Fis smaller than ๐. We then
apply ht-rand-exp to distribute the errors across the various results and close the |โโ
E mask,
leaving us with the following obligation:
๐ถ๐๐พ๐โ(โ๐ง.cauth๐พ๐งโ|โE\๐cauth๐พ(๐ง+๐ฅ)โ๐๐F๐ฅ๐ง)
faa๐๐ฅ{๐ง.โ๐F๐ฅ.๐๐F๐ฅ๐ง}E
Since the faaoperation is atomic, we can open the invariant ๐ถaround the expression, resulting in
this goal:
๐โฆโ๐โcauth๐พ๐โ(โ๐ง. ...)
faa๐๐ฅ{๐ง.โ(๐:nat).๐โฆโ๐โcauth๐พ๐โโ๐F๐ฅ.๐๐F๐ฅ๐ง}E\๐
The rest of the proof follows nicely from the proof rule for the faaoperation, completing the proof.
For๐ผ2, we similarly step through the program, where we additionally allocate a tape ๐
, and thus
we arrive at the following goal:
๏ฃฑ๏ฃด๏ฃด๏ฃด๏ฃด ๏ฃฒ
๏ฃด๏ฃด๏ฃด๏ฃด๏ฃณ๐ถ๐๐พ๐โ๐
โฉโ(1,๐)โ( |โE โ
โ๐F.
E(๐)โ(E๐3[F]โค๐)โ
(โ๐ฅ.0โค๐ฅ<4โE(F(๐ฅ))โ|โโ
E
(โ๐ง.cauth๐พ๐งโ|โE\๐cauth๐พ(๐ง+๐ฅ)โ๐๐F๐ฅ๐ง)))๏ฃผ๏ฃด๏ฃด๏ฃด๏ฃด ๏ฃฝ
๏ฃด๏ฃด๏ฃด๏ฃด๏ฃพ
faa๐(rand๐
1โ2+rand๐
1){๐ง.โ๐F๐ฅ.๐๐F๐ฅ๐ง}E
From here, we directly open the |โE โ
and access the E(๐)error credit. Then, unlike what we did
for๐ผ1, here we perform a probabilistic update where we presample two values ๐ฃ1,๐ฃ2onto the tape
๐
, and we distribute E(๐ฃ1โ2+๐ฃ2)for each branch, i.e., we update the resources via the following
lemma (in this instance, ยฎ๐is instantiated to be the empty tape list ๐). This follows from Equation (7)
which we proved previously.
After closing the|โโ
E mask, we are left with the following obligation:
๐ถ๐๐พ๐โ๐
โฉโ(1,[๐ฃ1,๐ฃ2])โ
(โ๐ง.cauth๐พ๐งโ|โE\๐cauth๐พ(๐ง+๐ฃ1โ2+๐ฃ2)โ๐๐F(๐ฃ1โ2+๐ฃ2)๐ง)
faa๐(rand๐
1โ2+rand๐
1){๐ง.โ๐F๐ฅ.๐๐F๐ฅ๐ง}E
Page 34:
34 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
ยฉยญยญยญยญยญยญยญยญยญ
ยซโ๐F๐E๐
๐พยฎ๐.
(E๐๐[F]โค๐)โ
๐โEโ
isRand๐๐พโ
randTape๐
ยฎ๐๐พโ
E(๐)โ
|โโEโ๐.E(F(๐))โrandTape๐
(ยฎ๐ยท[๐])๐พยชยฎยฎยฎยฎยฎยฎยฎยฎยฎ
ยฌ
(a) Presampling specificationโ๐๐พ๐
๐ยฎ๐E.
๐โEโ isRand๐๐พโctape๐
(๐ยทยฎ๐)
randf๐
{๐ง.๐ง=๐โrandTape๐
ยฎ๐}E
(b)randf specification
Fig. 12. Selection of Specification of Rand Module
We can then read the values of the tape directly for both samples:
๐ถ๐๐พ๐โ๐
โฉโ(1,๐)โ
(โ๐ง.cauth๐พ๐งโ|โE\๐cauth๐พ(๐ง+๐ฃ1โ2+๐ฃ2)โ๐๐F(๐ฃ1โ2+๐ฃ2)๐ง)
faa๐(๐ฃ1โ2+๐ฃ2){๐ง.โ๐F๐ฅ.๐๐F๐ฅ๐ง}E
From here, the fetch-and-atomic-add step is similar to that for the ๐ผ1implementation.
The proof of ๐ผ3is very similar to that of ๐ผ2, except for the presampling step after allocating the
tape resource. In particular we want to show that we can repeatedly presample enough values
into the tape such that the last element is smaller than 4and all values beforehand are 4, while
distributing the error credit according to the final value. Here we use Equation (8) proved previously
to do so.
After performing the probabilistic update on the tape (such that it contains an โacceptedโ value
at the end), we can then step through the rest of the program, looping repeatedly until we reach
the final โacceptedโ value and establish the postcondition.
C Other Case Studies
C.1 Rand Module
For the random counter module introduced previously in ยง5.1, we identified three distinct imple-
mentations (ยง5.3) that sample randomness from a uniform distribution for the incrCntr operation,
e.g. we can directly call a single rand (๐ผ1), chain various rand s together ( ๐ผ2), or use a rejection
sampler method where we repeatedly sample until we obtain a desirable value ( ๐ผ3). We now define a
general interface, which we refer to as the Rand module , that captures what it means to sample from
a uniform distribution atomically, and show that several implementations satisfy it. In later case
studies, we use this interface to verify larger programs, to highlight the usability of this module
and to demonstrate modular reasoning.
The Rand module is parameterized by a natural number ๐, which is the range of the uniform
distribution (we are sampling uniformly from {0,...,๐}). The interface exposes two functions,
randAllocate andrandf , which creates a tape and samples from it, respectively. It also describes
various abstract predicates, their side conditions, and specifications of the functions, most notably
the specification that allows clients to presample into the abstract tape randTape , and reading from
it with randf , which we present in Figure 12.
The first condition states that when given the isRand invariant, a randTape abstract predicate,
and some error credits, we can append a value at the end of the tape and split the errors in an
Page 35:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 35
expectation-preserving way, similar to the presampling specification presented in the random
counter module. The second condition states that given the isRand invariant and a non-empty tape,
we can run randf on the tape to deterministically pop the tape and return its first element.
By choosing concrete definitions for the abstract predicates of this module, one can show that
various implementations of random samplers satisfy this Rand module specification; e.g. we proved
that a rejection sampler meets the specification of the Rand module (the proof is similar to showing
that implementation ๐ผ3of the randomized counter module meets its specification).
C.2 Lazy Random Sampler
In this subsection, we consider the implementation of a concurrent lazy one-shot random sampler
that samples lazily. This sampler is lazy in the sense that we only perform the sampling the first
time the thunk is invoked and we store the result in a reference that is read from whenever we call
the thunk again.
A selection of the code of the lazy random sampler is presented in Figure 13. The function
lazyRandInit creates a tuple containing a lock and a reference that points to None . When we call
lazyRandf with the tuple and a tape label, we acquire the lock and load the value of what the
location is pointing to. If it is Some๐ฅ, we directly return ๐ฅ. Otherwise, we sample ๐ฅfrom the
tape with the function randf from the Rand module defined in Appendix C.1 and store it into the
location. We additionally take in an extra argument ๐ก๐๐inlazyRandf and store it into the location
together with the sampled value to represent the first thread id that succeeds in acquiring the lock
and performing the actual randomized operation. We release the lock at the end right before we
return from the function.
lazyRandInitโฮป_.letโ=ref None in
let๐๐=newLock()in
(๐๐,โ)lazyRandfโฮป(๐๐,โ)๐
๐ก๐๐.
acquire๐๐;
let๐ฃ=match !โwith
|Some๐ฅโ๐ฅ
|Noneโ
let๐ฅ=(randf๐
,๐ก๐๐)in
โโSome๐ฅ;๐ฅ
end in
release๐๐;๐ฃ
Fig. 13. Code of the Lazy Random Sampler
To motivate what the specification of this lazy random sampler module should look like, we first
consider a client program lazyRace that uses this module. In this example, we set the parameter
of the internal Rand module to be 1, sorandf samples uniformly between 0and1. (The function
lazyAllocTape in this example creates a tape for this lazy random sampler, and we omit the code
for brevity.)
lazyRaceโlet๐=lazyRandInit()in
(lazyRandf๐(lazyAllocTape())0)|||( lazyRandf๐(lazyAllocTape())1)
In the lazyRace program, we create a lazy random sampler and fork two threads. Each thread
attempts to sample from it but they pass a different ๐ก๐๐as the parameter parameter. It should be the
Page 36:
36 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
case that both threads return the same tuple value ๐ฅ=(๐ฅ1,๐ฅ2); intuitively, this is because regardless
of how the threads are scheduled, the thread that is executed last must read the value stored by the
the thread that is executed first. Consider the following specification of lazyRace where for both
return values of the threads, we have ๐ฅ1=๐ฅ2with error probability 1/2.
{E(1/2)}lazyRace{๐ฃ.โ๐.๐ฃ=((๐,๐),(๐,๐))}
This is true morally because whichever threads gets scheduled first to perform the sampling, we can
useE(1/2)to avoid sampling a value from randf that is different from the ๐ก๐๐passed, ensuring that
the sampled value is identical to the ๐ก๐๐. However, there is some subtlety in the proof of this Hoare
triple, in particular, we cannot perform any presampling in advance of the actual lazyRandf . If we
directly attempt to presample a value to each tape on both threads (avoiding the corresponding
๐ก๐๐), we need to pay up to E(3/4)error credits because we are doing two presampling calls, where
ideally, we should only need to do one. One might try to rewrite lazyRace such that both threads
share the same tape, but this does not solve the presampling problem directly. In particular, before
either threads call lazyRandf , we do not know what value to sample into the tape. Whatever value
we presample onto the tape, the scheduler can deliberately choose to schedule the threads in a
way such that the ๐ก๐๐of the winning thread does not match the value presampled. In other words,
we want to delay the operation of presampling and perform it not before the lazyRandf call, but
during it.
Given this observation, the specification of the lazy random sampler module is written in a way
that allows presampling to be performed within the lazyRandf call. Here we show the specification
for presampling into the tapes of the lazy random sampler module and lazyRandf in Figure 14. The
ยฉยญยญยญยญยญยญยญยญยญยญยญ
ยซโ๐F๐๐E๐
๐พ๐๐.
(E๐๐[F]โค๐)โ
๐โEโ
isLazyRand ๐๐ ๐๐๐พโ
lazyTape๐
None๐พโ
E(๐)โ
|โโEโ๐.E(F(๐))โ
lazyTape๐
(Some๐)๐พยชยฎยฎยฎยฎยฎยฎยฎยฎยฎยฎยฎ
ยฌ
(a) Presampling Specificationโ๐๐พ๐๐ ๐๐
๐ก๐๐๐ 1๐2.
๏ฃฑ๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด ๏ฃฒ
๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃณisLazyRand ๐๐ ๐๐๐พโ
(โ๐.๐๐โlazyAuth๐๐พโ
|โโโคmatch๐with
|Some(๐ฅ,๐ฆ)โ๐๐โlazyAuth๐๐พโ๐1๐ฅ๐ฆ
|Noneโโ๐โฒ.lazyTape๐
(Some๐โฒ)๐พโ
(lazyTape๐
None๐พโ
|โโค๐(๐โฒ,๐ก๐๐)โlazyAuth(๐โฒ,๐ก๐๐)๐พโ
๐2๐โฒ๐ก๐๐)
end)๏ฃผ๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด ๏ฃฝ
๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃพ
lazyRandf๐๐๐
๐ก๐๐{(๐ฅ,๐ฆ).๐1๐ฅ๐ฆโจ๐2๐ฅ๐ฆ}E
(b)lazyRandf Specification
Fig. 14. Selection of Specification of the Lazy Random Sampler Module
presampling specification for the lazy random sampler is not too different from the other previous
examples; the main difference is that the abstract tapes for the module lazyTape stores an option
type instead of a list. Since for each tape, only the first value could ever be relevant in that it is
chosen to be the value stored in the reference, there is no reason to presample more than one value
into a single tape.
Now, let us focus on the more complicated specification for the lazyRandf function. Firstly,
notice that the lazy random sampler predicate isLazyRand takes in an additional predicate ๐as an
Page 37:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 37
argument. Intuitively, ๐is the invariant protected by the lock, and if the reference maps to the
value๐, it is the case that ๐๐holds whenever we access the lock and release it. The precondition of
thelazyRandf function requires two resources. The first being the abstract predicate isLazyRand
and the second being a view shift. The view shift encodes how the state of the module changes
throughout the call. The view shift starts by assuming that we have ๐๐andlazyAuth๐๐พfor some
๐, which represents the operation of acquiring the lock and gaining access to the authoritative state
of the lazy random sampler. We then perform a case split on ๐. If it is Some(๐ฅ,๐ฆ), then this means
that the lazy random sampler has already committed to a value, so we return directly by releasing
the lock and establishing some postcondition ๐1๐ฅ๐ฆ. Otherwise, if it is None , we reach the branch
where we have to do a randomized sampling. Here we are allowed to perform some probabilistic
update operation to provide a non-empty lazyTape (since the view shift is implemented with a |โโโค),
and it suffices to prove that after reading that value ๐โฒin the tape, we establish the authoritative part
of the lazy random sampler with the reference storing (๐โฒ,๐ก๐๐)and some postcondition ๐2๐โฒ๐ก๐๐. If
all preconditions hold, then the return value of the function is some pair (๐ฅ,๐ฆ), where either ๐1๐ฅ๐ฆ
or๐2๐ฅ๐ฆholds.
The key ingenuity of the specification of lazyRandf is that the view shift is described by the
|โโmodality, allowing us to perform presampling on abstract tapes within the function call in
addition to outside it. In particular, we can choose to perform a presampling action on a tape or
not depending on whether the sampler is storing a None (it has not been invoked before) or not.
This flexibility allows us to prove the specification of the lazyRace , by only performing a single
presampling within the first invocation of the function call lazyRandf .
C.3 Concurrent Amortized Collision-free Hash
When proving the correctness of randomized data structures, it is useful to assume that a hash
function is collision-free, in that different input keys for the function return different hash values.
In reality, collisions might occur but with very low probabilities.
Here, we first consider a concurrent collision-free hash, one that can be shared among many
threads, and each thread pays error credits to avoid collisions for every presampling action. Because
we want to be able to use the hash in a concurrent context, the specification of the hash is written
in a HOCAP-style with presampling tapes exposed to allow modular reasoning. We implement
a concurrent model of the idealized hash function under the uniform hash assumption [Bellare
and Rogaway 1993]. The assumption states that the hash function hashf mapping sets of keys ๐พ
to hash values ๐is a random oracle, in that for each key ๐โ๐พ, the hash value โ(๐พ)is sampled
uniformly from ๐independently of all other keys. We implement this model as a tuple containing
a lock and a mutable map ๐๐, choosing๐พand๐to be{0,...,๐}. The main hash function hashf is
shown below. The lock is acquired and released around the body of the hash function to ensure
that at most one thread is changing the state of the mutable map. In the critical section, if the key ๐
has been hashed before, we directly return ๐๐(๐). Otherwise, we sample a fresh value uniformly
from๐with the randf function defined in Appendix C.1, read a value from the tape ๐
, store it in
Page 38:
38 Kwing Hei Li, Alejandro Aguirre, S. O. Gregersen, Philipp G. Haselwarter, Joseph Tassarotti, and Lars Birkedal
๐๐(๐), and return it at the end.
hashf(๐๐,๐๐)๐๐
โacquire๐๐;
let๐ฃ=match get๐๐๐ with
|Some(๐)โ๐
|Noneโlet๐=randf๐
in
set๐๐๐๐ ;
๐
end in
release๐๐;๐ฃ
We show the presampling specification and the specification for hashf in Figure 15. To achieve
collision-freedom, we need to ensure that every value we presample to a tape generated by the hash
is different from any value previously presampled to alltapes generated by the hash. To be precise,
suppose we have presampled a total of ๐ values on all tapes of the hash. If we want to presample a
new value to a tape, we need to pay at least E ๐
๐+1to sample a unique value different from all
values presampled before. To keep track of all the values sampled before, the interface introduces
ahashsize abstract predicate that stores the set of all values that has been presampled before.
To presample onto a tape for the collision-free hash, we need to additionally pass in a hashsize
predicate to determine the amount of error needed to avoid the previous presampled-values. The
hashf specification is defined in almost the same way as the lazyRandf specification, the view shift
in the precondition performs a case split to determine whether a key has been hashed before by
looking into the mutable map.
ยฉยญยญยญยญยญยญยญยญยญยญยญยญยญ
ยซโ๐๐๐๐E๐
๐พยฎ๐๐ โ.
((๐ +(๐+1โ๐ )๐๐)/(๐+1)โค๐)โ
๐โEโ
isHashโ๐๐๐พโ
hashTape๐
ยฎ๐๐พโ
hashsize๐ ๐พโ
E(๐)โ
|โโEโ๐.E(๐๐)โhashsize(๐ +1)๐พโ
hashTape๐
(ยฎ๐ยท[๐])๐พยชยฎยฎยฎยฎยฎยฎยฎยฎยฎยฎยฎยฎยฎ
ยฌ
(a) Presampling Specificationโ๐๐พโ๐๐
๐๐ 1๐2.
๏ฃฑ๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด ๏ฃฒ
๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃณisHashโ๐๐๐พโ
(โ๐.๐๐โhashAuth๐๐พโ
|โโโคmatch๐!!๐with
|Some๐ฃโ๐๐โhashAuth๐๐พโ๐1๐
|Noneโโ๐ยฎ๐.hashTape๐
(๐ยทยฎ๐)๐พโ
(hashTape๐
ยฎ๐๐พโ
|โโค๐(๐[๐:=๐])โ
hashAuth(๐[๐:=๐])๐พโ๐2๐ยฎ๐)
end)๏ฃผ๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด ๏ฃฝ
๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃด๏ฃพ
hashfโ๐๐
{๐ฅ.๐ 1๐ฅโจโยฎ๐.๐ 2๐ฅยฎ๐}E
(b)hashf Specification
Fig. 15. Selection of Specification of the Collision-free Hash
We also used this specification to derive an amortized version of the collision-free hash, which
we show in Figure 16. This hash specification has two main advantages. Firstly, clients do not need
to pass a hashsize predicate as a precondition for presampling into the tape. In addition, the error
credit๐๐ด(๐,๐)to be paid is constant as it is amortized across a fixed number of insertions ๐that
is decided in advance. To keep track of the maximum number of times the hash is used, clients need
Page 39:
Modular Reasoning about Error Bounds for Concurrent Probabilistic Programs 39
to give up a single hashToken predicate; exactly ๐number of these hashToken s are generated when
the hash is initialized. The proof of this more complex specification is similar to that in Aguirre
et al.[2024] which we omit here. We emphasize that this amortized specification can be derived
from the non-amortized specification (Figure 15) without taking into account how the concurrent
hash is implemented.
ยฉยญยญยญยญยญยญยญยญยญ
ยซโ๐๐๐๐E๐
๐พยฎ๐โ.
๐โEโ
isHashโ๐๐๐พโ
hashTape๐
ยฎ๐๐พโ
hashToken 1๐พโ
E(๐๐ด(๐,๐))โ
|โโEโ๐.hashTape๐
(ยฎ๐ยท[๐])๐พยชยฎยฎยฎยฎยฎยฎยฎยฎยฎ
ยฌ
Fig. 16. Amortized Presampling Specification
Just like the specification presented in Appendix C.2, the specification for both the non-amortized
and amortized concurrent collision-free hashes uses the probabilistic update modality in the view
shift in its hashf specification to allow presampling to occur within the hashf body. As an example,
consider the following program (similar to lazyRace in Appendix C.2) and its specification.
{E(๐๐ด(๐,๐))}
letโ=initHash()in
(hashfโ0(hashAllocTape()))|||( hashfโ0(hashAllocTape()))
{๐ฃ.โ๐.๐ฃ=(๐,๐)}
Here we create an amortized hash and spawn two threads that each creates a tape and uses the
tape to hash the value 0. Because both threads are hashing the same key 0, it should be the case
that we only need to pay one constant ๐๐ด(๐,๐)for the first hash operation. However we do not
know which thread is scheduled first in advance, so we cannot perform the presampling in advance
before the hashf call. The probabilistic update modality allows us to perform the presampling
within the hashf call in the case where the value of ๐!!0isNone , indicating that this thread has
been scheduled first to do the randomized sampling.