Time Bounds for General Function Pointers Aquinas Hobor

advertisement
Time Bounds for
General Function Pointers
Robert Dockins and Aquinas Hobor
(Princeton University)
(NUS)
Goal
• Use step-indexed models to prove program
time bounds 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
time bounds in the presence of certain
(approximated) recursive domain equations
• For example, the kinds of domains that occur
in semantic models of the assertions of
concurrent separation logic with first-class
locks and threads.
3
Goal
• Use step-indexed models to prove program
time bounds in the presence of certain
(approximated) recursive domain equations
• For example, the kinds of domains that occur
in semantic models of the assertions of
concurrent separation logic with first-class
locks and threads.
– But maybe this is a really hard domain to attack…
4
Goal
• Use step-indexed models to prove program
time bounds in the presence of certain
(approximated) recursive domain equations
• Testbed: soundness of a logic of time bounds
for a language with
1. Function pointers
2. Semantic assertions (assert truth of a (classical)
logical formula at the current program point)
5
A very simple language
6
Simple semantic definitions
7
Simple semantic definitions
Uh oh…
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
We detail our predicates/assertions informally
and refer to the paper for formal construction
11
We detail our predicates/assertions informally
and refer to the paper for formal construction
12
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.
13
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
14
Hoare Rules, 1
15
Hoare Rules, 1
16
Hoare Rules, 2
17
Hoare Rules, 2
18
Hoare Rules, 3
19
Hoare Rules, 3
20
The Call Rule
The interesting rule is for call… and it’s not too bad:
21
The Call Rule
The interesting rule is for call… and it’s not too bad:
1. x must evaluate to a label l
22
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
23
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
24
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.
25
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.
26
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
27
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:
28
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
29
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
1. We have verified part of ª to specification ¡
30
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
2. We want to add this specification for l
31
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
3. We must have verified the code for l
32
The “Vsimple” rule
So this looks pretty bad, but we can take it apart:
4. When the precondition P(a) holds
33
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.
34
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.
35
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
36
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).
37
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…
38
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.
39
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.
40
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…
41
The “Vfull” rule
This is moderately horrible. We have (at least):
42
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
43
The “Vfull” rule
This is moderately horrible. We have (at least):
2. The same basic termination measure trick
44
The “Vfull” rule
This is moderately horrible. We have (at least):
3. A parameter b used for higher-order functions
45
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)
46
The “Vfull” rule
The Vsimple rule is just a special case of Vfull.
47
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.
48
Defining a calling convention
• To specify the “apply” function, we must define a
calling convention for the sub-function
49
Defining a calling convention
• The registers r1 – r4 are callee-saves; registers
from r5 ! 1 registers are caller-saves
50
Defining a calling convention
• A stdfun’s precondition, postcondition, and
termination measure only depend on r0.
51
Apply’s precondition, postcondition,
and termination measure
52
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.
53
The code is simple, although the verification is
a bit cluttered…
54
Notice how we track the termination measure
55
The verification obligation from Vfull
This is somewhat large (thank goodness for
machine-checking!). There are a few points of
particular interest.
56
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.
57
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.
58
The verification obligation from Vfull
3. We thread the termination argument
into the verification required.
59
“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”.
60
The key technical hurdle for soundness
All our key definitions (Hoare tuple, funptr,
etc.) are defined in terms of “halt”, which is
not obviously hereditary/continuous/
monotonic/downward closed.
A. What is hereditary?
B. Why is halting not hereditary?
61
The key technical hurdle for soundness
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.
62
The key technical hurdle for soundness
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 [].
63
The key technical hurdle for soundness
Our definition:
w ² haltsn(¾) ´
|w| > n !
(9 wh. (|w|-|wh| · n) Æ
(w,¾) * (wh, []) )
This is actually very similar to the basic
definition.
64
The key technical hurdle for soundness
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.
65
The key technical hurdle for soundness
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.
66
The key technical hurdle for soundness
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.
67
The key technical hurdle for soundness
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.
68
The key technical hurdle for soundness
See the paper (and/or Coq development) for
how to use this definition to build up the Hoare
tuple, etc.
Our top-level “erased” theorems are in standard
form: if a program is verified in our logic, then it
terminates.
All our proofs are machine checked in Coq.
Our core proofs are quite compact.
69
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…
70
Download