• No results found

Partial Evaluation

In this section we give an overview of partial evaluation that is used in Chapter 3. Partial evaluation follows from the observation that a one-argument function can be obtained from one with two arguments by fixing one of the input arguments. Partial evaluation performs this process to programs proceeding as follows: a partial evaluator is given a subject program p together with part of its input data, in1. It constructs a new specialised program pin1 which, when given p’s remaining input in2, will yield the same result that p would have produced given both inputs in1 and in2. We provide an example of this process below.

Example 6 (Partial Evaluation). Below we define a recursive program that com- putes xn. The program contains two inputs, namely, x and n.

int f(n , x) {

if (n == 0) then return 1;

else if ( even (n)) then return f(n/2 ,x) ˆ 2 else x * f(n -1 ,x)

Given we fix n= 5, we obtain the program below. int f5 (x) {

return x * (( x ˆ 2) ˆ 2) }

We are able to precompute all expressions involving n, to unfold the recursive calls to function f , and to reduce x ∗ 1 to x. This optimisation was possible because the program’s control is completely determined by n. If on the other hand x= 5 but n is unknown, specialisation gives no significant speedup.

Lombardi was first to coin the term Partial Evaluation in 1964 in reference to discussing Lisp’s ability to compute with incomplete information [96]. How- ever, as a theory, partial evaluation has its foundations in Kleene’s s-n-m theo-

2.2. Partial Evaluation 53 rem [97]. Kleene proved that for any given Turing machine for a general m+n- argument function f , and given values a1 to am of the first m arguments, there exists a Turing machine for the specialised function g = fa1,...am which satisfies g(b1,...,bn)= f (a1,...,am,b1,...,bn) for all b1to bn. Futamura [98] was the first to use partial evaluation in the context of program transformation and considered the application of the partial evaluator to itself, thus deriving compilers, compiler gen- erators and compiler generator generators in form of semantic equations. The first implemented partial evaluator can be traced to Redfun, a partial evaluator for Lisp. This work also mentioned the possibility of a compiler generator (generator), simi- lar to Futamura. Around the same time Turchin proposed the idea of partial evalua- tion in the context of symbolic computation of functional languages. In the mid-80s Jones, Sestoft, and Søndergaard developed implemented first self-applicable par- tial evaluator written in Lisp and used as a compiler generator [99]. This line of work propelled a wide variety of research and applications of partial evaluation. These applications include parser and compiler generators [100], program transfor- mations [96], abstract interpretation [101, 102], security analysis [103], implemen- tation of Virtual Machines [104], and Model driven development [105, 106] among many other applications. A particularly interesting application of partial evaluation that is in the spirit of the approach presented in Chapter 3 is the partial evaluation of interpreters in model driven development to turn an interpreter into a translator. Here a partial evaluator is used to specialising a model interpreter with respect to a model to create a compiled model interpretation. Another related approach is ob- served in [104] allows uses to define languages solely by defining an interpreter. The system then uses the interpreter and partial evaluation to perform compilation independent of the language. Techniques relating to Datalog program transforma- tion such as [42, 107] can be seen as instances of partial evaluation.

2.2.1 Trivial Partial Evaluation

A partial evaluator is typically denoted as a Mix operator defined in Definition 2. The term Mix was given after mixed computation.

Definition 2 (Mix operator). An operator Mix is a partial evaluator iff ∀p, s,d : ~p(s,d) = ~~Mix(p, s)(d)

We say the program produced by ~Mix(p, s) is the residual program.

A partial evaluator thus performs a mixture of code execution and code gen- eration in the Mix operator. Thus the specialisation can be shown in our previous example as the following equation: p5 = ~Mix(p,5). The execution can be de- scribed as the following equation: out= ~p5(x).

2.2.2 Interpreter Partial Evaluation

A special case of partial evaluation that follows the work of Futamura [108] is when the program that is being partially evaluated is an interpreter. It follows then that we can construct a first Futamura projection:

Definition 3 (First Futamura Projection). Let int be an interpreter for the language L itself written in the language M. Then, for an arbitrary program p written in L and its input d we have:

~PL(d)= ~intM(p,d)= ~~Mix(int, p)M(d)

The implementation language of Mix is irrelevant for the purpose of this equa- tion. The equation in particular means that the residual program, i.e., ~Mix(int, p), is an M-program with the same operational behaviour as the L-program p.

2.2.3 Considerations in Partial Evaluation

2.2.3.1 Online vs Offline

There are two approaches to partial evaluation: online and offline. An online partial- evaluator is a non-standard interpreter. The treatment of each expression is de- termined at partial evaluation time. Online partial evaluators in general are very accurate but at the price of a considerable interpretive overhead. Offline partial evaluators are structured with a preprocessing phase and specialisation phase. The

2.2. Partial Evaluation 55 preprocessing phase employs a binding-time analysis to determine for each expres- sion whether it can be evaluated at partial-evaluation time or whether it must be evaluated at run-time. Once this information is determined, the specialisation is performed. The approach in Chapter 3 can be is offline (in a single specialisation phase) as the static parts are known ahead of time.

2.2.3.2 Termination

In the case of unfolding calls during function specialisation, partial evaluation can loop in two ways: either by unfolding infinitely many function calls or by creat- ing infinitely many specialised functions. Both of these issues can be avoided by defining a bound on the number of unfolded calls and the number of specialised functions, but often this strategy appears unsatisfactory. Hence more sophisticated techniques have been proposed, e.g., [109, 110]. As will be explained, in the ap- proach of Chapter 3 termination issues do not manifest due the fact that we do not unfold recursive rules, and instead, replace the rule with an interpreter definition which has termination characteristics on Datalog’s finite domain.

2.2.3.3 Performance Benefits

It is not always apparent if partial evaluation is beneficial for a given application. Given a time function t and a program p we say the execution time of p is t(p). Therefore, for a fixed two input program p with static input s and dynamic input d, the speedup function is defined as:

speedups(d)= ttp(s,d) ps(d)

In general, if speedups(d) > 1 for all s, and d changes more than s then partial evaluation is advantageous. In cases where, s and d both change frequently, the time to do specialisation must be accounted for and thus we desire the following to hold:

Remark. We note that many uses of partial evaluation are not motivated primarily by performance [103, 105, 106]. For example, the approach taken in [105] use partial evaluation as a method of correct compilation with respect to a model and an interpreter. A virtue is that the method yields target programs that are always correct with respect to the interpreter. Thus the problem of compiler correctness seems to have vanished. This approach is clearly suitable for prototype implementation of new languages from interpretive definitions (known as meta-programming in the Prolog community).

Our approach uses both benefits of partial evaluation: On one hand we per- form interpreter guided compilation, our residual program is correct by using an interpreter. On the other hand, we remove expensive run-time aspects (e.g., virtual dispatch) in the interpretor to produce faster residual programs.