A Theory of Termination via Indirection Aquinas Hobor

advertisement
A Theory of Termination via Indirection
Robert Dockins and Aquinas Hobor
(Princeton University)
(NUS)
Goal
• Use step-indexed models to prove program
termination in the presence of certain
(approximated) recursive domain equations
• Testbed: soundness of a logic of total
correctness for a language with
1. Function pointers
2. Semantic assertions (assert truth of a (classical)
logical formula at the current program point)
2
Goal
• Use step-indexed models to prove program
termination in the presence of certain
(approximated) recursive domain equations
• Testbed: soundness of a logic of total
correctness for a language with
1. Function pointers
2. Semantic assertions (assert truth of a (classical)
logical formula at the current program point)
3
A very simple language
4
Simple semantic definitions
5
Simple semantic definitions
Uh oh…
6
We detail our predicates/assertions informally
and refer to the paper for formal construction
7
We detail our predicates/assertions informally
and refer to the paper for formal construction
8
We detail our predicates/assertions informally
and refer to the paper for formal construction
9
We detail our predicates/assertions informally
and refer to the paper for formal construction
10
What does funptr l t [P] [Q] mean?
1. The program has some code c at address l .
(This is why we want predicates to be able to
judge programs.)
2. When c is called with some initial store ½, if
t(½) is defined then c makes at most t(½)
function calls before returning to its caller.
3. P and Q are actually functions from some
function-specific type A to predicates. If t(½)
is defined then for all a, if P(a) holds before
the call then Q(a) will hold afterwards.
11
Why it was (considered) hard
• Certain people who will remain anonymous
(some in this room, some elsewhere, e.g.,
Princeton, NJ) thought step-indexed models
were inapplicable to liveness problems
• We have discussed the difficulty of statements
like, “method X cannot be used to prove Y”
12
Why it was (considered) hard
• Certain people who will remain anonymous
(some in this room, some elsewhere, e.g.,
Princeton, NJ) thought step-indexed models
were inapplicable to liveness problems
• We have discussed the difficulty of statements
like, “method X cannot be used to prove Y”
13
Why it was (considered) hard
• But if you asked, such people said:
1. Step-indexed models have an outer “forall n”
that quantifies (universally) over the number of
steps; this works well for safety properties but
very poorly for liveness properties
2. While “safe” is hereditary/monotonic/closed
under approximation, “halts” is not (actually, this
is related to point 1, too…)
3. It’s never been done and termination is hard
14
Why it was (considered) hard
• But if you asked, such people said:
1. Step-indexed models have an outer “forall n”
that quantifies (universally) over the number of
steps; this works well for safety properties but
very poorly for liveness properties
2. While “safe” is hereditary/monotonic/closed
under approximation, “halts” is not (actually, this
is related to point 1, too…)
3. It’s never been done and termination is hard
15
Why it was (considered) hard
• But if you asked, such people said:
1. Step-indexed models have an outer “forall n”
that quantifies (universally) over the number of
steps; this works well for safety properties but
very poorly for liveness properties
2. While “safe” is hereditary/monotonic/closed
under approximation, “halts” is not (actually, this
is related to point 1, too…)
3. It’s never been done and termination is hard
16
Why it’s actually not that hard
1. It turns out that the outside quantification
can just as easily be existential if we can only
find the right value to instantiate it…
– This is exactly what a termination proof gives us!
– “P terminates” a.k.a. “exists n. P reaches halt in n
steps”
17
Why it’s actually not that hard
1. It turns out that the outside quantification
can just as easily be existential if we can only
find the right value to instantiate it…
– This is exactly what a termination proof gives us!
– “exists n. prog reaches halt in n steps”
18
Hoare Judgment
• Our Hoare Judgment looks a bit complex:
¡, R `n { P } c { Q }
• ¡ contains function pointer assertions and R
is the postcondition of the current function
• n is an upper bound on the number of
function calls c makes before it terminates
19
Hoare Rules, 1
20
Hoare Rules, 1
21
Hoare Rules, 2
22
Hoare Rules, 2
23
Hoare Rules, 3
24
Hoare Rules, 3
25
The Call Rule
The interesting rule is for call… and it’s not too bad:
26
The Call Rule
The interesting rule is for call… and it’s not too bad:
1. x must evaluate to a label l
27
The Call Rule
The interesting rule is for call… and it’s not too bad:
2. l must be a pointer to a function with termination
measure t, precondition Pl and postcondition Ql
28
The Call Rule
The interesting rule is for call… and it’s not too bad:
3. The termination measure t must evaluate to
some n on the current store
29
The Call Rule
The interesting rule is for call… and it’s not too bad:
3. The termination measure t must evaluate to
some n on the current store… and so this call will
take no more than n+1 calls.
30
The Call Rule
The interesting rule is for call… and it’s not too bad:
4. We require the (parameterized) precondition to
be true at call, and guarantee the (parameterized)
postcondition will be true on return.
31
So, not too bad…
• Most of these rules are really nothing to get
excited about… even the call rule is pretty
simple when you understand the parts…
• (This is a virtue, of course…)
• But we’re not done. We’ve only shown the
rule for using the funptr, not the rules for
verifying that a function actually terminates
32
Verifying function bodies
• Verifying function bodies, while very
important, is not part of the main line of this
talk – why proving termination with a stepindexed model isn’t so bad.
• We can verify functions in many interesting
cases – recursive, mutually recursive, higherorder (we can handle map of map), etc.
• If you want, ask me about this at the end. (I
have 35 slides as backup!)
33
Why it’s actually not that hard
2. The second reason people thought that
termination was really hard with stepindexed models was that “halting” is not
hereditary/monotonic/downward closed.
A. What is hereditary?
B. Why is halting not hereditary?
34
Why it’s actually not that hard
2. P hereditary ´ (w ² P Æ wÃw’) ! w’ ² P
Here à is the “age” or “approximate”
operation on step-indexed worlds.
We can only approximate a finite number of
times before we “hit bottom”. The step
relation approximates w during function call.
35
Why it’s actually not that hard
2. The standard definition:
w ² halts(¾) ´ 9 wh. (w,¾) * (wh, [])
Let w à w’. The problem is that the 
relation approximates the world (at function
call), so w’ might not have enough “time” left
to actually reach the halting state [].
36
Why it’s actually not that hard
2. Our definition:
w ² haltsn(¾) ´
|w| > n !
(9 wh. (|w|-|wh| · n) Æ
(w,¾) * (wh, []) )
This is actually very similar to the basic
definition.
37
Why it’s actually not that hard
2. Our definition:
w ² haltsn(¾) ´
|w| > n !
(9 wh. (|w|-|wh| · n) Æ
(w,¾) * (wh, []) )
Here is the exists and requirement that we
step to the halt state.
38
Why it’s actually not that hard
2. Our definition:
w ² haltsn(¾) ´
|w| > n !
(9 wh. (|w|-|wh| · n) Æ
(w,¾) * (wh, []) )
This is the key “trick” – if the amount of time
left in w is not enough, then we become true
(and thus hereditary) trivially.
39
Why it’s actually not that hard
2. Our definition:
w ² haltsn(¾) ´
|w| > n !
(9 wh. (|w|-|wh| · n) Æ
(w,¾) * (wh, []) )
We must be sure that n is really a bound on
the number of steps required to halt.
(Alternative: see paper’s Coq development.)
40
Why it’s actually not that hard
2. Our definition:
w ² haltsn(¾) ´
|w| > n !
(9 wh. (|w|-|wh| · n) Æ
(w,¾) * (wh, []) )
So, really not that bad. This is the
“fundamental” trick that makes everything
else possible.
41
Why it’s actually not that hard
2. See the paper (and/or Coq development) for
details on how to use this definition to build
up the definition for the Hoare tuple, etc.
Actually I have presented a slightly modified
version (we continue to tinker), but feel free
to ask us for the latest version.
42
Why it’s actually not that hard
3. The third objection is that termination is
tricky and have never been done with stepindexing before. We observe the following:
• Our top-level “erased” theorems are stated in
absolutely standard form: if a program is
verified in our logic, then it terminates.
• No indirection theory in statement of
(erased) top-level theorem.
43
Why it’s actually not that hard
3. The third objection is that termination is
tricky and have never been done with stepindexing before. We observe the following:
• All our proofs are machine checked in Coq.
• Our core proofs are quite compact.
44
Compact Proofs
• The main file is 826 lines long, and contains:
A. Semantic model of the terminating function
pointer, Hoare judgment, whole-program
verification judgment
B. 10+ Hoare rules and soundness proofs
C. 3 whole-program rules and soundness proofs
D. Top-level theorems (if P is verified, then it halts)
• Really quite short…
45
46
V-rules
• We need another judgment, written ª : ¡, that
says that program ª contains functions verified
to “funptr” specifications in ¡.
• We will verify functions one at a time (or in the
case of mutually-recursive groups, as a group).
• The “base case” is an easy rule:
47
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
48
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
1. We have verified part of ª to specification ¡
49
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
2. We want to add this specification for l
50
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
3. We must have verified the code for l
51
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
4. When the precondition P(a) holds
52
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
4. When the precondition P(a) holds and the
termination measure is equal to some n.
53
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
5. This n is also an upper bound on the number of
calls this function makes.
54
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
6. The postcondition is just ?: you can’t fall out the
bottom
55
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
6. The postcondition is just ?: you can’t fall out the
bottom, but the return condition is Q(a).
56
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
7. We can assume every funptr we have previously
verified, and…
57
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
8. We can call ourselves using a modified function
specification precondition: t has decreased.
58
The “Vsimple” rule
Here are the critical uses for t. If we assume the
termination measure is equal to n at the function
start, then we can make no more than n calls and
can recurse only when t < n.
59
Using Vsimple
• We will verify the unary addition function
• Our language, while Turing-complete, is
extremely painful to program in; this is not
fundamental but it does mean that our
examples are somewhat “lame”.
• Expanding the result to a more programmable
language would not be very difficult now.
60
Using Vsimple: naturals as lists
• listnat(0,0)
• listnat(n,v) \Rightarrow listnat(n+1, (0,v))
•
•
•
•
•
0
1
2
3
…
0
(0, 0)
(0, (0, 0))
(0, (0, (0, 0)))
…
61
Precondition, postcondition,
termination measure
addP(n,m) ´ 9 v1, v2. (r1  v1) Æ (r2  v2) Æ
listnat(n,v1) Æ listnat(m,v2)
addQ(n,m) ´ 9 v2. (r2  v2) Æ listnat(n+m,v2)
addt(½) ´ the unique n s.t. 9 v1. (½(r1) = v1) and
listnat(n,v1)
62
Code (stored at label 1)
63
The verification
• Actually, using the Hoare rules is very easy.
64
The verification
• Actually, using the Hoare rules is very easy.
• But very tedious. We won’t bore you.
• The more interesting question is, what does
the verification obligation generated from
Vsimple look like?
65
Verification obligation from Vsimple
n
¡n,
xxx
66
Vsimple, in a nutshell
• The Vsimple rule is fine for most functions, but
it can only verify, well, simple recursive
functions (as well as calls to both simple and
complex functions previously verified).
• If we want “the goodies” (mutual recursion,
polymorphic termination arguments, etc.)
then we need to call in Vsimple’s big brother…
67
The “Vfull” rule
This is moderately horrible. We have (at least):
68
The “Vfull” rule
This is moderately horrible. We have (at least):
1. A set © of funptr specifications in the goal as well
as the assumptions
69
The “Vfull” rule
This is moderately horrible. We have (at least):
2. The same basic termination measure trick
70
The “Vfull” rule
This is moderately horrible. We have (at least):
3. A parameter b used for higher-order functions
71
The “Vfull” rule
This is moderately horrible. We have (at least):
3. There can be a strong dependency between b
and the other factors (e.g., the type of a)
72
The “Vfull” rule
The Vsimple rule is just a special case of Vfull.
73
Using Vfull
• We are going to verify the simplest nontrivial
higher order function we can, “apply”.
• It takes as an argument a pair of a function f and
an argument v and just applies f to v.
• The “interesting” part is how the polymorphism is
verified as opposed to the function behavior.
74
Defining a calling convention
• To specify the “apply” function, we must define a
calling convention for the sub-function
75
Defining a calling convention
• The registers r1 – r4 are callee-saves; registers
from r5 ! 1 registers are caller-saves
76
Defining a calling convention
• A stdfun’s precondition, postcondition, and
termination measure only depend on r0.
77
Apply’s precondition, postcondition,
and termination measure
78
Apply’s precondition, postcondition,
and termination measure
• These look “obvious” until you realize that P,
Q, and t seem free in the definition. We will
see how the Vfull rule “pipes” these in.
79
Apply’s precondition, postcondition,
and termination measure
• Notice that we can use the called function’s
termination measure to build our own
80
The code is simple, although the verification is
a bit cluttered…
81
If you’ve got it, flaunt it: we toss in a higherorder assert for fun…
82
Notice how we track the termination measure
83
The verification obligation from Vfull
This is somewhat large (thank goodness for
machine-checking!). There are a few points of
particular interest.
84
The verification obligation from Vfull
1. We can check this function first, — i.e., ¡ = >
That is, we verify this function before we verify the
functions we will pass to it.
85
The verification obligation from Vfull
2. The Vfull rule “pipes in” t, P, and Q – in fact, the
triple (t,P,Q) is the “b” from Vfull.
86
The verification obligation from Vfull
3. As before, we thread the termination argument
into the verification required.
87
“Stacking” apply
• As it happens, the apply function, itself, is a
standard function (r1 – r4 are preserved,
argument passed in r0, return in r0).
• In fact, we can pass “apply” to itself without
difficulty. The rules will prevent us from
unbounded recursion. We can “apply (apply
(apply … (apply (f))))” but the function f “at
the bottom” must terminate “on its own”.
88
More examples…
• We could go on to map, map of map, etc., but
maybe it would be better here to just refer to
the paper and mechanized development.
• Are there questions?
89
Download