Spy Game – Verifying a Local Generic Solver in Iris
Paulo Em´ılio de Vilhena, Jacques-Henri Jourdan, Franc¸ois Pottier
January 22, 2020
When is a function constant?
Consider a program “f” that behaves extensionally.
Is it possible to dynamicallydetect that “f” is aconstant function?
What if “f” is defined onlazy integersinstead?
f : int - > int
When is a function constant?
Consider a program “f” that behaves extensionally.
Is it possible to dynamicallydetect that “f” is aconstant function? No.
What if “f” is defined onlazy integersinstead?
t y p e l a z y _ i n t = u n i t - > int f : l a z y _ i n t - > int
Approximate solution
Idea: “If fdoesnotuse its argument, then it must be constant.”
t y p e l a z y _ i n t = u n i t - > int
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy : l a z y _ i n t =
fun () - > r := f a l s e ; 0 in
let _ = f spy in
! r
• We refer to this programming technique asspying.
• Can we verify the correctness ofis_constant?
Approximate solution
Idea: “If fdoesnotuse its argument, then it must be constant.”
t y p e l a z y _ i n t = u n i t - > int
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy : l a z y _ i n t =
fun () - > r := f a l s e ; 0 in
let _ = f spy in
! r
• We refer to this programming technique asspying.
• Can we verify the correctness ofis_constant?
Approximate solution
Idea: “If fdoesnotuse its argument, then it must be constant.”
t y p e l a z y _ i n t = u n i t - > int
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy : l a z y _ i n t =
fun () - > r := f a l s e ; 0 in
let _ = f spy in
! r
• We refer to this programming technique asspying.
• Can we verify the correctness ofis_constant?
Approximate solution
Idea: “If fdoesnotuse its argument, then it must be constant.”
t y p e l a z y _ i n t = u n i t - > int
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy : l a z y _ i n t =
fun () - > r := f a l s e ; 0 in
let _ = f spy in
! r
• We refer to this programming technique asspying.
• Can we verify the correctness ofis_constant?
Approximate solution
Idea: “If fdoesnotuse its argument, then it must be constant.”
t y p e l a z y _ i n t = u n i t - > int
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy : l a z y _ i n t =
fun () - > r := f a l s e ; 0 in
let _ = f spy in
! r
• We refer to this programming technique asspying.
• Can we verify the correctness ofis_constant?
Approximate solution
Idea: “If fdoesnotuse its argument, then it must be constant.”
t y p e l a z y _ i n t = u n i t - > int
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy : l a z y _ i n t =
fun () - > r := f a l s e ; 0 in
let _ = f spy in
! r
• We refer to this programming technique asspying.
• Can we verify the correctness ofis_constant?
Approximate solution
Idea: “If fdoesnotuse its argument, then it must be constant.”
t y p e l a z y _ i n t = u n i t - > int
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy : l a z y _ i n t =
fun () - > r := f a l s e ; 0 in
let _ = f spy in
! r
• We refer to this programming technique asspying.
• Can we verify the correctness ofis_constant?
Motivation
Why is this a relevant question?
• Spying has never been verifiedin separation logic.
• Spying is used in real-worldfixed point computationalgorithms.
In the rest of this talk
• Explain and verify spying usingIris – an expressive separation logic.
• By the end, we will have the key ideasto verify is_constant!
• These same ideas allow verifying fixed point computation algorithms.
Specification of is_constant
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = let r = ref t r u e in
let spy () = r := f a l s e ; 0 in let _ = f spy in ! r
The specification is a Hoare triple:
{f implements φ}
is constant f
{b.b =true ⇒ ∃c.∀m. φ(m) =c}
“x computes m” is sugar for {true} x () {y.y =m}
“f implements φ” is sugar for
∀x,m.{x computes m} f x {y.y =φ(m)}
Intuition
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = (* A s s u m p t i o n : f i m p l e m e n t s φ *) let r = ref t r u e in
let spy () = r := f a l s e ; 0 in
let _ = f spy in
! r
At a first glimpse, the code suggests an intuitive idea:
“Ifrcontains true, then φis a constant function.”
The assertion becomes true only after“f spy”. It is notan invariant.
Intuition
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = (* A s s u m p t i o n : f i m p l e m e n t s φ *) let r = ref t r u e in
let spy () = r := f a l s e ; 0 in
let _ = f spy in
! r
At a first glimpse, the code suggests an intuitive idea:
“Ifrcontains true, then φis a constant function.”
The assertion becomes true only after“f spy”. It is notan invariant.
Intuition
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = (* A s s u m p t i o n : f i m p l e m e n t s φ *) let r = ref t r u e in
let spy () = r := f a l s e ; 0 in
let _ = f spy in
! r
At a first glimpse, the code suggests an intuitive idea:
“Ifrcontains true, then φis a constant function.”
The assertion becomes true only after“f spy”. It is notan invariant.
Intuition
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = (* A s s u m p t i o n : f i m p l e m e n t s φ *) let r = ref t r u e in
let spy () = r := f a l s e ; 0 in
let _ = f spy in
! r
At a first glimpse, the code suggests an intuitive idea:
“Ifrcontains true, then φis a constant function.”
The assertion becomes true only after“f spy”. It is notan invariant.
Intuition
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = (* A s s u m p t i o n : f i m p l e m e n t s φ *) let r = ref t r u e in
let spy () = r := f a l s e ; 0 in
let _ = f spy in
! r
At a first glimpse, the code suggests an intuitive idea:
“Ifrcontains true, then φis a constant function.”
The assertion becomes true only after“f spy”. It is notan invariant.
Insight 1
let i s _ c o n s t a n t ( f : l a z y _ i n t - > int ) = (* A s s u m p t i o n : f i m p l e m e n t s φ *) let r = ref t r u e in
let spy () = r := f a l s e ; 0 in
let _ = f spy in
! r
A better candidate invariant mentions how many times fcalls spy:
“#(calls) =#(past calls)+#(future calls) and
if rcontainstrue then #(past calls) = 0.”
To name the number of futurecalls, we needprophecy counters.
Prophecy Counters
They are ghostcode; they do not exist at runtime.
Implemented using Iris’s prophecy variables (Jung et al. 2020).
{true}
prophCounter() {p.∃n.p n}
{p n}
prophDecr p
{().0<n ∗ p (n−1)}
{p n}
prophZero p {().n= 0}
Intuition
The Invariant
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ;
! r
The operationprophCounter () yields a natural numbern.
Because we use prophDecr insidespy andprophZero at the end, n is the number of times spywillbe called!
n =#(calls)
The Invariant
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ;
! r
Informal:
“#(calls) =#(past calls)+#(futurecalls) and
ifrcontains true then #(past calls) = 0.”
Inv(r,p,n) = ∃(k : nat) (l : nat) (b : bool).
The Invariant
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ;
! r
At the end, by exploiting the invariant, we obtain:
r7→b ∗ (b =true⇒n= 0)
“If rcontainstrue, thenspy has never been called.”
But how to prove that φis constant from there?
The Invariant
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ;
! r
At the end, by exploiting the invariant, we obtain:
r7→b ∗ (b =true⇒n= 0)
“If rcontainstrue, thenspy has never been called.”
Insight 2 – The link between n and φ
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ; ! r
“If spyis never called, it can pretend to compute an arbitrary integer.”
n= 0 =⇒ ∀m.{true}spy (){y.y =m}
Therefore
n = 0 =⇒
{f implements φ} f spy {c.
∀m.
c =φ(m)}
Insight 2 – The link between n and φ
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ; ! r
“If spyis never called, it can pretend to compute an arbitrary integer.”
n= 0 =⇒ ∀m.spy computes m
Therefore
n= 0 =⇒
{f implements φ} f spy {c.
∀m.
c =φ(m)}
Insight 2 – The link between n and φ
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ; ! r
“If spyis never called, it can pretend to compute an arbitrary integer.”
n= 0 =⇒ ∀m.spy computes m Therefore
n= 0 =⇒ ∀m.
{f implements φ}
f spy {c.
∀m.
c =φ(m)}
Insight 2 – The link between n and φ
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ; ! r
“If spyis never called, it can pretend to compute an arbitrary integer.”
n= 0 =⇒ ∀m.spy computes m Therefore
n= 0 =⇒ ∀m.
{f implements φ}
f spy
∀m.
Insight 2 – The link between n and φ
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in
let _ = f spy in p r o p h Z e r o p ; ! r
“If spyis never called, it can pretend to compute an arbitrary integer.”
n= 0 =⇒ ∀m.spy computes m Therefore
n= 0 =⇒
{f implements φ}
f spy {c.∀m.c =φ(m)}
Conjunction rule
Moving the quantifier is justified by arestricted conjunction rule:
∀x.{P}e {y.Q(x,y)} Qis pure {P}e0 {y.∀x.Q(x,y)}
wheree0 is a copy ofe instrumented withprophecy variables.
The proof in Iris is novel and is yet another use case of prophecies.
Combining the previous steps
let i s _ c o n s t a n t f = let r = ref t r u e in
let p = p r o p h C o u n t e r () in let spy () =
p r o p h D e c r p ; r := f a l s e ; 0 in let _ = f spy in
p r o p h Z e r o p ; ! r
“If rcontains true at the end, thenspyis never called.”
r7→b ∗ (b =true⇒n= 0)
“If spyis never called, then φis a constant function.”
n= 0 =⇒
{f implements φ}
f spy {c.∀m.c =φ(m)}
Summary
What we have seen so far
• is_constant– an example of spying.
• Proof sketch foris_constant.
• How prophecy variables are used to handle spying.
• A restricted conjunction rule.
For the rest of the talk
• What is alocal generic solver.
• Explain theconnection between spying and local generic solvers.
What is a Local Generic Solver?
A term coined by Fecht and Seidl (1999).
• Asolver computes the least function “phi” that satisfies eqs phi = phi
where “eqs” is a user-supplied function.
• Genericmeans it is parameterized with a user-defined partial order.
• Localmeansphi is computed on demand and need not be defined everywhere.
API of a Local Generic Solver
t y p e v a l u a t i o n = v a r i a b l e - > p r o p e r t y
val lfp : ( v a l u a t i o n - > v a l u a t i o n ) - > v a l u a t i o n
A simpleexample is to compute Fibonacci:
t y p e v a l u a t i o n = int - > int
let eqs ( phi : v a l u a t i o n ) ( n : int ) =
if n <= 1 t h e n 1 e l s e phi ( n - 1) + phi ( n - 2) in
let fib = lfp eqs
Dependencies
“fibatndepends onfibat n - 1andn - 2.”
t y p e v a l u a t i o n = int - > int
let eqs ( phi : v a l u a t i o n ) (n: int ) =
if n <= 1 t h e n 1 e l s e phi (n - 1) + phi (n - 2) in
let fib = lfp eqs
• Local generic solvers use dependencies for efficiency.
• Dependencies are discovered at runtimevia spying.
Conclusion
What is in the paper
• Improvements to Iris’s prophecy variableAPI.
• Proof of aconjunction rule.
• Use of locks to make our code thread-safe.
• Specification and proof ofmodulus, the general case of spying.
• Specification and proof of alocal generic solver.
Limitations
• We only provepartial correctness.
• We do not prove deadlock-freedom.
Questions?
modulus
Spying is subsumed by a single combinator, modulus, so named by Longley (1999).
let m o d u l u s ff f = let xs = ref [] in let spy x =
xs := x :: ! xs ; f x
in
let c = ff spy in ( c , ! xs )
• lfpuses modulus.
• is_constantcan be written in terms ofmodulus.
modulus
Spying is subsumed by a single combinator, modulus, so named by Longley (1999).
let m o d u l u s ff f = let xs = ref [] in let spy x =
xs := x :: ! xs ; f x
in
let c = ff spy in ( c , ! xs )
let i s _ c o n s t a n t p r e d = let r = ref t r u e in let spy () =
r := f a l s e ; 0
in
let _ = p r e d spy in
! r
• lfpuses modulus.
• is_constantcan be written in terms ofmodulus.
modulus
Spying is subsumed by a single combinator, modulus, so named by Longley (1999).
let m o d u l u s ff f = let xs = ref [] in let spy x =
xs := x :: ! xs ; f x
in
let c = ff spy in ( c , ! xs )
let i s _ c o n s t a n t p r e d = let z e r o () = 0 in m a t c h
m o d u l u s p r e d z e r o w i t h
| _ , [] - > t r u e
| _ , _ :: _ - > f a l s e
• lfpuses modulus.
• is_constantcan be written in terms ofmodulus.
Conjunction rule
∀x.{P}e (){y.Q x y} Q is pure {P}withProph e{y.∀x.Q x y}
wherewithProph eis the program einstrumented with prophecies:
let w i t h P r o p h ( e : u n i t - > ’ a ) = let p = n e w P r o p h () in
let y = e () in r e s o l v e P r o p h p y ; y
Prophecy variables
Prophecy Allocation {true}
newProph() {p.∃zs.p zs}
Prophecy Assignment {p zs}
resolveProph p x ().∃zs.zs =x ::zs0 ∗p zs0
Prophecy Disposal {p zs} disposeProph p
{().zs = []}
Improvements
• The operationdisposeProph is new.
Specification of Fix
∀eqsE.(E is monotone) ⇒
{eqs implements E}
lfp eqs
{phi.phi implements µE}¯
Remarks
• Partial correctness: termination isnotguaranteed.
• Possible deadlocks depending on the user implementation ofE.
Related work
Hofmann et al. (2010a) present a Coq proof of a local generic solver:
• they model the solver as a computation in a state monad,
• and they assume the client can be modeled as a strategy tree.
Why it is permitted to model the client in this way is the subject of two separate papers (Hofmann et al. 2010b; Bauer et al. 2013).