cps & contification

Matthew Fluet fluet@CS.Cornell.EDU
Wed, 17 Jan 2001 12:55:50 -0500 (EST)


> An analysis A is safe for program p = (fm, T, N) if 
> 	*   A(fm) = Unknown
> 	**  For all nontail calls (f, g, j) in N,
> 		A(f) = Uncalled 
> 		or A(g) in {j, Unknown}
> 	*** For all tail calls (f, g) in T,
> 		A(f) = Uncalled
> 		or A(g) in {f, A(f), Unknown}
> 
> Define the function C: Analysis -> Analysis by
> 	C(A)(g) =
> 	     if g = fm
> 	       then fm
> 	     else if exists j such that forall (f, g, j') in N. j = j'
> 		                        and forall (f, g) in T. A(f) = j
> 	            then j
> 	          else g
> 
> Fact: C is monotone.
> 
> Let A' = lfp(C).  A' is the continuation based analysis.
> 
> Fact: A' is safe.

I don't think so.  Consider:

fun fm () = ... K1(f()) ... K2(f()) ...
fun f  () = ... g () ...
fun g  () = ... g () ...

Then T = {(f,g), (g, g)}, N = {(fm,f,K1), (fm,f,K2)}

   fm        f         g
Uncalled  Uncalled  Uncalled
   fm        f      Uncalled
   fm        f         f

But this violates * (because A'(fm) = fm <> Unknown) 
and ** (because A'(fm) <> Uncalled and A'(f) = f notin {K1, Unknown}
intersect {K2, Unknown}).

Also, the continuation based analysis should fail to determine any
functions are contifiable in the above set.

Changing C to the following restores safety:

 	C(A)(g) =
 	     if g = fm
 	       then Unknown
 	     else if exists j such that forall (f, g, j') in N. j = j'
 		                        and forall (f, g) in T. A(f) = j
 	            then j
 	          else Unknown

Let A' = lfp(C).  

Claim: A' is safe.
Proof:
*  A'(fm) = (lfp(C))(fm) 
          = if fm = fm     
              then Unknown
            else ...
          = Unknown

** Let (F, G, J) be an arbitrary element of N
   Suppose A'(F) = Uncalled.
    Then done.
   Suppose A'(F) <> Uncalled.
    Then A'(G) = (lfp(C))(G)
               = if G = fm
                   then Unknown
                 else if exists j such that forall (f, g, j') in N. j = j'
                                            and forall (f, g) in T. A'(f) = j
                        then j
                      else Unknown
               in {J, Unknown} 
               subset {J, Unknown}

*** Let (F, G) be an arbitrary element of T
    Suppose A'(F) = Uncalled.  
     Then done.
    Suppose A'(F) <> Uncalled.
     Then A'(G) = (lfp(C))(G)
                = if G = fm
                    then Unknown
                  else if exists j such that forall (f, g, j') in N. j = j'
                                             and forall (f, g) in T. A'(f) = j
                         then j
                       else Unknown
               in {A'(F), Unknown} 
               subset {F, A'(F), Unknown}

Now for the set of functions above, we have

   fm        f         g
Uncalled  Uncalled  Uncalled
Unknown   Unknown   Uncalled
Unknown   Unknown   Unknown

which corresponds to what we would expect for the continuation based
analysis.


> Now define a second analysis, A, based on A'.
> 
> 1. if A'(f) <> Unknown
>    then let A(f) = A'(f)
> 2. if A'(f) = Unknown and there exists a (g, f, j) in N
>    then let A(f) = Unknown
> 3. The only functions for which we have not defined A are only called in tail
>    position.  Define a directed graph G = (Node, Edge) where
> 	Node = Fun U {Root}
> 	Edge = {(f, g) | (f, g) in T and A'(g) = Unknown}
> 		U {(Root, f) | A'(f) <> Unknown}
> 		U {(Root, fm)}
>    Consider the dominator tree of G rooted at Root.
>    For any f in Fun for which we have not yet defined A(f), define A(f) to be
>    Unknown if the immediate dominator of f is Root.  Otherwise, define A(f) to be
>    the ancestor of f whose immediate dominator is Root.

Is this always well-defined?  

For the original A' and the set of functions above,
we assign A(f) = f, A(g) = f, A(fm) = fm (by rule 1) and all
elements of Fun have been defined.  But, A violates safety, for the
same reasons as above.

With the revised A' and the set of functions above,
we make no assignments by rule 1.  We assign A(f) = Unknown by rule 2.  We
construct the graph G with 
Node = {fm, f, g} and 
Edge = {(f, g), (g, g)} 
       U {}
       U {(Root, fm)}
which isn't a connected graph, so some nodes don't have an ancestor whose
immediate dominator is Root.

The problem seems to be that we really wanted an edge (Root, f).  I don't
quite understand the whole definition of Edge
Edge = {(f,g) | (f,g) in T and A'(g) = Unknown} 
       // this builds the tail call graph,
       //  but leaves out nodes we already figured out by Rule 1
       U {(Root, f) | A'(f) <> Unknown} 
       // this is the part I don't understand
       U {(Root, fm)}
       // ensure that Root always dominates fm

It seems to me that we want edges (Root, f) for everything that we know
must be Unknown.  Would the following definition make sense:

Edge = {(f,g) | (f,g) in T and A'(g) = Unknown}
       U {(Root, f) | A'(f) = Unknown and exists (g, f, j) in N}
       U {(Root, fm)}

i.e., roll rule 2 into the definition of G.