Stephanie weirich cornell university
1 / 52

- PowerPoint PPT Presentation

  • Uploaded on

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

I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.
Download Presentation

PowerPoint Slideshow about '' - 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
Stephanie weirich cornell university

Stephanie Weirich

Cornell University

Resource Bound Certification

Joint work with Karl Crary, CMU


  • Sometimes code runs too long

  • How do we prevent it?

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


  • What annotations can we use for execution time verification?

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


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



  • 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



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


  • What if the argument is not a uint or a function?

  • Option: Pre-defined mapping from structured data values to their “sizes”

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


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


  • 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
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
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
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 language1
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
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 recursion1
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
List Representation

datatype list = Null | Cons of nat * list

fun time(m : list) =

case m of

Nil => 0

| Cons(val, next) =>


Decision procedure
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
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
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
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
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
Pointer Types

  • Consider pointer types

    • int* Either a reference to an int or null

    • [email protected] 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 [email protected]


Enforcement types
Enforcement types

  • Static representation of integer pointers

    datatype ptr = Null | Ptr

    intptr(m) =

    case m of Null => int<0>

    | Ptr => [email protected]

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


// 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
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
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 size1
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) =>


Example code
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
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
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
Recoverable Resources


(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
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
Waste example

bool member (int x; replist(m) w) <length(m)>{

if (w == NULL) return false;


// m=Cons( val , next)

if (x == w->val) {

waste <next>; return true;

} else {

return member( w->next );




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

More details
More details

  • Building block for refinement is

    “virtual case analysis”

Another example

Dynamic trees

union tree {

uint Leaf;

struct node Node;


struct Node {

const [email protected] left;

const [email protected] right;


Static representation

datatype tree =

Leaf of nat

| Node of tree * tree

Another Example

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



Enforcement type for trees
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
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 case1
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
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
Future Work

  • Extension of implementation to other resources

  • More flexible enforcement types

  • Stronger equational logic

  • Inference and analysis in source language