The ideas presented here are from
a paper called
Interprocedural constant propagation, by
D. Callahan, K. Cooper, K. Kennedy, and L. Torczon, published in the
Proceedings of the Symposium on Compiler Construction in 1986.
The basic idea is to compute a jump function Js,f
for each call site s and each formal f of the called procedure.
Each jump function summarizes the (dataflow) effects of all paths from
the start of the procedure that contains the call to call site s.
The paper by Callahan et al assumes that the problem of interest is
constant propagation.
For example, given the following call to p and header of p:
Jump functions are used to determine what is true at procedure entry
(not how a procedure call affects dataflow information) by combining values
from all call sites.
Example
A single, general algorithm can be defined that uses jump functions to
determine what value each formal f must have at procedure entry (for all
procedures). Code for this algorithm is given below. Then we will talk
about three different ways to compute the jump functions.
Note that the possible values for each formal form the following
lattice:
Now we'll consider three ways to compute jump functions:
Note: The Callahan et al paper on which these notes are based
seems to assume that alias analysis (to determine the possible aliases
of reference formals and globals)
has already been done.
Thus, we know, for every definition and use of a variable x what other
variables might be defined or used, and we assume that use/def
information is used when appropriate (e.g., when doing constant
propagation or reaching defs analysis).
The pass-through approach is a way to enhance the results of
the "all or nothing" approach, by taking into account cases
where a procedure's formal parameter is "passed through"
unchanged to another procedure (e.g., procedure p has a formal
parameter f, which is not modified by p and is passed as an actual parameter
in a call to q).
The pass-through approach involves using the results of reaching-definitions
analysis (as well as the constant propagation used for the all-or-nothing
approach):
For each procedure, do reaching definitions analysis.
In our example, at call site s3 the second actual parameter (f2) is only
reached by the definition at the start of q; therefore, the corresponding
jump function is: Js3,f5(f1, f2, f3) = f2
For each call site s:
In our example, the slice of procedure q with respect to actual y at call site
s3 is:
Overview
call site s in proc q called proc p
s: call p(a1, a2, ..., an) procedure p(f1, f2, ..., fn)
there would be n jump functions: Js,f1 Js,f2 ...
Js,fn.
Jump function Js,fk takes as inputs the values that q's formals
are guaranteed to have at the start of procedure q, and produces as output
the (single) value that p's formal fk will have for this call (i.e., the
value of actual parameter ak).
procedure main() { procedure q(f1, f2, f3) { procedure p(f4, f5, f6) {
s1: call q(1,2,3); int y = f3*2; ...
s2: call q(3,2,1) f1 = 0; }
} s3: call p(f1, f2, y);
f2 = y;
}
For this example, the best jump functions would be:
Js1,f1() = 1
Js1,f2() = 2
Js1,f3() = 3
Js2,f1() = 3
Js2,f2() = 2
Js2,f3() = 1
Js3,f4(f1,f2,f3) = 0
Js3,f5(f1,f2,f3) = f2
Js3,f6(f1,f2,f3) = f3*2
Top
/ | | \
... -1 0 1 2 ...
\ | | /
Bottom
A formal that is determined to have value "bottom" is not constant.
The "Top" value is used for initialization; a formal will only end
up with value "Top" if its procedure is never called.
Here's the general algorithm:
// determine, for each formal parameter f, what value f must have at procedure entry
// initialize
for each formal parameter f in the program: Val(f) = top
for each call site s
for each formal f of the called procedure
Val(f) = Val(f) meet Js,f( values of the formals of the calling procedure )
put all formals on a worklist
// iterate until the greatest fixed point is found
while the worklist is not empty {
remove a formal f from the worklist
let p be the procedure whose formal f is
for each call site s in p {
for each formal x of the called procedure such that
f is used in jump function Js,x {
tmp = Val(x) meet Js,x( values of p's formals )
if ( tmp < Val(x) ) {
Val(x) = tmp;
add x to the worklist
}
}
}
}
Note that if the program has no recursive procedures, then the "iterate"
loop is not needed.
Formals can be given values by handling procedures
in topological order on the call graph (give values to procedure p's formals
only after giving values to the formals of all procedures that call p).
For the example given above, the formals' values would be:
f1 = bottom
f2 = 2
f3 = bottom
f4 = 0
f5 = 2
f6 = bottom
Jump Functions
In each case, the examples will be based on constant propagation;
however it should be clear how to use the same techniques for other
dataflow problems.
All or Nothing
For our example program, if we do constant propagation on procedure q,
the dataflow fact just before call site s3 is:
f1 -> 0
f2 -> bottom
f3 -> bottom
y -> bottom
So the jump function Js3,f4(f1, f2, f3) = 0, while the jump
functions for f5 and f6 at this call site just return bottom.
Pass Through
For each call site s, for each actual a:
if a has only one reaching def and that def is the enter node
then Js,f(f1, f2, ...,fn) = fk
else Js,f is as defined by the all-or-nothing approach
Symbolic Execution
y = f3*2
so Js3,f6(f1,f2,f3) = f3*2
| Return Jump Function | All-or-Nothing | Pass-Through | Symbolic Execution |
| Rq,f1(f1,f2,f3) | 0 | ||
| Rq,f2(f1,f2,f3) | bottom | bottom | f3*2 |
| Rq,f3(f1,f2,f3) | bottom | f3 |
Return to Interprocedural Analysis table of contents.
Go to the previous section.