TopGlobal Common Sub-expression Elimination

Global Common Sub-expression Elimination

  1. Now, we want to consider how to do an even better job of eliminating common sub-expressions. In particular, we want to handle control structures. So, in a piece of (meaningless) code like:

    x = y*z;
    m := z/n;
    while ( y*z > 0 ) {
        if ( z/n > l ) {
           z = y*z;
        } else {
           z = y*z - 1;
       }
        m = z/n;
    }
    

    we would like to be smart enough to realize that the boolean of the if is not a redundant common sub-expression (it will have been pre-computed on the first iteration but not on later iterations), but that the boolean of the if statement is redundant and that y*z is redundant in the assignments within the if statement.

    Note, as I mentioned earlier, "global" in compiler-optmization really means one-procedure-at-a-time.

  2. The first step in the process of recognizing common sub-expressions globally is (somewhat surprisingly) a simplification of the technique for basic blocks.

    We begin by scanning the code of the procedure being processed to identify textually equivalent expressions. That is, we ignore whether the actual values of the expressions we identify might be different because they reference variables whose values have changed.

    This is not sufficient to identify CSE's, but it serves as an important first step.

  3. Next, we need to determine whether the value of each distinct expression that appears in the program will be available whenever program execution reaches each point in the program where the expression appears. This will be the case if we can be sure that another copy of the expression will be evaluated on every path from the start of execution to the evaluation of the expression in question and none of the variables used in the expression are changed after the last evaluation of a copy of the expression.
  4. To determine which expressions are available at each program point, we will associate a variable, AVAIL(p), with each program point. The value of AVAIL(p) can be any subset of the distinct expressions found in the procedure being processed. Our goal is to specify the equations relating the values of the AVAIL(p) variables in such a way that a solution to the equations will assign to each AVAIL(p) variable a conservative approximation to the set of expressions actually available at that program point.

    Representing such a set at compile-time can be fairly easy. Assuming we have made a prepass over the procedure identifying textually equivalent expressions, we can just use a counter to assign small integer "name" to the expressions that appear in the procedure. Then, our set of expressions can be represented as a set of small integers (using a bit-vector).

  5. The notion of a "point in the program" will depend on how we represent our code internally.
  6. Given an expression that appears at several places in a program, to deterimine which (if any) of the evaluations of are redundant we need to examine how the flow of control through the program relates each occurence of to:

    We say that is "generated" wherever it is evaluated and "killed" by statements that may change variables used in the expression.

  7. If you are reading carefully, you will notice that I am trying to be very careful about the use of the word "may". In particular, above I said "statements that may change" rather than "statements that change".

    When we see a statement like

    x =

    when doing program analysis, we know that a value will be assigned to x, but we can't be sure that it will be different from x's old value. So, assuming this statement changes x would be wrong. We can only say it may change x. If it turns out it doesn't, we may assume two equivalent expressions are not CSE's when they really are.

    This is another example of a "conservative" approximation.

  8. One advantage of my approach (i.e. working with the abstract syntax tree rather than with basic blocks in a control flow graph) is that the specification of the equations that determine the values of the AVAIL(p) variables is tied to the syntax of the language. For each statement type, we give a rule for generating equations involving the program points in and around the statement.
  9. To simplify the equations a bit, we will assume that for each variable, x, in the procedure we pre-compute the set KILL(x) of expressions that appear in the procedure and reference the value of x. This is the set of expressions that would be killed by an assignment to x.
    assignment statements
    Given an assignment of the form

    < p1 > x = exp < p2 >

    where p1 is the program point just before the assignment and p2 is the point just after the assignment it is clear that
    AVAIL(p2) = ( AVAIL(p1) + { sub-expression of exp } - KILL(x))
    if statement
    Given an if statement of the form:

    < p0 > if ( exp ) {
    < p1 > stmt1 < p3 >
    } else {
    < p2 > stmt2 < p4 >
    } < p5 >

    AVAIL( p5 ) = AVAIL( p3 ) &AVAIL( p4 )
    AVAIL( p1 ) = AVAIL( p2 ) = AVAIL( p0 ) + {  expressions appearing in exp }
    while loop
    Given a while loop of the form:

    < p0 > while < p1 > ( exp ) {
    < p2 > stmt < p3 >
    } < p4 >

    AVAIL( p1 ) = ( AVAIL( p0 ) &AVAIL( p3 ))
    AVAIL( p2 ) = AVAIL( p4 ) = AVAIL( p1 ) + {  expressions appearing in exp }
  10. We can solve the equations for AVAIL (and for many other similar problems that arise in global optimization) by an iterative technique.
    1. Start by setting all the AVAIL(p) sets to the empty set.
    2. execute all the "equations" as assignment statements.
    3. If any of the AVAIL sets changed when all the equations were executed, do it again.
  11. I'd like to quickly show an example of how these techniques can be applied to a simple sample program. I will use the program point names included in the annotated version of our sample program shown above.
  12. There are only two expressions that appear more than once in this example, y*z and z/n. So, we need only consider these expressions (it would make sense to ignore expressions that only appear once in a real compiler too).
  13. The KILL sets associated with the variables that may be changed by assignments in the fragment are KILL(x) = {} , KILL(z) = {y*z,z/n} and KILL(m) = {} .
  14. The equations generated are then:

    AVAIL(p0) = {} + {y*z} - {}
    AVAIL(p1) = AVAIL(p0) + {z/n} - {}
    AVAIL(p2) = AVAIL(p1) &AVAIL(p9)
    AVAIL(p3) = AVAIL(p10) = AVAIL(p2) + {y*z}
    AVAIL(p4) = AVAIL(p5) = AVAIL(p3) + {z/n}
    AVAIL(p6) = AVAIL(p4) + {y*z} - {y*z,z/n}
    AVAIL(p7) = AVAIL(p5) + {y*z} - {y*z,z/n}
    AVAIL(p8) = AVAIL(p6) &AVAIL(p7)
    AVAIL(p9) = AVAIL(p8) + {z/n} - {}

  15. Repeatedly applying these equations as assignments we obtain:

    p0 p1 p2 p3, p10 p4 ,p5 p6 ,p7 ,p8 p9
    {} {} {} {} {} {} {}
    { y*z } { y*z, z/n } {} { y*z } { y*z } {} {z/n}
    { y*z } { y*z, z/n } {z/n} { y*z, z/n } { y*z, z/n } {} {z/n}
    { y*z } { y*z, z/n } {z/n} { y*z, z/n } { y*z, z/n } {} {z/n}
    (to keep things readable, I have merged variables which clearly must have equal values)
  16. From these results, we can see that the evaluation of z/n in the boolean of the if statement and the instances of y*z in the branches of the if statement are redundant.

Computer Science 434
Department of Computer Science
Williams College

TopGlobal Common Sub-expression Elimination