Partial Evaluation, also known as Program Specification , is a program optimization technique which generates specified programs by fixing one input to a particular value. In other words, if a program takes more than one input, and one of the inputs varies more slowly than the others, then specialization of the program with respect to that input gives a faster specialized program. Moreover, very many real-life programs exhibit interpretive behaviors.
A computer program can be seen as a mapping P of input data into output data:
P: {INPUT static
, INPUT dynamic
} → OUTPUT
INPUTstatic: the part of the input data known at compile time
INPUTdynamic: the part of the input data known at run time
If we write it like this:
P: INPUT static
→ {INPUT dynamic
→ OUTPUT}
The residual in the bracket can be seen as specialized program.
A partial evaluator is an algorithm which, when given a program and some of its input data, produces a residual or specialized program . Running the residual program on the remaining input data will yield the same result as running the original program on all of its input data.
This kind of computation is Staged Computation , since it is divided into several stages, including compile time, link time, run time. Details see below.
Example:
Regular expression
RE → NFA → DFA
Runtime code generation:
Packet filters
Synthesis kernel
Fast path optimization
Applications:
Visualization of Multi-dimensional data sets
Pattern recognition
Computer graphics by 'ray tracing'
Two-stage computation:
Compiler : generates a target program in some target language from a source program in a source language
Parser generator : program generator to generate a parser from a context-free grammar
Compilers and parser generators first transform their input into an executable program and then run the generated program on runtime inputs for a compiler, or on a character string to be parsed. The figure compares two-step compilative program execution with one-step interpretive execution.
Give an example:
Function pow (n, x) = if (n=0) then 1 else if even (n) then ( pow (n/2, x))
2 else x * pow (n-1, x)
If we want to specialize function pow when static input x equals 3. The process is as follows: pow
3
(x) = if (3=0) then 1
else if even (3) then ( pow (3/2, x))
2
else x * pow (3-1, x) pow (2, x) = if (2=0) then 1
else if even (2) then ( pow (2/2, x))
2
else x * pow (2-1, x) pow (1, x) = if (1=0) then 1
else if even (1) then ( pow (1/2, x))
2
else x * pow (1-1, x) pow (0, x) = if (0=0) then 1
else if even (0) then ( pow (0/2, x))
2
else x * pow (0-1, x)
Thus, pow
3
(x) = x * (x * 1)
2
.
Let L be a programming language, L-program be the set of well-format program to L. If p is a program in language L, denotes its meaning, typically a function from several inputs to an output. The subscript L indicates how p is to be interpreted.
The meaning of p: input → output
Let S be a “source” language, then Interpreter:
Let T be a “target” language. Compiler S → T:
Programming Specialization:
Where r means residual program
Partial evaluation gives a remarkable approach to compilation and compiler generation.
For example, partial evaluation of an interpreter with respect to a source program yields a target program. Thus compilation can be achieved without a compiler, and a target program can be thought of as a specialized interpreter.
Kleene presented it in 1952:
It is also called the iteration theorem, which makes use of the lambda notation introduced by Church. Let denote the recursive function of k variables with Gödel number x .
Then for every and , there exists a primitive recursive function s such that for all x , , ..., ,
A direct application of the s-m-n theorem is the fact that there exists a primitive recursive function such that
For all x and y
The s-m-n theorem is applied in the proof of the recursion theorem. The s-m-n theorem is the theoretical premise for a branch of computer science known as partial evaluation.
Residual:
: A program (L-program X input) → L-program
: Executable or target 1 st
Futamura Projection
: produce compiler
: Compiler generator
2 nd
Futamura Projection
3 rd
Futamura Projection
: Self-application partial evaluation
Definition: a computational state is a pair σ = (prog-pt, store) store: variable → value
What to classify variable as either static or dynamic?
Definition: A partial computational state is a pair (prog-pt, static-store)
Definition: A division is a classification of variables, dynamic or static classes
For example, a computation state (l, s)
Where σ s
, σ’ are static stores
Definition: A division is uniformly congruent if for any transition (l
1
, σ
1
) → (l static value of σ
2
can be computed from the static value of σ
1
, that is
2
, σ
2
); the
.
For a “flow-chart” language
Ploy: (prog-pt * static stores) → code
For example:
This online PE may cause program:
L: if y ≠ 0 then begin x := x + 1 y := y + 1 goto l target
end
Simple BTA x i
are inputs, y j
are locals
L: B o
= {x i
<-> y j
| y j
belongs to {D, S}} is initial division of inputs
How to process:
1) B = B o
U {y j
-> S}
2) For a statement, y k
:= exp if exist z belongs to FV(exp) such that B(z) = D
Set B (y k
) = D
3) Repeat z until a final point
P ::= program (x
1
, x
2
… x m
, y
1
, y
2
… y m
) b
1
, b
2
… b p
B ::= l:s
S ::= x:=e; s
| goto l
| if e then goto l
1
else goto l
2
| end e
Online specialization computes program parts as early as possible and takes decisions
“on the fly” using only (and all) available information.
Offline specialization begins with BAT, whose task is to place appropriate annotations on the program before reading the static input.
2
e ::= b
| x
|
λx.e
|
| e@e’ |
| if e then e’ else e’’ |
| lift e
Evaluation:
λx.e e @ e’ if e then e’ else e’’
Fun power (n,x) = if (n=0) then <1>
Else < x *
~
(power (n-1, <x>)) >
<…> means delay enclosed code by a stage
~ Means forced evaluation one stage earlier
Problem: after PE, some original small programs become a large number of codes, such as image processing. Data specialization aims to address the problem.
Program specification: Λ2 → static data → (dynamic data → result)
Data specialization: Λ2 → { loader: static data → cache
reader: (cache * dynamic data) → result
}
For example: dotprod (x
1
, y
1
, z
1
, x
2
, y
2
, z
2
, scale) { if scale ≠ 0 then return (x else error
1
*x
2
+ y
1
*y
2
+ z
1
*z
2
)/scale
}
After data specialization: dotprod-loader (x
1
, y
1
, x
2
, y
2
, scale) { cache → slot = x
1
*x
2
+ y
1
*y
2
} dotprod-reader (cache, z
1
, z
2
, scale) { if scale ≠ 0 then (cache → slot + z else error
1
*z
2
)/scale
}