cps & contification

Matthew Fluet Matthew Fluet <fluet@CS.Cornell.EDU>
Tue, 16 Jan 2001 14:59:16 -0500 (EST)


> Here is my attempt at defining "safety" constraints for contification.
> 
> Abstractly, a program consists of a main function and a set of tail and nontail
> calls.  A tail call consists of a caller and a callee.  A nontail call consists
> of a caller, callee, and jump.
> 
> 	j in	Jump
> 	f in	Fun
> 		TailCall = Fun x Fun
> 		NonTailCall = Fun x Fun x Jump
> 	p in	Program = Fun x P(TailCall) x P (NonTailCall)

Just to make sure I have the terminology correct:

(f,g) in TailCall corresponds to something like:

fun f (...)
  = let
      fun L_1 (...)
        = g(...)
      fun L_2 (...)
        = ...
    in
      case 
        of true => L_1 | false => L_2
    end

I.e., the caller is f and the callee is g, with no notion that g is
really being called in L_1? or even deeper lexical nesting?

Second question, relating to the safety constraints below, is it possible
for a local function to be unused?  If so, then although g is
called in L_1, it may not be the case that g is really called by f.


(f,g,j) in NonTailCall corresponds to something like:

fun f (...)
  = let
      fun L_1 (...)
        = ...
      fun L_2 (...)
        = L_1(g(...))
    in
      ...
    end

Again, no notion that g is really being called in L_2, although it is
returning to the continuation L_1?  How about the possibility that L_2 is
never called; in which case does g really ever return to L_1?

> An analysis is a map from functions to the continuation that they return to.
> 
> 	A in Fun -> {Uncalled, Unknown} U Jump U Fun
> 
> Here is the intended meaning of A.
> 
> 	A(f)		meaning
> 	--------	------------------------------------------------
> 	Uncalled	f is never called during evaluation of the program
> 	Unknown		f may returns to different places
> 	j		f always returns to continuation j
> 	g		f always returns to function g
> 
> I now define a sufficient condition, called "safe", on an analysis in order for
> the contification transformation to be correct.
> 
> An analysis A is safe for program p = (fm, T, N) if 
> 	*   A(fm) = fm

Does        A(fm) = Unknown
make more sense?  I wouldn't think that the interpretation that the main
function fm always returns to fm is what we want to say; because the next
step would be to say that if A(g) = f in Fun, then g is contifiable in f.

> 	**  For all nontail calls (f, g, j) in N, either
> 		A(f) = Uncalled
> 		or A(g) in {Uncalled, Unknown, j}
> 	*** For all tail calls (f, g) in T, either 
> 		A(f) = Uncalled
> 		or A(g) in {Unknown, f, A(f)}

Assuming an exclusive or, if A(f) <> Uncalled, why can A(g) = Uncalled in
non-tail calls but not in tail calls.  This ties back to the question I
asked above, about whether local-functions can be unused.  If they can be
unused, then it seems that both calls could have A(g) = Uncalled.  On the
other hand, if local functions are always used, then in non-tail calls,
maybe we can have A(g) in {Unknown, j}.

> 3. If A(f) in Jump and there is a path of tail calls from f to g
>    then A(g) in {Unknown, A(f)} U Fun
> 	Proof: by induction on the length of the path.

Does this fact depend on A(f) in Jump, or just A(f) <> Uncalled?  

> So, my gut feeling is that we can do a two step algorithm:
> 
> 1. Run the continuation based analysis.
> 	For anything that's marked Uncalled or is in Jump, we can leave it at
> 	that. 
> 2. For all functions that are marked Unknown, we need to run a second analysis
> 	that determines if they can be contified within another function.  To 
> 	me this feels like doing some kind of scc or dominator calculation on
> 	the graph of tail calls, but I don't quite see it yet.

I agree that there is a graph feel to this problem, but I don't see it yet
either.