The program:
          read(N)
          i := 1
          sum := 0
          goto loop
   loop:  if i > n goto end else body   
   
   body:  sum := sum + i
          i := i + 1
          goto loop
   end:   print sum
Convert to:
  ( List of reads
    List of blocks )
We get:
  
  (
   (
      (read N)
   )
   (
    ( block nil
      (assign i 1)
      (assign sum 0)
      (goto loop)
    )
    ( block loop
        (cond ...)
    )
   )
  )
  interp()
    lab     \
             >-- local variables
    store   /
  at a halt:  (pp, store)    // this is a compuation state
                             // pp = program point
                             // halt only before a basic block
  partial computation state:  (pp,vs)   // vs = values of statically
                                        // determined variables
Note: these states become labels on the basic blocks of the residual program.
Original      Specialized
program:      program:
   pp: ~~~  / (pp,vs1): ~~~    \
       ~~~ /            ~~~    |
       ~~~<   (pp,vs2): ~~~    } We do not want an infinite number of these!
       ~~~ \            ~~~    |
            \ (pp,vs3): ~~~    /
                        ~~~
               \__  __/
                  \/ 
                 poly = the set of specialized program points
      read(z)
      y := 1
  q:  if y < 3 then goto r else s
  r:  y := y + 1
      z := z + 1
      goto q
  s:  print z
Consider a trace of a ``symbolic execution'' of the program.
We introduce a name (z) for the unknown input value in computation states
of the trace:
      (q,(1,z'))         // z' is whatever z was, where z is a value.
      (r,(1,z'))         // z' was "z-bar" in class
      (q,(2,z'+1))
      (r,(2,z'+1))
      (q,(3,z'+2))
      (r,(3,z'+2))
comp state = (pp, (vals of static vars, vals of dynamic vars))
           =~ ((pp, vals of static vars), vals of dynamic vars)
               \_______________________/
                           |
            these will be the program points of P'.
GOAL:  P  ==> P' (the residual program)
        i
We guarantee we have preserved the semantics of the original program.
New states can be mapped back to original states very easily.
Back to the example ...
The specialized program:
          read(z)
  (q,1):  goto(r,1)     --->  ((q,1),z')
  (r,1):  z := z + 1    --->  ((r,1),z')
          goto(q,2)     
  (q,2):  goto(r,2)     --->  ((q,2),z'+1)
  (r,2):  z := z + 1    --->  ((r,2),z'+1)
          goto(q,3)
  (q,3):  goto(s,3)
  (s,3):  print(z)
Compression of transitions:
          read(z)
  (r,1):  z := z + 1
  (r,2):  z := z + 1
  (2,3):  print(z)
  Note 1: This cannot be mapped directly back to original program.
  Note 2: This is called "converting data into control" (i.e., y was 
          built in to the control flow).
We need a program to keep the static portion around, while spitting out
fragments of the residual program.
We extend the notion of static and dynamic inputs to other entities in the program.
Definition: A division is a classification of each variable as either S (``static'' or ``supplied'') or D (``dynamic'' or ``delayed'').
Definition: A division is uniform if each variable v has the same classification at every point in the program; it is non-uniform otherwise.
It would make no sense to have the following:
       V : D  (V is dynamic)
         .
         .  --->  W := f(V)    // Because V is dynamic, we cannot allow
         .                     // W to be static.
       W : S
So, dependent variables (W is dependent on V) must remain in the
same division class.
Definition: Variables classified as S only depend on variables that are S (or, ``any variable that depends on a D variable must also be D''; or, ``D begets D'').
The example program above has uniform division and is congruent:
       y : S
       z : D
Comments:
A dynamic test that always leads to a particular part of the program:
            ( dynamic )         If the B branch is always taken,
                /\              after a dynamic test, it cannot be 
               /  \             statically checked that block A
              /    \            will never be executed (this would be
             A      B           tantamount to solving the halting 
                                problem).
Back to the example ...
     (2, z'+1)
     (3, z'+2)
         \__/
           \_  none of these affect 
               program conversion
  If at any time z' would have affected the x values, then we must
  reflect that in the conversion.
A congruent division says we can never cross from D --> S, but it is ok to go S --> D. In other words:
       S  D                S  D        // going down
        \                    /  
         \       NOT        /
       S  D                S  D 
We are doing full execution on the static part.  We generate program points
of the form (pp,vs)
Definition: The set of all specialized program points (pp,vs) that are reachable (via symbolic execution) from the start of the program is called poly.
Some auxiliary subroutines:
  eval(exp,vs)   :  evaluate exp on vs (assuming that exp contains
                    only static variables)
  simplify(exp,vs) :  simplification of exp
Look at (2):
      poly := { (pp ,vs ) }     // pp = start of program
                   0   0             0
                                // vs = info we are given about input                    
                                     0
      
      while (poly contains an unmarked (pp,vs) do
        select an unmarked (pp,vs) and mark it
        generate code (pp,vs)
        poly := poly U successors (pp,vs)
      end
Recaping so far:
       x->S
       y->D
       
  
    Original       Specialized
    program:       program:
       pp: ~~~  / (pp,vs1): ~~~    \
           ~~~ /            ~~~    |
           ~~~<   (pp,vs2): ~~~    } We do not want an infinite number of these!
           ~~~ \            ~~~    |
                \ (pp,vs3): ~~~    /
                            ~~~
                   \__  __/
                      \/ 
                     poly
  
  poly := { (pp0, vs0) };
  new_program := empty_program
  insert reads for vars - {vars of vs0}
Now, the worklist part.  We assume that poly and new_program are global
variables:
  while poly contains an unmarked (pp,vs) do
     Select and mark an unmarked (pp,vs) from poly
     Generate(pp, vs)  /* Generate code for basic block that starts at pp,
                        * inserting successors into poly */
  od
Now, to generate code, we use the function Generate():
Generate( pp, vs )
  new_blk := empty_blk
  for command:=pp; exists more stmts in the block; command:=next(command) do
    /* Here there should be a switch statement based on the type of
     * command being worked on.  The actions taken for each type of cmd
     * are shown here in tabular form:
     */
      +-------------------------------------------------------------------------------+
      |cmd type   | condition   | Perform action     | Append to          | Insert    |
      |           |             |                    | new_block          | into poly |
      +-------------------------------------------------------------------------------|
      |x := exp   | x is Static | vs :=              |        -           |     -     |
      |           |             | vs[x->eval(exp,vs)]|                    |           |
      |           |-------------------------------------------------------------------|
      |           | x is Dynamic| simplified_exp :=  | "x:="              |     -     |
      |           |             |  simplify(exp,vs)  | simplified_exp";"  |           |
      +-------------------------------------------------------------------------------|
      |return exp |     -       |         ''         | "return("          |     -     |
      |           |             |                    | simplified_exp");" |           |
      +-------------------------------------------------------------------------------|
      |goto pp'   |     -       |         -          | "goto" (pp',vs)    | (pp',vs)  |
      +-------------------------------------------------------------------------------|
      |if exp     | exp dyn.    | simplified_exp :=  | "if" simplified_exp| (pp',vs)  |
      |  goto pp' |             |   simplify(exp,vs) |   "goto"(pp',vs)   | (pp'',vs) |
      |else pp''  |             |                    | "else" (pp'',vs)   |           |
      |           |-------------------------------------------------------------------|
      |           | exp static  |                    | "goto"(pp', vs)    | (pp',vs)  |
      |           |eval(exp,vs)=|         -          |                    |           |
      |           |     T       |                    |                    |           |
      |           |-------------------------------------------------------------------|
      |           |eval(exp,vs)=|         -          | "goto"(pp'',vs)    | (pp'',vs) |
      |           |     F       |                    |                    |           |
      +-------------------------------------------------------------------------------+
   od /* end for-loop */
   attach new_block to new_program
Some notes on compressing transitions. We would like to compress transitions on the fly. Consider the case where we lay down the code "goto(pp',vs)". What if (pp',vs) winds up being another goto? The idea for avoiding this is to not emit the unconditional goto's, but to simply change the current command to be the command at the beginning of (pp',vs). The idea is that we want to suck up that basic block (pp',vs) and start working on that block with our current vs. For example, in the table above consider the case where we have an if statement, and exp is static and evaluates to T. The new action for transition compression on-the-fly would be
command := lookup(prog.,pp')In addition, we would not append anything to new_block, nor insert (pp',vs) into poly (because (pp',vs) will not label a block).
This material is in Chapter 4 of [Jones et al. 93].
We may want to patially evaluate an interpreter int, running a program p:
[| pe |] int pThe interpreter has available all program variables. It manipulates the environment -- so it knows all values p{i} where i is the value of variable p.
Consider the classic structure for an environment:
           env  <= env is dynamic  
           /\ 
          /  \
         /\   \
        /  \   \
      name val  \
       S    D   /\
               /  \
              /\   \
S indicates static and D indicates dynamic variables.
This implementation of the environment destroys the ability to partially evaluate the interpreter (with our simple binding-time analysis) because the whole environment is considered dynamic. An alternative implementation of the environment side-steps this problem. If we unzip the environment and keep separate name and value lists, then the name list will be static:
   name_list (S)         val_list (D)
       /\                    /\
      /  \                  /  \
     a   /\                5   /\
        /  \                  /  \
       w   /\                17  /\
          /  \                  /  \
         b   /\                3   /\
            /  \                  /  \
           d   nil               1   nil
Now consider an example tha could appear in some interpreter.
(Variables are marked (S)tatic and (D)ynamic in C-style comments):
parameters: name, namelist, valuelist
    search: if name /* S */ = hd(namelist /* S */) goto found else continue
            /* This code needs to be massaged to check for 
             * a nil namelist and take appropriate action. */
  continue: valuelist /* D */ = tl(valuelist)
            namelist  /* S */ = tl(namelist)
            goto search
     found: value /* D */ = hd(valuelist)
We will work through p.e. of this code with name = z and namelist = (x,y,z):
Selected from poly     | Generated                   | Inserted into poly
(also labels for new   | (bodies of new blocks)      |
 program pts)          |                             |
------------------------------------------------------------------------------
(search(z,(x y z)))    : goto(cont(z,(x y z)))       | (continue, (z,(x y z)))
------------------------------------------------------------------------------
(continue,(z,(x y z))) : valuelist = tl(valuelist)   |
                         goto(search(z,(y z)))       | (search (z,(y z)))
------------------------------------------------------------------------------
(search (z,(y z)))     : goto(cont(z,(y z)))         | (continue (z, (y z)))
------------------------------------------------------------------------------
(continue, (z, (y z))) : valuelist = tl(valuelist)   |
                         goto(search (z, (z)))       | (search (z, (z)))
------------------------------------------------------------------------------
(search (z, (z)))      : goto(found, (z,(z)))        | (found (z, (z)))
------------------------------------------------------------------------------
(found (z, (z)))       : value = hd(valuelist)       |
\_______________________  ___________________________/
                        \/ 
            Together, these form new_prog
This example is simple because the branch is always static.  We also generated
jumps to jumps.  After transition compression, we would have:
valuelist = tl(valuelist) valuelist = tl(valuelist) value = hd(valuelist)Or, if we had great transition compression:
value = hd(tl(tl(valuelist)))
A: ~~~~~~~~ (vs1)    B: ~~~~~~~~ (vs3)
   ~~~~~~~~   |         ~~~~~~~~   |
   ~~~~~~~~   |         ~~~~~~~~   |
   ~~~~~~~~   |         ~~~~~~~~   |
   ~~~~~~~~   v         ~~~~~~~~   v
   goto foo (vs2)       goto foo (vs2)
foo: ~~~~~~~~
     ~~~~~~~~
     ~~~~~~~~
     ~~~~~~~~
     ~~~~~~~~
From the foo block, we may generate:
(foo, vs2): ~~~~~~~~
            ~~~~~~~~
            ~~~~~~~~
            ~~~~~~~~
            ~~~~~~~~
We could also generate:
(A, vs1): ~~~~~~~~         (B, vs3): ~~~~~~~~
          ~~~~~~~~                   ~~~~~~~~
          ~~~~~~~~                   ~~~~~~~~
          ~~~~~~~~                   ~~~~~~~~
          goto (foo,vs2)             goto (foo,vs2)
If we are doing transition compression on the fly, we would not end (A,vs1) and (B,vs3) with goto (foo, vs2). Instead, we would copy (foo,vs2) to the location of the goto. This strategy has the drawback that the size of the code can blow up. If (foo,vs2) contains more jumps, we could have more code blow-up -- we could potentially have quadratic code blow-up. We might want to control this somehow.
Another problem can arise. Consider the set vs2 at the end of the original block A. For the end of A, we wanted to generate goto (foo, vs2). Therefore, we needed to generate the block (foo, vs2). What if there is another set vs4 at the end of another block that jumps to (foo, vs4). Then we may need to generate the new block (foo,vs4). However, we do not want to generate to different blocks (foo,vs2) and (foo,vs4) if the only difference between vs2 and vs4 is in the values of dead variables.
A concrete example of this potential problem is
Start: ifNaively, we might generate:then a:=1; ...... a .......; goto continue /* This will become * goto (cont, (1)) */ else a:=2; ...... a .......; goto continue /* This will become * goto (cont, (2)) */ continue: ~~~~~ \ ~~~~~ > No use of a ~~~~~ / a:=3 ~~~~~ ~~~~~ ~~~~~ 
(continue,(1)): ~~~~~            (continue,(2)): ~~~~~
                ~~~~~                            ~~~~~
                ~~~~~                            ~~~~~
	        a:=3                             a:=3
                ~~~~~ <---- stores the --------> ~~~~~
                ~~~~~       same at this         ~~~~~
                ~~~~~       point                ~~~~~
These basic blocks are identical; we do not want two copies of them.
There are two possible solutions:
Instead of using int in the previous equation, let's try using some other program p:
Notice that I said ``a'' generating extension for p, not ``the'' generating extension for p. In general, there are many generating extensions for p, of which pep is just one. Moreover, we can state the constraint for a program ge-p to qualify as a generating extension for p, as follows:
How do we create generating extensions? In fact, we already know one method—namely, use a self-applicable partial evaluator to create [[pe]][pe,pe] = pepe. The program pepe is a tool for creating generating extensions because, for all p, [[pepe]][p] = [[pe]][pe,p] = pep! In other words, pepe is a generating-extension generator.
However, there are other methods for constructing generating extensions that do not involve such heavy-weight machinery as self-applicable partial evaluation, which is what we explain next.
Consider the following straight-line-code example Q:
int a; int b; int x; int y; int z; int m; int n; int o; int r; read a; read b; x = a*a; y = a*b; z = b*b; m = x + 3*a; n = y + 4*a; o = z + 5*a; r = m*n*o; return(r);We first do binding-time analysis as usual, and obtain the following division: static variables = {a, x, m}; dynamic variables = {b, y, z, n, o, r}. We then create the generating extension for Q as the following piece of code, which declares the dynamic variables to be of type INT and the static variables to be of type int:
  int a; INT b("b");
  int x; INT y("y"); INT z("z");
  int m; INT n("n"); INT o("o"); INT r("r");
  read a;
  emit("read b;")
  x = a * a;
  y = a * b;
  z = b * b;
  m = x + 3 * a;
  n = y + 4 * a;
  o = z + 5 * a;
  r = m * n * o;
  emit("return(r);")
Above, we've used underlined symbols to denote INT operations and non-underlined symbols
to denote int operations.
Class INT is a class that redefines each of the operators that can be used in straight-line code: arithmetic operators, shift operators, bit-manipulation operators, and assignment. When each such operator is evaluated, the result is an abstract-syntax tree (of type AST, say), rather than a value of type int. For instance, class INT looks something like the following:
  class INT {   // Reinterpretation of type int that yields integer abstract-syntax trees
    INT(int n);                            // Constructor, which converts the value n from int to INT
    INT& operator=(const INT &x);          // Build an assignment AST and append it to the tail of a global list of ASTs
    INT& operator+();                      // Build a UnaryPlusExp node
    INT& operator+(INT& x);                // Build a PlusExp node
    friend INT& operator+(int& x, INT& y); // Build a PlusExp node
    friend INT& operator (INT& x, int& y); // Build a PlusExp node
    INT& operator-();                      // Build a UnaryMinusExp node
    INT& operator-(INT& x);                // Build a MinusExp node
    INT& operator*(INT& x);                // Build a TimesExp node
    ...
    INT(std::string s);                    // Constructor that builds a leaf node that represents a dynamic variable; a declaration AST is appended to the tail of a global list
    AST tree;
  };
When the generating extension for Q is executed with the input 9 for a, the result is a list of ASTs. When the list is unparsed (pretty-printed), we obtain the following program:
int b; int y; int z; int n; int o; int r; read b; y = 9 * b; z = b * b; n = y + 36; o = z + 45; r = 108 * n * o; return(r);
The example above shows how a basic block is handled. In general, for a basic block BB we will refer to the version of BB transformed (roughly) as above with INT variables as BBINT.
To create a generating extension for a program with branches (including loops), we need to address two issues:
  l1: if exp goto l2 else goto l3   // Basic block BB1
  l2: BB2
      goto l4
  l3: BB3
      goto l4
  l4: BB4  
The generating extension will contain four basic blocks—one for each of
the four basic blocks in program P—together with a fifth ``control
block,'' which has the following structure:
  control_block:
    if (Pending contains no unmarked pair)
      goto exit
    else
      goto control_block1
  control_block1:
    Select an unmarked 〈label,σ〉 pair from Pending
    Mark 〈label,σ〉
    ASTList = emptyList
    Change the state to σ   // (re-)establish the state of the non-INT variables
    goto label              // Use a computed goto or switch
For basic block BB1, the generating extension contains the following code:
  l1: genlabel("l1",state);
      tmp = exp;           // yields a simplified AST of class ExpNode
      state = snapshot();  // capture the state of the non-INT variables after evaluating exp in case exp contains side-effects
      generate("if (");
      tmp.Unparse();
      generate(")");
      genjmp(l2,state);
      generate("else");
      genjmp(l3,state);
      Insert(Pending,l2,state);
      Insert(Pending,l3,state);
      goto control_block;
For basic block BB2, the generating extension contains the following code:
  l2: genlabel("l2",state);
      BB2INT              // int/INT-transformed code for BB2
      Unparse(ASTList);  // Emit the residual code
      genJmp(l4,state);
      Insert(Pending,l4,state);
      goto control_block;
and the latter translation is also performed for BB3 and BB4.
The control block accesses Pending and repeatedly dispatches to the appropriate int/INT-transformed
basic block.
In this way, each basic block can be executed multiple times, and each time it produces
code that is added to the (growing) residual program.
The operator-overloading method was used circa 1995 in the CMix partial evaluator for C. CMix used a pipeline that involved both C and C++:
p.c ----BTA + gen.-ext. creation----> gen-p.cpp ----g++----> gen-p + input s ---------> ps.c ----gcc----> ps [C] [C++] [a.out] [C] [a.out]
With a functional language, we will need to address function calls. We may have to do partial evaluation for a program point a number of times. This is also the case during partial evaluation of a program written in our flowchart language, but in a functional language it can happen because of different calls, or sequences of calls, to the procedure that contains the program point. For some calling contexts, we may know that a given variable is static; for other calling contexts, that same variable may be dynamic.
A monovariant division will use one classifiction for each program point; a polyvariant division will possibly use multiple divisions for each program point (i.e., it is non-uniform).
Here is a chart contrasting and comparing PE for flow-chart languages and first-order functaional languages. (By ``first order'' we mean that only named functions are allowed.)
Feature of PE        | Imperative/Flow Chart     | First-order functional
                     |                           | (pp's will focus on fn. 
                     |                           | entries)
----------------------------------------------------------------------------
Binding              | Assignment to a global    | Values bound to params;
                     |                           | bindings created by fn.
                     |                           | application
----------------------------------------------------------------------------
Static/Dynamic       | Classification on globals | Classifications on params
----------------------------------------------------------------------------
Monovariant div.     | 1 div. per prog. pt.      |   
--------------------------------------------------    see graph for disc.
Polyvariant div.     | Many div. per prog. pt.   |
--------------------------------------------------
          ~~~~~~~~~~~          ~~~~~~~~~~~          ~~~~~~~~~~~
          ~~~~~~~~~~~          ~~~~~~~~~~~          ~~~~~~~~~~~
          call f(D,S)          call f(S,S)          call f(S,D)
                 \                  |                    /
                  \                 |                   /
                   +------------+   |   +--------------+
                                 \  |  /
                                  v v v
                               f( .., .. )
                               ~~~~~~~~~~~
                               ~~~~~~~~~~~
We might have many different binding-time-analysis results for the different calls
to f.
Comparison chart continued:
Feature of PE        | Imperative/Flow Chart     | First-order functional
--------------------------------------------------------------------------
Congruence           | D begets D                | D params beget D params
                     |                           |
                     |                           | g( ... W /* D */ ...)
                     |                           | ~~~~~~ | ~~~~~~~~~~~~
                     |                           | ~~~~~~ V ~~~~~~~~~~~~
                     |                           | f( ... W ... )
                     |                           |        |
                     |                           |        |
                     |                           |        V /* D */
                     |                           | f( ... X ... )
                     |                           | /* X is dyn, b/c W is */
---------------------------------------------------------------------------
Specialized prog. pt.| (pp,vs)                   | (f,vs)
                     | /* pp was alabel */       |  f is a function or 
                     |                           |  manufactured from arm
                     |                           |  of dynamic condition 
                     |                           |
---------------------------------------------------------------------------
Other concepts that we need include reduction, transition compression.
In Scheme0, we will have a list of function defintions. The first function will be the ``goal function.'' The partial evaluator will start with the goal function.
The syntax of Scheme0 is similar to LISP:
expr -> const          /* nil, t, and numeric constants */
     | (quote a)       /* an sexpr or list constant a */
     | var
     | (if expr expr expr)
     | (call funcname arglist)
     | (áopñ exp .... exp)
A division is a classification of parameters. We may have different classifications for different functions. We will be working with the assumption that we have a monovariant division. This means that there is only one classification per function (but different functions can have different classifications).
We need to worry about maintaining congruence:
.....(call g .... ej /* D */ ....)  .... (call g .... ej' /* S */...) ....
                   \                                   /  ^
                    \___________   ___________________/   |
                                \ /                       |
                                 V                        |
                    (def (g .... xj ... ) ...)            |
                                 ^ This should be         |
                                   classified as D        |
                                                          |
                                              Here we have a Static argument, 
                                              but we have decided that the
                                              formal parameter is Dynamic.
                                              We perform a "lift" on this
                                              argument.  A lift is a coercion
                                              to cause this expr to be treated
                                              as if it is Dynamic.
Note that we may still have many different versions of the function g corresponding to different static values being passed into the parameters. This is fine in a multivarient division. In a polyvariant division, we may also have different versions of g with different classifications.
Our specialized program points will look like (g.vs) (i.e., a cons cell to bundle a function name with values for the static parameters).
Transition Compression:
flowchart language              | Scheme0
----------------------------------------------------------------------
case goto pp'                   | In Scheme0, we will do call unfolding:
     bb := lookup(pp', program) | (call f .... ej ....) => a copy of
                                | f's expr w/ the ej's substituted for 
--------------------------------/ the formals.
Call unfolding can cause problems:
(define (f x) ..... x ..... x ..... x .....)unfolded, f may become
                       => ..... e ..... e ..... e .....
                                ^       ^       ^
                                \       |       /
                                 ------\|/-----/
                                        V
                         e is evaluated multiple times.  We can work around
                         employing let expressions (see [Jones et al. 93])
Transition Compression for Scheme0, continued:
We will unfold only those functions for which all arguments are static.
For the purpose of comparing Scheme0 evaluation with the specialization/simplification method for Scheme0 that we give below, we now give a call-by-value interpreter with static scoping.
eval[exp, env, program] <-
  cases exp
    nil: nil
    t: t
    n: n                 /* n a number */
    (quote c): c
    z: vz, where ((var1.val1) ... (z.vz) ... (varn.valn)) = env
    (if e1 e2 d3): if eval[e1, env, program]
                         then eval[e2, env, program]
                         else eval[e3, env, program]
    (call f (e1 ... en)): eval[ef, env', program],
               where (define (f (x1 ... xn) ef) = lookup[f,program]
               and vj = eval[ej, env, program], for j = 1, ..., n
               and env' = list[(x1.v1), ..., (xn.vn)]
    (áopñ e1 ... en):
               perform_op[áopñ, eval[e1, env, program], ..., eval[en, env, program]]
In contrast to the two-phase strategy that we used for partial evaluation of the flowchart language, the partial evaluator for Scheme0 will have three phases:
During binding-time analysis (see below), we will annotate the abstract syntax trees with the binding-time information. The specializer will work on the annotated AST.
For this purpose, we introduce a 2-level syntax:
expr -> const          /* nil, t, and numeric constants */
     | (quote a)       /* an sexpr or list constant a */
     | var
     | (ifs expr expr expr)
     | (ifd expr expr expr)
     | (calls funcname (arglist /* S */) (arglist /* D */))
      /*     \                            \              /
       *      The s stands for Static      This list should be empty for calls
       * calls is an indication to perform transition compression (unfolding)
       */
     | (calld funcname (arglist /* S */) (arglist /* D */))
     | (áopñs e1 .... en)
     | (áopñd e1 .... en)
     | (lift exp)  /* A static expression that needs to be residuated */
Thus, if we have a function definition
(define (f x1 .... xn) e),the definition would become
  (define (f (xs1 ... xsm /* static params */)
             (xd1 ... xdk /* dynamic params */)
             e-annotated))
The expression e-annotated is generated as follows:
The method described here differs from that in [Jones et al. 93]. We will build a data-dependence graph. Consider this example:
(define (f1 x1 x2 x3) .......
        (call f2 (+ x1 x2) 3 (+ x1 x3)) .......
        (call f2 x3 x1 x2) ....... )
(define (f2 a b c) .... (call f2 (op a) (op' b) (op'' c)) .... )
In the data-dependence graph for this function, we create a node for
each formal and actual parameter.  We will pass information along the
edges of the graph.
                                              return
                             x1    x2     x3   info
   +-------------- define f1  o     o      o    []  ----------------------+
   |                         (A)   (B)    (C)   (D)                       |
   |                       (to E) (to E) (to G)                           |
   |                       (to j) (to K) (to I)                           |
   |                                                                      |
   |                            (to D)                             (to D) |
   |             (E)   (F)   (G)  (H)               (I)   (J)   (K)  (L)  |
   +---- call f2  o     o     o   []        call f2  o     o     o   [] --+
                (to M)(to N)(to O)                 (to M)(to N)(to O)
                                              (to T)
                                              (to H)
                                              (to L)
                             (M)   (N)   (O)   (P)
            +----  define f2  o     o     o     []  ------+
            |               (to Q)(to R)(to S)            |
            |                                             |
            |                                 (to P)      |
            |                (Q)   (R)   (S)   (T)        |
            +-----   call f2  o     o     o     [] -------+
                            (to M)(to N)(to O)
As another example, consider the append function:
(define (app xs ys)
   (if (null? xs)
       ys
       (cons (car xs)
             (call app (cdr xs) ys)
       )
   )
)
In the graph, we start by optimistically labeling everything Static that
we can.  The arguments will be labeled according to the division we have found
for our variables.  We then adjust the labels by pushing the
dynamic labels over the directed arrows.  On the following graph, we are
considering:
(call append (1 2 3) ys)which has the division [S D] for the actual arguments. (In the graph, nodes are labeled either S or D. S>D means that something we assumed was static is changed to dynamic.)
+-------------------------+ | +----------------+ | | | V V | | xs ys +-----+ | |define append So Do [] S>D | | | |\ |\ ^ ^ ^ | | | | \ | +--+ / | | | | | +---+-----+ | | | | | | | | | | V V | | | | call append So S>Do []<---+ | | ^ ^ S>D | +----------------+ | +-------------------------+With this binding information, we can make the annotated program. For our function append, the first list is static, and the second list is dynamic.
(define (app (xs) (ys))
        (ifs (null?s xs)
         ys
        (consd (lift (cars cs))
               (calld app
                  ((cdr xs))
                  (ys)
               )
        )
)
We perform lifting when e is in a dynamic context. An expression is in a dynamic context in any of the following situations:
 (1) the expression is a dynamic argument list of a calld
     fk:  S S D D          /* We have this division for fk */
     (call fk S1 S2 S3 D)  /* Here we need S3 to be left as an expression */
      => (calld fk (S1 S2) ((lift S3) D))
 (2) the expression is an arglist of an opd
 (3) the expression is a subexpression of an ifd
 (4) the expression is a branch of an ifs in a dynamic context
 (5) the expression is the body of a goal function (to avoid disappearence of the goal function!)
 (6) the expression is the body of a function that has >= 1 dynamic parameter.
Note that the definition is recursive because of case (4).
      if => ifs    if the branch-expression is S
         => ifd    otherwise
      op => ops    if all arguments are static
         => opd    otherwise
      (call f arglist) => (calls f (arglist)())                if all actuals are S
                       => (calld f (arglist s) (arglist d))    otherwise
      e => (lift e)     if e is in a dynamic context
     
specialize[program, vs0] {
  let ( (define (f1 ...) ...) ...) = program in   // the goal function
  rprog = ()  // empty list of functions
  pending = { (f1.vs0) }
  marked = ∅
  while (pending ≠ ∅) {
    select and remove a pair (f.vs) from pending
    marked = marked ∪ { (f.vs) }
    let (define (f (x1 ... xm) (xm+1 ... xn)) e) = lookup[f,program]
    let (vs1 ... vsm) = vs      /* decompose the list vs */
    let evs = simplify[e, (x1 ... xm, xm+1 ... xn), (vs1 ... vsm, xm+1 ... xn)]
       /* Note that the "value" of xj is xj itself, for m+1 ≤ j ≤ n */
    pending = (pending  ∪ successors[evs]) - marked
    rprog = append[rprog, list[define, list[cons[f,vs], xm+1 ... xn], evs] :: nil]
  }
  return rprog
}
Note that the goal function is the first function processed (and residuated);
thereafter, new functions are appended to the end of rprog so that the (residual)
goal function stays as the first function on the list.
simplify[exp, names, values] <-
  cases exp
    nil: nil
    t: t
    n: n                 /* n a number */
    (quote c): c
    yj: vj, where (y1 ... yj ... yk) = names
             and (v1 ... vj ... vk) = values
    (ifs e1 e2 d3): if simplify[e1, names, values]
                       then simplify[e2, names, values]
                       else simplify[e3, names, values]
    (ifd e1 e2 d3): list[if, simplify[e1, names, values],
                             simplify[e2, names, values],
                             simplify[e3, names, values]]
    (calls f (e1 ... em) (em+1 ... ea)):
               simplify[ef, list[x1 ... xa], list[e1' ... ea']]
               where (define (f (x1 ... xm)(xm+1 ... xa) ef) = lookup[f,program]
               and ej' = simplify[ej, names, values], for j = 1, ..., a
    (calld f (e1 ... em) (em+1 ... ea)):
               list[call, (f::(e1' ... em')), em+1' ... ea']
               where ej' = simplify[ej, names, values], for j = 1, ..., a
    (áopñs e1 ... ea):
               perform_op[áopñ, simplify[e1, names, values], ..., simplify[ea, names, values]]
    (áopñd e1 ... ea):
               list[áopñ, simplify[e1, names, values], ..., simplify[ea, names, values]]
    (lift e): list[quote, simplify[e, names, values]]
  (define (power x n)
    (if (= n 0)
        1
        (* x (call power x (- n 1)))))
  BTA (init):  def pow   D.(x)  S.(n)  So      (given [D,S])
               ---
               call pow  S.     S.     So
               ----
                           _____________
                          /        ____ |
                          |       /    ||
                          |      |     vv
    (result):  def pow   D.(x)  S.(n)  Do--
               ---        | ^   | ^    ^   \
                          | |   | |    |    |
                          v  \  v  \   |    |
               call pow  D.  |  S. |   Do<-/
               ----       |  |  |  |
                           \_|   \_|
                     S        D
                ----------- -----
  (call d power ( (- n 1) ) ( x ) )
Work bottom-up:
prog =
  (define (power (n) (x) )
    (ifs (=s n 0)
      (lift 1)
      (*d x (calld power ( (-s n 1) ) ( x ) )))))
[See [Jones et al. 93], Figures 5.6 & 5.7,]
=> specialize[program, (3)]
   Before the while loop:
       f1 = power
       pending = { (power.(3)) }
   In the while loop:
       f = power
       vs = (3)
       evs = simplify[(ifs (= x n 0) ...), (n x), (3 x)]
                                 -------------------  -----  -----
                                   body of program    names  "values"
This situation leads to 3 calls on simplify:
   (i) simplify[(-s n 1), (n x), (3 x)]  \
               = 2                        |
                                          | processing of calld
  (ii) simplify[x, (n x), (3 x)]          |
               = x                       /
 (iii) simplify[x, (n x), (3 x)]
               = x
and produces
   evs = (* x (call (power (2)) x))
   marked = { (power.(3)) }
   pending = (∅ ∪ { (power.(2)) }) - { (power.(3)) }
   rprog = ( (define ((power.(3)) x) (* x (call (power.(2)) x))) )
Iteration 2 works on (power.(2)):
   ...
   evs = (* x (call (power (1)) x))
   marked = { (power.(3)), (power.(2)) }
   pending = (∅ ∪ { (power.(1)) }) - { (power.(3)), (power.(2)) }
   rprog = ( (define ((power.(3)) x) (* x (call (power.(2)) x)))
             (define ((power.(2)) x) (* x (call (power.(1)) x)))
           )
Iteration 3 works on (power.(1)):
   ...
   evs = (* x (call (power (0)) x))
   marked = { (power.(3)), (power.(2)), (power.(1)) }
   pending = (∅ ∪ { (power.(0)) }) - { (power.(3)), (power.(2)), (power.(1)) }
   rprog = ( (define ((power.(3)) x) (* x (call (power.(2)) x)))
             (define ((power.(2)) x) (* x (call (power.(1)) x)))
             (define ((power.(1)) x) (* x (call (power.(0)) x)))
           )
Iteration 4 works on (power.(0)):
   ...
   evs = (quote 1)
   marked = { (power.(3)), (power.(2)), (power.(1)), (power.(0)) }
   pending = (∅ ∪ ∅) - { (power.(3)), (power.(2)), (power.(1)), (power.(0)) }
   rprog = ( (define ((power.(3)) x) (* x (call (power.(2)) x)))
             (define ((power.(2)) x) (* x (call (power.(1)) x)))
             (define ((power.(1)) x) (* x (call (power.(0)) x)))
             (define ((power.(0)) x) (quote 1))
           )
Thus, the return value from "specialize[program, (3)]" is
  (   (define ((power.(3)) x) (* x (call (power.(2)) x)))
      (define ((power.(2)) x) (* x (call (power.(1)) x)))
      (define ((power.(1)) x) (* x (call (power.(0)) x)))
      (define ((power.(0)) x) (quote 1))
  )
If we had a more powerful unrolling strategy, the end result
would be:
( (define ((power.(3) x) (* x (* x (* x 1)))) )
The material below is taken from Section 5.2 of [Jones et al. 93]. However, some problems came to light when the TA implemented a partial evaluator for Scheme0 in Spring 2007. In particular, their equations for binding-time analysis do not seem to account for a certain kind of congruence issue that arises when a function that always has static arguments calls a function that can have dynamic arguments. A discussion of this issue, along with a modified set of equations for binding-time analysis can be found here.
Goal: mapping from function names to a vector of S's & D's
                                     ---------------------
                                    "binding times for the
                                     function's parameters"
Expressed with 3 functions:
  B   B   div
   e   v
Method: "Abstract Interpretation" (a.k.a. flow analysis)
                                                         _
    t   \in BindingTime = {S,D}       lattice:  D     S |_ D
                               *                |        -
    Tau \in BTEnv = BindingTime                 S    "S approximates D"
                                                     "D subsumes S"
    div \in Monodivision = Funcnames -> BTEnv
      _
NB:  |_  extends to BTEnv and div pointwise
---   -             -----     ---
                  _    ,     ,           _
  (1) (t ... t ) |_  (t ... t ) iff  t  |_  t   1 <= i <= n
        1     n   -    1     n        i  -   n
            _                      _
  (2) div  |_  div   iff div (f ) |_  div (f )  1 <= i <= k
         1  -     2         1  i   -     2  i
    ( we want the least dynamic solution -- i.e., most static possible)
  |_| : join  "least upper bound"    S |_| S = S
              "max" "highest"        * |_| D = D , D |_| * = D
  B [| e |] : BTEnv -> (Funcname -> BTEnv)
   v
    -- used to define the BTEnv transmormation from the BTEnv
       on entry to a function f to the the BTEnv at all call
       sites on a function g within f
        -----------  def  f   .   .   .   o  --------------------
       /                     /    |  /|                          \
      /                 ____/      \/ |                           \
     |                 / __________/\  \_____________________      |
      \               / /            \                       |     /
       \             v v              v                      v    /
        --  call g . . .       call g . . .     call h . . . . ---
  B [| e |] Tau g   -- just interested in g
   v
  B [| e |] Tau h   -- just interested in h
   v
                    |   |
  B [| e |] Tau g = |___| (vector for the kth use of g in e)
   v                  k
               ______ vector of S's & D's at entry of functions
              /
             v
  B [| c |] Tau g = (S, ..., S)  <- length = # parameters of g
   v
  B [| x  |] Tau g = (S, ..., S)
   v    1
  B [| if exp1 exp2 exp3 |] Tau g =
   v
      B [| exp1 |] Tau g  |_|  B [| exp2 |] Tau g  |_|  B [| exp3 |] Tau g
       v                        v                        v
                                n
                              |   |
  B [| op e ... e  |] Tau g = |___| B [| e  |] Tau g
   v       1     n             j=1   v    j
  B [| (call f e ... e ) |] Tau g =
   v            1     n
                n
              |   |
      let t = |___| B [| e  |] Tau g in
               j=1   v    j
        / t       if f != g
       < 
        \ t |_| (B [| e |] Tau ... B [| e  |] Tau) if f = g
                  e    1            e    n
  B [| e |] : BTEnv -> BindingTime
   e
  B [| e |] Tau =
   e
	case switch on e:
                    c   ->  s
                    x   ->  Tau  (jth component of Tau)
                     j         j
                             3
                           |   |
           if e  e  e   -> |___| B [| e  |] Tau
               1  2  3      j=1   e    j
                             3
                           |   |
         (op e  ... e ) -> |___| B [| e  |] Tau
              1      n      j=1   e    j
(once again we are looking for the least dynamic congruent division)
  div g =  |_| (vector for jth call site on g)
  -----     j              -------------
    ^                           ^
    |                           |
    |                            \
    \                             anywhere in the program
     this expresses congruence
                    (S)                                 (D)
     ... (call g ... e ...)        ......    (call g ... e  ...)
                      k                                   k
                      \_______________     ______________/
                                      \   /
                                       v v
                         (define (g ... x ...) ...)
                                       (D)
             n
           |   |
  div f  = |___| B [| e  |] (div f ) f
       k    i=1   v    i          i   k
  One equation for k = 1 ... n
  Start with div  = [f -> Tau , f -> (S, S, ... S), ... f  = (S, S, ... S)]
                0     1      1   2                       n
  Example
  -------
  (div power) = B [| e      |] (div power) power
                 v    power
              = B [| (= n 0) |] (div power) power
                 v   -------    -----------------
                      (S,S)         ( ... ) ...
            |_| B [| 1 |] ( ... )
                 v
            |_| B [| (* x (call power ...)) |] ( ... ) ...
                 v
                      _____ (S,S)
                     /
                   -----
              = B [| x |] ( ... ) ...
                 v
                         _____ (S,S)
                        /
                     -------
            |_| B [| (- n 1) |] ...
                 v
            |_| ( B  [| x |] (div power), B  [| (- n 1) |] (div power) )
                   e                       v
                 \-----------------------------------------------------/
                                           |
                                        ^  |           ^
                           ( (div power)|1, (div power)|2 )
              = (S,S) |_| (S,S) |_| (div power) = (div power)
     ===> the first approximation is stable (i.e., congruent)
     ===> compare with walking through the graph, pushing D's
          through the graph