1 resource bound certification stephanie weirich cornell university joint work with karl crary, cmu
TRANSCRIPT
1
Resource Bound Certification
Stephanie WeirichCornell University
Joint work with Karl Crary, CMU
2
Problem
Sometimes code runs too longHow do we prevent it?
3
Enforcement Methods
Run the code, and if it takes too long, kill it
Look at the code and decide how long it will take Annotations on the code can makes this
process decidable
4
Dynamic Method
Can decide how long is too long on the fly (even while the code is running)Code producer does not have to write code in any particular language (or include annotations) May be expensive to enforce
5
Static Method
Guarantee to the user that code will run without being prematurely terminatedMay provide faster executionCan encompass dynamic checking Static verification that the code executes
dynamic checks Makes policy enforcement part of the
model
6
Annotations
What annotations can we use for execution time verification?
7
Base Language
Type-Safe version of C all pointer accesses checked for NULL no pointer arithmetic safe memory-management (garbage-
collection or region-based) tagged unions
8
Caveat
Most examples in this talk will look like functional programmingIntension is to verify a low-level language (such as type-safe assembly language) Don’t have to trust the compiler Hopefully annotation methodologies
are general enough that people can be clever
9
Certification of Running Time
Simplification: Only count function calls and loop iterations Functions and loops annotated with the cost of execution
int add1(int x)<0> { return x+1;
}int add3(int x)<3> {
x = add1(x); x = add1(x);return add1(x);
}
10
Example
Function-typed arguments can influence the running time
int foo(int f(int x)<3>)<8> { return f(1)*f(3);
}But can restrict how the code is used
foo(add1);
foo(add3);
11
Abstract Time
Useful to abstract the time annotationint foo (int f(int x)<k>) <2k+2> {
return f(1) * f(3); }
foo (add1); // k=0, takes 2 steps + 1 for call
foo (add3); // k=3, takes 8 steps + 1 for call
12
Static Dependent Cost
What if the time depends on a non-function argument?Essential for recursive functions
uint fact (uint n)<n>{if (n == 0) return 1;else return (fact (n-1) * n);
}Note :
Time annotation must be non-negative Ignore underflow/overflow
13
Size
What if the argument is not a uint or a function?Option: Pre-defined mapping from structured data values to their “sizes”
14
Example - List
struct list {const uint val;const struct list* next;
}
The size of a list is its lengthSimplifying assumption - The members of the struct are const so that we do not have to track aliasing.
15
Sumf
int sumf (uint f(uint)<k>; struct list* x; int acc) <length(x)*(k+2)> {
if (x == NULL) return acc; else { int acc2 = acc + f (x->val);
struct list* x2 = x -> next;sumf (f, x2, acc2);
}}
16
Calling Sumf
sumf(add3, NULL,0); // 0*(3+2) + 1
struct list* x = new { val = 5; next = NULL };
sumf(add3, x,0) // 1*(3+2) + 1
17
Size
What if the time of f is dependent?uint sumf (uint f(uint y)<y>; struct list* x; uint acc) <length(x)*(??+2)> { if (x == null) return acc; else { uint acc2 = acc + f (x->val);
struct list* x2 = x -> next;sumf (f, x2, acc2);
}}
18
User-defined size
Need a programming language to express the mapping between datatypes and the time to iterate over them Expressive enough to represent structured
data, and functions over that data Not so expressive that equivalence is
undecidable
Need a way to connect dynamic data with a representation in this language
19
Decidable, Expressive Language
Typed-lambda calculus with products, sums and primitive recursion over inductive typesSyntax of functional programming language MLTerminology Dynamic language -- Type-safe C Static language -- this annotation
language
20
Static Language
Natural numbers (of type nat) and arithmetic operations 3+4 , 5*x
Higher-order functions fn (x : nat) => (fn (y :nat) => x+y) (of type nat (nat nat) )
Tuples (3,5) : nat nat
21
Static Language
Sums and recursive types notated with datatypes
datatype bool = False | True
fun not (b:bool) = case b of True => False
| False => True
22
Primitive Recursion
datatypes can recursively mention name only in positive positions
datatype foo = Bar of foo | Baz of foo * foo
datatype foo = Bar of foo foo
datatype foo = Bar of (foo int) foo
23
Primitive Recursion
Recursive functions over these datatypes can only call themselves on subterms of their arguments
datatype foo = Bar of foo | Baz of foo * foo
fun iter (x : foo) = case x of Bar(w) => iter(x)
| Baz(y,z) => iter(y)
24
List Representation
datatype list = Null | Cons of nat * list
fun time(m : list) = case m of Nil => 0 | Cons(val, next) =>
val+2+time(next)
25
Decision Procedure
We must be able to decide if two terms in the static language are equivalentAlgorithm: convert each term to a normal form and compareNeed a reduction system for terms that is confluent and strongly normalizing
26
Reduction Rules
Sample Reduction rules 3 + 4 --> 7 M + 0 --> M case Ci M of
C1 x1 => N1 | C2 x2 => N2 --> Ni [M/xi]
(fun f x => M ) N --> M[N/x, (fun f x => M)/f]
27
Connecting the Languages
We must be able to use this static language to describe the values of the dynamic languageUse the type system to enforce that a dynamic term matches a static description
28
Singleton Types
nat represents unsigned intsConnect constants in the two languagesIf m : nat , form singleton type uint<m>
uint<3> x;
x = 3; x = 4;
29
Using fact
New type of factorialuint fact(uint<m> x)<m>;
fact(3); // takes time 3+1uint<n> x;fact(x); // takes time n+1
30
Pointer Types
Consider pointer types int* Either a reference to an int or null int@ Must be a reference int<0> Must be a null pointer
Want to refine the type of a variable// x has type int*if (x == NULL) { // x has type int<0>} else { // x has type int@}
31
Enforcement types
Static representation of integer pointersdatatype ptr = Null | Ptrintptr(m) =
case m of Null => int<0> | Ptr => int@
If x : intptr(Ptr) then we know x is not NULL
32
Refinement
// suppose x : intptr(m)if (x == NULL) {
// here we know that x : int<0> // so therefore m must be Null, or // we’d get a contradiction
} else { // know that m is Ptr}
33
List Enforcement Type
datatype list = Null | Cons( nat, list)
replist(m) = case m of Null => int<0>
| Cons(val,next) => struct { const uint<val> val; const replist(next) rest }@
34
Using Enforcement Types
// if x has type replist(m)if (x == NULL) { // again x : int<0> } else {
// m must be Cons (val, next) // x: {const int<val> val; // const replist(next) rest }@ }
We’ve used a comparison in the dynamic code to increase our knowledge of the static representation
35
User-defined Size
Iterate over list, calculating a nat to represent execution time
fun time(m : list) = case m of Nil => 0 | Cons(val, next) =>
val+2+time(next)
36
Example : Code
uint sumf (uint f(uint y)<y>; replist(m) x; uint acc) <time(m)> { if (x == null) return acc; else { // m must Cons( val, next) // call to f takes time val + 1
uint acc2 = acc + f (x->val); struct list* x2 = x -> next;
// recursive call takes time(next) + 1 sumf (f, x2, acc2);
}}
37
Other Resources
“Effect notation” int f(int)<3> doesn’t generalize to resources that can be recovered (e.g. space)
Alternative: Augment the operational semantics with a virtual clock that winds down as the program executes
38
Virtual Clocks
Function types specify starting and ending times (int,12) f(int, 15) starts at time 15 and
finishes at time 12
Use polymorphism to abstract starting times (int, n) f(int,n+3) runs in 3 steps - it is
equivalent to int f(int)<3>
39
Recoverable Resources
Consider: (int, n+12) f (int, n+15)vs. (int, n) f (int,n+3)
If the resource is free-space: the first function may allocate as many as
15 units, as long as it releases 12 of them before returning.
The second function only requires a minimum of 3 units of free-space to execute.
40
Upper Bound
Sometimes it is enough just to know an upper bound of the running time.It is an approximation to help when static analysis fails.Add the instruction waste to the language to increment the virtual clockNo run-time effect
41
Waste example
bool member (int x; replist(m) w) <length(m)>{
if (w == NULL) return false;else
// m=Cons( val , next) if (x == w->val) {
waste <next>; return true; } else { return member( w->next ); }
}
42
TALres
We have implemented a similar system within the Typed Assembly Language frameworkClock contained in a virtual register, decremented on backwards jumpsTAL already has a sophisticated type constructor language Added sum and inductive kinds and refinement
procedure for comparison instructions Operations on static natural numbers
43
Source Language
Prototype implementation: PopCron Resembles C + timing annotations No separation between static and
dynamic languages
Compiler to TALres creates representations of datatypes
and their enforcement types
44
The real details
Static and dynamic language are really two levels of the same language Static language is embedded in the dynamic
language as a language of type constructors Types of dynamic language are constants in
the static language Types of static language are referred to as
kinds
45
More details
Building block for refinement is “virtual case analysis”
46
Another Example
Dynamic treesunion tree {
uint Leaf;struct node Node;
}struct Node {
const tree@ left;const tree@ right;
}
Static representation
datatype tree = Leaf of nat
| Node of tree * tree
47
Tree example
size (t : tree) = case t of Leaf(x) => x +1 | Node (left, right) => size(left)
+ size(right) + 2
uint sumf (uint f(uint<k>)<k>; reptree(t) t) <size(t)> {switch t {
case Leaf(x): return f(x); case Node(x):
return sumf(x.left) + sumf(x.right); }
}
48
Enforcement type for trees
reptree (tree) = case tree of Leaf x =>
union { uint<x> Leaf; empty Node;} | Node (left, right) =>
union { empty Leaf; struct { const reptree(left)@ left;
const reptree(right)@ right; } Node;
}
49
Virtual Case
switch t { case Leaf(x): // Suppose at runtime we could examine the
static // representation case t of
Leaf (x) => This is the only branch that could occurNode (x) =>… as here x is of type empty
50
Virtual Case
switch t { case Leaf(x): // Since one branch is impossible we don’t need
to // specify it
vcase t of Leaf(x) => // binds x in following code
This case statement is virtual because we know what branch will be taken -- no run time effect
51
Related Work
FX Reistad and Gifford, 94
Sized Types Hughes, Pareto, Sabry, 96 Chin Khoo, 00
PCC Necula and Lee, 97
DML Xi and Pfenning, 98
52
Future Work
Extension of implementation to other resources More flexible enforcement typesStronger equational logicInference and analysis in source language