1 / 52

Resource Bound Certification

Stephanie Weirich Cornell University. Resource Bound Certification. Joint work with Karl Crary, CMU. 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

Audrey
Download Presentation

Resource Bound Certification

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. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

  2. Problem • Sometimes code runs too long • How 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 terminated • May provide faster execution • Can 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 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

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

  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 length • Simplifying 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 types • Syntax of functional programming language ML • Terminology • 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 equivalent • Algorithm: convert each term to a normal form and compare • Need 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 language • Use the type system to enforce that a dynamic term matches a static description

  28. 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;

  29. 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

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

  32. 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 }

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

  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 clock • No 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 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

  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. 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 Example

  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 occur Node (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

More Related