- 297 Views
- Uploaded on

Download Presentation
## PowerPoint Slideshow about 'Resource Bound Certification' - Audrey

**An Image/Link below is provided (as is) to download presentation**

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.

- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript

Problem

- Sometimes code runs too long
- How do we prevent it?

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

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

Static Method

- Guarantee to the user that code will run without being prematurely terminated
- May provide faster execution
- Can encompass dynamic checking
- Static verification that the code executes dynamic checks
- Makes policy enforcement part of the model

Annotations

- What annotations can we use for execution time verification?

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

Caveat

- Most examples in this talk will look like functional programming
- Intension 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

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);

}

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);

Abstract Time

- Useful to abstract the time annotation

int 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

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

Size

- What if the argument is not a uint or a function?
- Option: Pre-defined mapping from structured data values to their “sizes”

Example - List

struct list {

const uint val;

const struct list* next;

}

- The size of a list is its length
- Simplifying assumption - The members of the struct are const so that we do not have to track aliasing.

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);

}

}

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

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);

}

}

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

Decidable, Expressive Language

- Typed-lambda calculus with products, sums and primitive recursion over inductive types
- Syntax of functional programming language ML
- Terminology
- Dynamic language -- Type-safe C
- Static language -- this annotation language

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

Static Language

- Sums and recursive types notated with datatypes

datatype bool = False | True

fun not (b:bool) = case b of

True => False

| False => True

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

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)

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)

Decision Procedure

- We must be able to decide if two terms in the static language are equivalent
- Algorithm: convert each term to a normal form and compare
- Need a reduction system for terms that is confluent and strongly normalizing

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]

Connecting the Languages

- We must be able to use this static language to describe the values of the dynamic language
- Use the type system to enforce that a dynamic term matches a static description

Singleton Types

- nat represents unsigned ints
- Connect constants in the two languages
- If m : nat , form singleton type uint<m>

uint<3> x;

x = 3;

x = 4;

Using fact

- New type of factorial

uint fact(uint<m> x)<m>;

fact(3); // takes time 3+1

uint<n> x;

fact(x); // takes time n+1

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@

}

Enforcement types

- Static representation of integer pointers

datatype ptr = Null | Ptr

intptr(m) =

case m of Null => int<0>

| Ptr => int@

- If x : intptr(Ptr) then we know x is not NULL

Refinement

// suppose x : intptr(m)

if (x == NULL) {

// here we know that x : int<0>

// so thereforemmust beNull, or

// we’d get a contradiction

} else {

// know thatmisPtr

}

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 }@

Using Enforcement Types

// if x has typereplist(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

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)

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);

}

}

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

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>

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.

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 clock
- No run-time effect

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 );

}

}

TALres

- We have implemented a similar system within the Typed Assembly Language framework
- Clock contained in a virtual register, decremented on backwards jumps
- TAL already has a sophisticated type constructor language
- Added sum and inductive kinds and refinement procedure for comparison instructions
- Operations on static natural numbers

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

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

Dynamic trees

union 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

Another ExampleTree 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);

}

}

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;

}

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 occur

Node (x) =>

…as here x is of type empty

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

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

Future Work

- Extension of implementation to other resources
- More flexible enforcement types
- Stronger equational logic
- Inference and analysis in source language

Download Presentation

Connecting to Server..