1 Resource Bound Certification Stephanie Weirich Cornell University Joint work with Karl Crary, CMU

Preview:

Citation preview

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