Advanced Compiler Design and Implementation Loop Transformations Chapter 14 Dudu Ronen Outline This chapter is about optimizations done for loops. The assumption in this kind of optimizations is that loops are performed many times. This makes it is worth while to reduce the cost of the code inside the loop, even on account of the initialization code performed outside the loop. We covered two optimizations: Strength Reductions and elimination of unnecessary array bounds checking. In this lesson we cover: Induction-Variable Optimizations Strength Reduction Live Variable Analysis Elimination of unnecessary array bounds checking Induction Variable Optimizations What is an Induction Variable An induction variable is a variable that changes in a constant way each iteration of the loop. For example, if we have inside a loop the code x:=x+C where C is a loop invariant, than x is an induction variable. Example 1 Induction variables are not always obvious, as in the following example. do i=1,100 a(i) = 202 – 2*i enddo In this case, the value put in a(i) decreases each iteration by 2. This means, that we can rewrite the code as follows: t1 = 202 do i=1,100 t1 = t1 - 2 a(i) = t1 enddo Not that we have replaced the more complex expression “202-2*I” with the simpler “t1-2”. Example 2 x := init for ... a := ... x := x {a} y := x z od In this example: z is a loop invariant x and y are induction variables Advanced Compiler Design and Implementation Intermediate Representations Therefore, we can replace the above code with the following one: x := init y := initz for ... a := x := x {a} if a z then y := y od {a} Finding Induction Variables The are two types of induction variables: Basic – Variables that change each iteration by a constant (loop invariant) amount. Dependant – A variable which depends on another induction variable. Typically, induction variables are variables that change by a linear function. Consider the following example: 1. Original code t1 = 202 do i=1,100 t1 = t1 - 2 a(i) = t1 enddo 2. MIR code t1 202 i 1 L1: t2 i >100 if t2 goto L2 t1 t1 - 2 t3 addr(a) t4 t3 - 4 t5 4 * i t6 t4 + t5 *t6 t1 i i + 1 goto L1 L2: 2 Advanced Compiler Design and Implementation Intermediate Representations 3. After Moving Loop Invariants t1 202 i 1 t3 addr(a) t4 t3 - 4 L1: t2 i >100 if t2 goto L2 t1 t1 - 2 t5 4 * i t6 t4 + t5 *t6 t1 i i + 1 goto L1 L2: We can see that i and t1 are basic induction variables. t5 and t6 depend on i. We say that in this example there are two classes of variables: those who depend on the basic variable i (i, t5 and t6) and those who depend on the basic variable t1 (only t1). 4. Transforming the Induction Variables In this transformation, we notice that t6 increases by 4 each iteration. t1 202 i 1 t3 addr(a) t4 t3 - 4 t6 t4 L1: t2 i >100 if t2 goto L2 t1 t1 - 2 t6 t6 + 4 *t6 t1 i i + 1 goto L1 L2: 5. Transforming the loop condition Since i is required only for the loop condition, we use t6 instead of i in the condition: t1 202 i 1 t3 addr(a) t4 t3 - 4 t5 4 t6 t4 t7 t3 - 396 L1: t2 t6 > t7 if t2 goto L2 t1 t1 - 2 t6 t6 + 4 *t6 t1 goto L1 L2: All in all, we cut the 5 instructions we had inside the loop after we moved the loop invariants into 3. 3 Advanced Compiler Design and Implementation Intermediate Representations Note: consider the following example: x1 = x1 + c1 x2 = x2 + c2 y = a1x1 + a2x2 We would like to replace the calculation of y into a calculation that relies on the value of y in the previous iteration. Obviously, this can be done. However, the algorithm presented here does not handle this type of situations. Algorithm to Identify Induction Variables Identify loops Identify loop invariants and constants Identify basic induction variables biv = biv + c Inductively identify variables j with a unique assignment j = b * biv + c Split multiple assignments with the same induction variables into different induction variables. For example, if we have: x = x + 5 … x = x + 7 we will replace it with x1 = x1 + 5 … x2 = x1 + 7 (If we use SSA, then this would already be done) Strength Reduction General Strength reduction is about changing an expression with a simpler expression. The general idea is to examine the sequence of differences, instead the sequence of the original values (examples below). The usual case is to replace “j = b * biv + c” in a loop by “j = j + c1” and appropriate initialization. The original sequence looks like b, b+c, 2*b+c, 3*b+c,… while the differences sequence is c, c, c, … . This allows us to replace the calculation of “b * biv + c” inside the loop with the addition of c. We can generalize this reduction as shown in the following example. The sequence i² has a differences sequence of 2i+1. This means, that we may replace calculation of i² inside a loop with a calculation of 2i-1. After we do that, the calculation of 2i-1 yields another induction, this time it changes by 2. We get something like the following code: 1. Original Code for i=2,n do t= i² endfor 2. After one reduction t1=1 t=1 for i=2,n do t1=2*i-1 t= t+t1 endfor 4 Advanced Compiler Design and Implementation Intermediate Representations 2. After the second reduction t1=1 t=1 for i=2,n do t1=t1+2 t= t+t1 endfor Notes: 1.Like many other optimizations, strength reduction is not correct for floating point. 2. Linear reduction is useful for calculating addresses. Algorithm for Strength Reduction For every induction variable j = b * biv + c : Allocate a new temporary variable tj and replace the (single) assignment to j by: j tj After assignments biv biv + c0 insert an assignment tj tj + b * c0 Put an assignment tj b * biv + c in the preheader Replace j by tj If we have nested loops, we do this bottom up. Elimination of Induction Variables Note that in the above algorithm, tj has now become an induction variable by itself Luckily, we can sometimes eliminate some of the induction variable we created during the transformations. We do that by the following techniques: 1. Use a “live” algorithm. The the induction variable is not “live”, we can eliminate it. In Pascal, for example, we cannot use the loop variable after the loop, and it may become “dead”. 2. If the only thing that keep a loop variable “live” is the condition of the loop, we can replace it with a linear function (e.g. replace i with tj). Then, we may eliminate the loop variable. Live Variable Analysis Live variable analysis is brought here to complete the picture. A variable is live at a program point if it may be used before set in a path from this point, i.e. the value currently held by the variable may be still valid in a future reference to the variable. The following algorithm finds live variables. Local information USE - variables that may be used before set in a block DEF - variables that must be assigned in the block Iterative solution: LVout(EXIT) = LVout(B) = B ’ Succ(B) LVin(B’) LVin(B) = USE(B) (LVout(B)-DEF(B)) 5 Advanced Compiler Design and Implementation Intermediate Representations Other uses for live variables include: Register allocation: two variables that are “live” at the same time will use two different registers. Software defects 1: uninitialized variables are “live” at the beginning of a procedure. Software defects 2: unused parameters Garbage Collection: a variable that is not “live” may be released. Eliminating Unnecessary Array Bounds Checking General Exceed array bounds may cause severe problems. However, checking array bounds at runtime takes a long time (30% - 500%), and some compilers even allow to disable array bounds checking (e.g. ADA) using a pragma. A compiler can eliminate most of the checks, or at least move them out of the loop. Actually, when a compiler fails to move an array bounds check, it may indicate an “unreasonable” program, and the compiler may issue a warning. Example In the following example “trap 6” means we exceeded array bounds. In this case, we can remove all the checks from the loop: i is always >= 1, and the test for n > 100 can be done outside the loop. Pascal code: var b: array[1…100] of integer; … for i := 1 to n do …. b[i] …. MIR code: L1: ... if if t3 t4 t5 t6 t1 goto L1 L2: 1 > i trap 6 i > 100 trap 6 addr(b) t3 - 4 4 * i t4 + t5 *t6 Algorithm Assume that we need to show that some expression e inside the loop is within the bounds (lo e hi). lo and hi are assumed to be loop invariants. 1. If e is loop invariant move the check to preheader block. 2. If e is a basic induction variable replace the check by checking that lo init and fin < hi in the preheader. This algorithm can be generalized using partial redundancy elimination Kolte & Wolf 1995. We insert an extra check for the bounds in the preheader. Then, we will eliminate the checks inside the loop using algorithm based on the partial redundancy algorithm (it requires some modifications, since we wish to remove the checks and not only avoid recalculation the expression). Summary We have seen two optimizations for loops: 6 Advanced Compiler Design and Implementation 1. Strength reduction 2. Elimination of array bound checks Intermediate Representations References S. S. Muchnick, Advanced Compiler Design and Implementation, Chapter 14 7