Generalized Constant Propagation
Generalized Constant Propagation
A S t u d y in C
1 Introduction
Generalized Constant Propagation (GCP) is a top-down compositional compiler
analysis based on the style of abstract interpretation [CC77]. A GCP analysis
statically approximates the possible values each variable could take at each point
in the program. As an extension of constant propagation (CP), GCP estimates
ranges for variables rather than their precise Value: each variable at each point
is associated with a minimum and maximum value.
We have implemented GCP for the full C language, in both intraprocedural
and interprocedural forms. We have tested the accuracy of our method both
by assessing the quality of GCP information, and by measuring the amount of
information which could be useful to subsequent analyses. Our experiments have
show G C P to be efficient and viable; programs with many procedures obviously
benefit more from an interprocedural analysis, but surprisingly high accuracy
can be achieved with just an intraprocedural analysis. The use of read/write
sets and points-to analysis also enhance the accuracy of GCP, particularly when
constants cross procedure calls.
G C P information has several uses. Since it is an extension of CP, the same
reasons for using it apply: elimination of dead code, arithmetic optimizations,
static range-checking, etc. Naturally, G C P will tend to be more powerful in these
respects; the value of a variable may not be constant, but its range might still
allow a conditional to be statically evaluated. Ranges can also be useful for
subsequent analyses, such as various loop transformations. However, it is also
true that G C P locates more exact constants than CP, making GCP valuable
even if range information is not needed.
* Research supported in part by an NSERC Graduate Fellowship and FCAR.
** Research supported in part by NSERC and FCAR
75
In examining the ouput of GCP analysis, we also noticed that the information
could be useful for program understanding. For example, variables determined
by GCP to be bounded can also be transformed to more tightly typed variables,
such as booleans or subranges. This information could be added to the program,
making the program easier to understand and simplifying further analyses.
1.1 R e l a t e d Work
1.2 Overview
Our analysis relies on an Abstract Syntax Tree intermediate representation of
the program. In Sect. 2 we describe this structure, and how we can augment it
to support interprocedural analyses as well as intraprocedural ones. Section 3
explains the semantic basis for our analysis and gives rules for the intraproce-
dural version. Using the framework of Sect. 2, we extend our analysis to a full
interprocedural GCP in Sect. 4. We then give experimental results in Sect. 5,
comparing GCP with CP and contrasting the effects of intraprocedural GCP,
GCP with Read/Write sets, and the complete interprocedural GCP.
76
Read~Write Sets
i'~i~1"~:~
f ~ GeneralizedConstantProp.Ranges
...... ~W' ... ~. . . . . . . . . . . . . . . . .
Fig. 1. Overview
GCP analysis also uses the output of points-to analysis and read/write set
analysis. Points-to analysis is a context-sensitive interprocedural analysis that
approximates the points-to relationships at each program point [EGH94]. For
each indirection of the form *x, points-to information can be used to find the set
of named locations pointed-to by x. Named locations include globals, parameters,
locals, and special symbolic names that represent names that are not visible
within the scope of a function, but are accessible via pointers. An example of a
symbolic name is the name a 1-x that is used to represent the location accessed
via *x in the procedure i n c r in Fig. 2(b).
3 We use i-x to denote the first dereference of x, 2-x for the second dereference, etc.
77
by left arc, and the mapping information indicates that the location name a in
main corresponds to the symbolic name 1-x in i n c r . Whereas, in the second
invocation (right arc), the name b in main corresponds to 1-x in i n c r . This
mapping allows us to use one name within i n c r without losing the context-
specific information from each invocation site. A more detailed description of
the interprocedural environment, including the invocation graph can be found
in [HEGV93].
Read/write set analysis uses the points-to information to calculate the lo-
cations read and written by each basic and compositional statement, s For the
purposes of GCP, we only use the write sets calculated for procedure calls. Note
that read/write sets include all local, symbolic, and global locations written by
a procedure call. 6 Figure 2(b) gives the w r i t e set for each statement in our ex-
ample program (shown in bold italics). Note that the second assignment in i n c r
shows that the symbolic location 1-x is written. Also note that the write sets for
procedure calls are quite precise as both points-to and read/write set analysis
are context-sensitive interprocedural analyses.
Figure 2(b) also gives the range information that is collected for the program.
Each direct use of a variable has been decorated with the appropriate GCP
information as computed by our interprocedural algorithm.
3 Intraprocedural GCP
Within a procedure, GCP is a straightforward top-down semantic analysis of
the SIMPLE AST. The semantic domain is first specified; in our case we will
I
as aggregate scalars, and even pointers as unsigned integers. Arrays are approx-
imated as the merge of their contents.
Ranges form a nice semantic domain. If [a, b] and Iv, d] are (inclusive, closed)
ranges, one can consider [a, b] E [c, d] if a > c and b < d. The meet of two ranges
[a, b] and [c, d] is then the range which includes them both: [min (a, c), max (b, d)].
Our ranges are also discrete, even for the representation of real numbers;
every element is finite, and there exist least upper bounds for arbitrary sets
of elements. In other words, the domain is a Scott Domain. The existence of
fixed-points for monotonic functions, and closure under cross-product are then
guaranteed.
This domain does have one unpleasant property--it is quite 'tall,' i.e., one
can form very long chains. This will have implications for how fixed points are
computed during the analysis of loops and recurs• as will be seen later.
3.2 Semantic Functions
As mentioned, every type of statement in the SIMPLE AST (both expression and
control) needs a semantic analogue; we need separate methods for determining
how GCP information is altered by assignments, the various arithmetic opera-
tions, sequencing, conditionals, loops, ...etc. In order to ensure convergence, it
is also necessary that each semantic function be monotonic in its domain.
We cannot describe all the semantic functions here, but in Figs. 3 and 4 we
show the semantic operations corresponding to a few different kinds of state-
ments in a compositional form. We hope that this will give the reader some
idea of the flavour of the effort. Note that the structured nature of our SIMPLE
representation lends itself to concise and relatively compact analysis rules.
Most of these functions are quite obvious; assignment requires locating the
range for the right hand side value, and storing it as the range for the left
hand side variable (the effect of pointers is discussed below). Semantic plus is
paradigmatic of most arithmetic functions. The largest range which could result
from the operation being applied to any combination of values in the operand
ranges is computed and returned as the result.
Most semantic functions dilute information, due to the necessity of being con-
servative. Conditionals, though, can generate information. When control passes
through a conditional, it is necessary that the condition be satisfied (then-part),
or unsatisfiable (else-part), and this can be reflected in the range sets passed
into the corresponding statements. For example, a statement such as •177
implies that • must be less than 0 when entering the affirmative branch, and that
• must be at least 0 within the negative branch. Every conditional we encounter
therefore splits the input into two constrained sets (line 13, Fig. 4), which must
be merged after the conditional is completed.
3.3 Loops and Stepping
Loops in a semantic analysis require the computation of a fixed point in the
semantic domain. The process is illustrated starting from line 27 in Fig. 4 (other
loop structures are similar); the GCP information coming out of the body of
the loop is merged with the information entering the loop, and the process is
80
/ * Given statement S, an input GCP set, returns the output GCP set */
f u n process_stmt (S,Input) =
if basic_stmt(S)
return(process_basic_stmt (S,Input))
else
case S o f
< SEQ(S1,S2) > = >
return(process_stmt(S2,process stmt(S1 ,Input)))
< IF(cond,thenS,elseS) > = >
return(process_if(cond,thenS,elseS,Input ) ) lO
< WHILE(cond,bodyS) > = >
return(process while(cond,bodyS,Input))
f u n process basic_stmt(S,Input) =
case S o f
<x=y>=>
/ * RangeOf returns the range for the given variable in the given input set */
r e t u r n ( I n p u t - x:RangeOf(x,Input) + x:RangeOf(y, Input))
< x = *y > -----> 20
/ * MergeRanges returns the smallest range ~ every range in a given list.
Dereference returns a list of variables pointed to by the given variable
at the given statement. RangesO] is the list-version of RangeO-f */
[a..b] = MergeRanges(RangesOf(Dereference(y,S)),Input)
r e t u r n ( I n p u t - x:RangeOf(x,Input) + x:[a..b])
/ * Merge pair-wise merges two lists o] ranges -for the same set of variables.
DefinitelyPoints To returns true if the given variables have definite points-to
relationship at the given program point */
<*x=y>=>
derefx = Dereference(x,S) 30
if (DefinitelyPointsTo(x,derefx,S))
r e t u r n ( I n p u t - derefx:RangeOf(derefx,Input) + derefx:RangeOf(y, Input))
/ * If x does not definitely point to a single variable, then the
strategy is to merge the range from the right-hand-side of the '
statement with the range of all variables x can possibly point to */
f o r e a c h temp in derefx
Input = Input - temp:Rangeof(temp,Input) +
temp:Merge(R~ngeOf(temp,Input ),Rangeof(y, Input))
return(Input)
<x=y+z>=> 4c
[a..b] = semantic plus(RangeOf(y,Input),R~ngeOf(z,Input))
r e t u r n ( I n p u t - x:RangeOf(x,Input) + x:[a..b])
f u n semantic_plus([a..b],[c..d]) =
i f ( a + c < - ~ ) e = - c v else e = a + c
i f ( b + d > cx~) f = c~ else f = b + d
return([e..f])
f u n process_while(cond,bodyS,Input) =
iterations = 0
O u t p u t = oldBodyInput = b o d y O u t p u t = {}
do
Input = Merge(Input,bodyOutput) 30
(consistentlnput,inconsistentInput) = ConstrainConditional(cond,Input)
converged = (consistentInput = = oldBodyInput)
i f (not converged and iterations > maxiterations)
/ * StepUp artificially moves a non-converging range up in the
semantic domain */
StepUp(consistentInput,oldBodyInput)
iterations = 0
o l d B o d y I n p u t = consistenttnput
b o d y O u t p u t = process s t m t ( b o d y S , c o n s i s t e n t I n p u t )
O u t p u t ---"Merge(Output,inconsistentInput) 40
iterations = iterations + 1
w h i l e (not converged)
return(Output)
repeated until convergence. The output of the loop is gathered as the merged
result of all sets of ranges which do not satisfy the conditional; in the actuM
implementation we also merge the results of break and c o n t i n u e statements
with the Output and Input sets respectively.
Our semantic functions are all monotonic in their individual range-domains,
so we will reach a fixed point eventually. Unfortunately, the domain of ranges
can be quite "tall;" there are monotonic chains of very long length: e.g., [0..0]
[0..1] < ... <_ [-co..oo] where oo is typically 231. In the worst case then, our
monotonicity requirement only ensures convergence after 232 steps per variable,
which is clearly unacceptable.
We can, however, speed up this process by sacrificing the quality of infor-
mation. Ranges for variables that refuse to converge after some fixed number
of iterations can be artificiMly "stepped up" (raised in the semantic domain). If
each range can be stepped only so many times before reaching [-c~..co], then
the monotonicity of our semantic operations guarantees convergence in much
less time. By using some heuristics to guide the choice of which variable to step
(e.g., choosing the loop index first), we can achieve a reasonable compromise
between efficiency and accuracy. In our implementation, for instance, we have 2
non-converging iterations for each stepping operation. The first four steppings
individually push the non-converging ends of the variables in the loop condi-
tional to the loop bounds (if known), or to oo (or -co). The next step is to push
some (n = 40 in our case) and then all the non-converging ends of all variables to
oo, then stepping all non-converging ranges to [-oo, oo], and finally stepping all
variables to I-co, c~]. Thus, each fixed-point requires at most 14 + 2n iterations.
3.4 C o n s i d e r a t i o n s for C
Almost all languages have loops, and the difficulties they present to semantic
analyses are not unique to C. The C language though does have two distinct
features which greatly impact the efficacy of GCP: pointers and an abundance
of procedure calls.
P o i n t e r s . Whenever a dereferenced pointer is encountered in the code, it is
essential to know which variables might be accessed in order to compute the
correct range information. When a dereferenced pointer appears on the right
hand side of an expression, as an R-value, the semantic function computes the
least upper-bound of all ranges which might be referred to as the result of the
dereference (see the semantic x = *y in Fig. 3). No matter which variable is
actually accessed during runtime, it is then guaranteed to be included in the
range GCP reports for the dereference. When a dereference occurs on the left
hand side of an assignment, as an L-value, correctness requires that the range to
be stored be merged with the existing range values for every variable which might
be indicated by the dereference (see *x = y in Fig. 3). This sort of conservative
estimation can result in very poor information. If the set of variables accessed
by an arbitrary pointer dereference is not known, all referenced variables must
be assumed accessible.
Points-to analysis limits this sort of conservative dilution. By identifying
target variables for each dereference, it is possible to restrict the number of ranges
83
4 Interprocedural GCP
The approximations used for intraprocedural GCP information over procedure
calls are clearly suboptimal; in order to be surely correct, we seem to be forced
to throw away a great deal of information. Even with the more accurate identifi-
cation of altered or aliased variables possible with points-to and read/write sets,
we still have to discard all information about the range of an altered variable.
We cannot know exactly what the function does to the variables it changes, so
it is necessary to assume the variables could be anything after a call.
An interprocedural analysis does not suffer from this limitation. By knowing
the effect of each call on both local and global variables, we can determine, for
instance, that a given procedure simply increments its value rather than comput-
ing an arbitrary function. Moreover, by using the invocation graph framework
developed in section 2, we can compute interprocedural information in a context-
sensitive way, avoiding the generMizations (and hence dilution of information)
produced by the calling contex~ problem.
4.1 Using t h e I n v o c a t i o n G r a p h
Making GCP interprocedural requires just two functions, map and unmap. As
each function call is recursively traversed, the actual parameters passed to the
callee are mapped to the formal parameters. The mapping information calculated
by points-to.analysis is used to map between names in the caller and symbolic
names in the callee. As the function body is processed, the ranges computed
from the current input set are merged with the existing ranges imbedded in
the program from previous calls to the same function. Once the input set has
completed the body, the values it contains are unmapped back to the caller's
variables using the original map information. The ranges stored within the callee
84
will then represent the merged input of all calls to that function, while the ranges
returned after processing a function call represent the result of the call given the
current input set from the caller (i.e. these vMues are context-sensitive). The
process is shown functionally in Fig. 5 as a three-way branch on the invocation
graph node (ign) type; non-recursive computations are illustrated in the first
case, and map and unmap rules for GCP are shown in Fig. 6.
In the absence of recursion, this process is straighforward. When a recursive
call appears, however, we are required to compute a fixed-point for the call
representing all possible unrollings for the recursive call. This is indicated in our
traversal of the invocation graph by a matching recursive and approximate node
pair (linked by a backedge).
At each recursive node we store an input, an output, and a list of pending
inputs. The input and output pair can be thought of as approximating the effect
of the call associated with the recursive function (let us call it f), and the pending
list accumulates input information which has not yet been propagated through
the function. The fixed-point computation generalizes the stored input until it
finds an input that summarizes all invocations of f in any unrolled call tree
starting at the recursive node for f. Similarly, the output is generalized to find a
summary for the output for any unrolling of the call tree starting in the recursive
node for f. The generalizations of the input and output may alternate, with a
new generalization of the output causing the input to change.
Consider the rule for the approximate node in Fig. 5; in this case, the current
input is compared to the the stored input of the matching recursive node. If the
current input is contained in the stored input, then we use the stored output as
the result. Otherwise, the result is not yet known for this input, so the input
is put on the pending list, and bottom (2_) is returned as the result. Note that
an approximate node never evaluates the body of a function, it either uses the
stored result, or returns 2_.
Now consider the recursive rule. In this case we have an iteration that only
terminates when the input is sufficiently generalized (the pending list of inputs
is empty) and the output is sufficiently generalized (the result of evaluating the
call doesn't add any new information to the stored output).
5 Experimental Results
In order to examine the relative merits of the different fiavours of GCP, we
need a qualitative way Of measuring the GCP information produced. This is
provided by dividing the ranges GCP can produce into four categories, according
to their potential utility: E x a c t , an actual constant, like [3..3]; B o u n d e d , a
finite subrange, like [1...10]; H a l f - o p e n , one end of the range is a number, but
the other is infinite, like [1..(x~]; and Total, the range is 1-, like [ - c ~ . . ~ ] .
We have counted the number of ranges falling into each of the four categories,
for each of three different variations on GCP:
Naive: Intraprocedural GCP only. No points-to information; a pointer deref-
erence returns all variables which have had their address taken. A function
call causes all globals and all variables which have had their address taken
to be set to -l-.
85
cedural analysis keeps the two different calls to i n c r distinct, while still merging
the values of d e l t a within the procedure. The results for this program under the
N a i v e and R / W strategies are much less precise. With N a i v e there are only 2
exact ranges corresponding to the 2 uses of the local variable c. With the R / W
strategy one more exact range is found for the use of the global g2. In this case
the R / W sets are used to determine that no procedure call kills the constant
value generated by the assignment g2 = 2.
We have run the three kinds of GCP on the following benchmark set:
A s u i t e : Compiler test suite from Argonne National Labs.
C h o m p : Solves a simple board game.
Circle: An O(n 4) minimum spanning circle algorithm.
C l i n p a c k : Numerical test routines.
C l u s t e r : Two greedy graph clustering algorithms.
D h r y s t o n e : Standard timing benchmark.
Frac: Computes rational representation of a real number.
M e r s e n n e : Computes n digits of 2p - 1 for a given p and n.
N r c o d e 2 - 4 : Another test suite for vectorizing C compilers.
N u m e r i c a l : Complex number routines - zroots, laguer.
S t a n f o r d : Baby benchmarks suite - 10 small programs.
T o m c a t v : A standard Fortran benchmark, ported to C.
Of course the data GCP collects will be greatly influenced by many factors.
In the left co]umns of Table 1, we show the number of SIMPLE statements in the
program, function definitions, function calls (includes calls to library functions),
global variables, maximum loop nesting and total number of loops. It should
be expected that programs having more functions, calls and globals will benefit
more from using read/write sets, and from using an interprocedural GCP.
[[Benchmark[StmtslFuncslCallsIGlobalsl Nest ILoopslIF-PslAvg iter Ilntra(s) Ilnter(s) II
Asuite 1841 93 299 23 3 218 623 8.10 16.08 20.39
*Chomp 439 20 54 5 2 22 259 6.33 0.89 7.07
Circle 251 4 5 12 4 8 266 6.59 58.91 57.75
Clinpack 909 11 53 23 2 33 1 7 0 1 8.56 11.13 96.16
Cluster 599 20 63 18 3 53 565 6.08 21.24 28.54
Dhrystone 242 14 20 65 2 7 71 17.30 9.98 55.81
Frac 103 2 3 0 2 3 9 7.00 0.33 0.50
MersenneJ 117 8 17 3 2 7 102 7.52 0.18 1.87
Nrcode2-4 405 3 36 28 2 44 82 9.16 6.33 6.44
Numerical 319 II 18 II 2 ii 33 3.97 1.29 5.70
*Stanford 998 47 84 67 3 88 4 0 5 8 5 . 4 6 2 6 . 9 3 456.88
Tomcatv 333 2 15 7 3 19 310 4 . 8 9 4 6 . 0 9 50.24
In Fig. 7, we illustrate the relative quality of the analyses based on how ranges
are divided up into the four different kinds, and the relative quality of regular
88
constant propagation as well. The length of the black bars show the percentage
of exact ranges as found by GCP and CP, and the length of the dark gray bars
shows the extra constants found just by GCP. The total length of the bars show
the percentage of ranges that give at least some information (exact, bounded, or
open). It is interesting to note that a suprisingly large fraction of ranges contain
at least some useful information, and that in several cases ( s t a n f o r d , m e r s e n n e ,
dhrystone, and marginally c l i n p a c k and a s u i t e ) GCP locates more constants
than CP. These extra constants are the result of merged information being subse-
quently reduced to a constant, such as a boolean (b=[0..1]) within " i f (b) {...}."
In comparing the effectiveness of naive versus l%/W and I - R / W , however, the
results seem to depend very much on the style of the benchmark under analysis.
The n a i v e analyses of c i r c l e , c l u s t e r , numerical and tomcatv2 give al-
most the same results as the more expensive R / W and I - R / W anMyses. For
the latter, there is only one large main function, all scalars are locM variables,
and there are no pointers to local variables. Thus, the naive scheme does not
make any overly conservative assumptions, and the results are quite accurate.
In the three former c~ses, there are Mmost no constants to speak of, regardless
of the form of the analysis.
Most of the remaining benchmarks show improved results for the I-I%/W
method. In these cases constants are propagated through parameters, and so
interprocedural results are substantially better. However, dhrystone, nrcode
and c l i n p a c k M1 show benefits from using R / W sets; here, the reduced kill-sets
provided by the R / W analysis (particularly with respect to library functions)
allow more constants to be carried across procedure calls.
GCP is a semantic analysis on a tall domain, so the amount of time needed
by GCP is important to consider. In Table 1 we also show dynamic measure-
ments of the number of loop fixed-points, the average number of iterations for
each fixed-point, and the total time (user time in seconds, on a Sun Sparc 20)
consumed by both l%/W G C P and I-I%/W G C P . Note that these times are
for our unoptimized code; a reduction in time by a constant factor would be
easy to achieve with a less naive implementation of range sets, and, particularly
in the I-I%/W case, by including memoization. P~elative relationships, however,
are wlid. Intraprocedural GCP takes time roughly proportional to nesting, but
interprocedural GCP can take much longer due to procedure calls imbedded in
loops, and recursion. Also note that, due to the stepping heuristics, the compu-
tation of each fixed-point actually requires very few iterations.
I
Asuite I-R/W ] ~ "~'~",,,,,,,,,,'"~,........ " ~ ~, ' ,, ' ',,,,,,~,I
Asuite R/W '1 ,,,;,~ ,;,,, ",,~1 I
Asuite 2459 | ,,i~~%~ ;~i I I
Chomp I-R/W~::~-,~i~,;,:,,,~.,~,-:~ ~ ~ I
Chomp R/W-I~.~ ~ ~,~ ~
Chomp 197--~ ~.~ : ~,~,
Circle I-R/W -D, ~ , J
Circle R/VV -.1:~, n
Cimle 291 --.L.,
Clinpack I-R/W
Clinpaek R/W | . ,, ,~ I
Clinpack 1 2 0 4 - ~ll;::;:~:. . . . I
Cluster I + R / W - i.;;;,~,=~, u I
Cluster R / W - [~;i;r'r~,.,r'r~,l I
C l u s t e r 7 9 5 - ~,,,. v.~] I
Dhrystone I-R/W
Dhrystone R/W
Dhrystone 164
Frac I -P,IW
Frac R/W
Frac 90
Mersenne I- RA,V , . . . . . . . . . . J ~,,,~,~': ; ill I
Mersanne RAN
Mersenne 90
Nrcode2-4 I-R/W
Nrcode2-4 R/W
Nreode2-4 477 "~ ~"~ ~.., ,~.. ~='~ ~ ~,".. ~ -~-~ . I
Numerical I- P,,/W..~..-...:~ "1
Numerical R / W 4 - ,
Numerical 357'4-~.~
U I I U I I I I I I I
0o 10 20 30 40 50 60 70 80 90 100
Percentage
Fig. 7. Relevant ranges found for benchmarks. The percentages of the different types
of ranges are show; black and dark gray are for exact constants (regular CP and extras
found by GCP), light gray is bounded, and white for half-open. The remaining ranges
are total. The number next to each benchmark name is the actual total number of
relevant ranges in the program.
be quite reasonable in this respect; by stepping ranges that do not converge,
fixed-points can be calculated in just a few iterations per loop. The inclusion of
a simple heuristic, such as stepping the variable involved in the loop conditional
first, permits rapid convergence without overly sacrificing quality of information.
We are also considering the effects of a few simple heuristics to enhance in-
traprocedural GCP. It should be possible to improve n a i v e G C P by identifying
the more common situations where G C P unnecessarily discards information in
order to be safe, such as over selected library calls. The use of stepping also
requires further examination; perhaps accuracy or speed can be improved with
different heuristics. The effect of G C P on other analyses that use G C P informa-
tion also remains to be examined: how often are ranges actually useful?
References
[Bou93] Franqois Bourdoncle. Abstract debugging of higher-order imperative lan-
guages. In Proc. of SIGPLAN PLDI '93, pages 46-55, Albuquerque, N.
Max., Jun. 1993.
90
[cc77] Patrick Cousot and Radhia Cousot. Abstract interpretation: A unified lat-
tice model for static analysis of programs by construction of approximations
of fixpoints. In Con]. Rec. o] POPL-$, pages 238-252, Los Angeles, Calif.,
Jan. 1977.
[CC92] Patrick Cousot and Radhia Cousot. Comparing the galois connection and
widening / narrowing approaches to abstract interpretation. Technical
Report LIX/RR/92/09, Ecole Polytechnique Laboratoire d'Informatique,
91128 Palaiseau Cedex, France, 3uin 1992.
[CCKT86] David Callahan, Keith D. Cooper, Ken Kennedy, and Linda Torczon. In-
terprocedural constant propagation. In Proc. of the SIGPLAN '86 Symp.
on Compiler Construction, pages 152-161, Palo Alto, Calif., 3un. 1986.
[CH95] Paul R. Carini and Michael Hind. Flow-sensitive interprocedural constant
propagation. In Proc. o] S I G P L A N P L D I '95, pages 23-31, La Jolla, Calif.,
3un. 1995. .
[DM81] Nachum Dershowitz and Zohar Manna. Inference rules for program anno-
tation. IEEE Transactions on Software Engineering, SE-7(2):207-222, Mar.
1981.
[EGH94] Maryam Emami, Rakesh Ghiya, and Laurie 3. Hendren. Context-sensitive
interprocedural points-to analysis in the presence of function pointers. In
Proc. of SIGPLAN PLDI '9~, pages 242-256, Orlando, Flor., 3un. 1994.
[EH94] Ana M. Erosa and Laurie 3. Hendren. Taming control flow: A structured
approach to eliminating goto statements. In Proc. o] the 1994 Intl. Con].
on Computer Languages, pages 229-240, Toulouse, France, May 1994.
[GT93] Dan Grove and Linda Torczon. Interprocedural constant propagation: A
study of jump function implementations. In Proc. of SIGPLAN PLDI '93,
pages 90-99, Albuquerque, N. Mex., Jun. 1993.
[Hat77] William H. Harrison. Compiler analysis of the value ranges for variables.
IEEE Trans. on Software Eng., 3(3):243-250, May 1977.
[HDE+92] L. Hendren, C. Donawa, M. Emami, G. Gao, Justiani, and B. Sridharan.
Designing the McCAT compiler based on a family of structured intermediate
representations. In Proc. of the 5th Intl. Work. on Languages and Compilers
for Parallel Computing, number 757 in LNCS, pages 406-420, New Haven,
Conn., Aug. 1992. Springer-Verlag. Publ. in 1993.
[HEGV93] Laurie J. Hendren, Maryam Emami, Rakesh Ghiya, and Clark Verbrugge.
A practical context-sensitive interprocedural analysis framework for C com-
pilers. ACAPS Tech. Memo 72, Sch. of Comp. Sci., McGill U., Montreal,
Qua., Jul. 1993. In ftp://ftp-acaps.cs.mcgill.ca/pub/doc/memos.
[MS93] Robert Metzer and Sean Stroud. Interprocedural constant propagation: An
empirical sthdy. ACM Letters on Programming Languages and Systems,
2(1-4):213-232, 1993.
[Pat95] Jason R. C. Patterson. Accurate static branch prediction by value range
propagation. In Proc. of SIGPLAN PLDI '95, pages 67-78, La Jolla, Calif.,
Jun. 1995.
[Sri92] Bhama Sridharan. An analysis framework for the McCAT compiler. Mas-
ter's thesis, McGill U., Montr6al, Qu6., Sep. 1992.
[WZ91] Mark N. Wegman and F. Kenneth Zadeck. Constant propagation with con-
ditional branches. ACM Trans. on Programming Languages and Systems,
13(2):181-210, Apr. 1991.