The document describes several preliminary source code transformations that can be applied to make dependence testing and analysis easier:
1. Loop normalization transforms loops to standard form with a lower bound of 1, upper bound of N, and step of 1.
2. Constant and copy propagation substitute variables with known constant values to simplify analysis.
3. Induction variable substitution eliminates auxiliary variables by replacing them with linear functions of the main loop index, again simplifying dependence testing.
Dead code elimination is also discussed to remove unused statements. The example applies these transformations to put the code into a form suitable for vectorization and other optimizations.
2. Motivation
• Programmers wont write code with dependence test in mind
• Must perform some transformation to source code
• Applying Preliminary transformation, makes it easier to build DDG
and do dependence test
• Does not change the semantics of program, hence safe
3. Introduction
• Loop Normalization
Because dependence test are easier when stride is 1
• Auxiliary Induction variable substitution
recognize loop “invariant” variables
evaluate opportunities for Auxiliary Induction variable substitution
• Copy propagation / Constant Propagation
to propagate constant valued assignments
• Dead code elimination
find usage of “live” variables or useful statements
discard statements that do not matter in further execution
4. Introduction
• Loop Normalization
Because dependence test are easier when stride is 1
• Auxiliary Induction variable substitution
recognize loop “invariant” variables
evaluate opportunities for Auxiliary Induction variable substitution
• Copy propagation / Constant Propagation
to propagate constant valued assignments
• Dead code elimination
find usage of “live” variables or useful statements
discard statements that do not matter in further execution
Data flow analysis
5. Example
for( I = 1; I <= 100; I++){
KI = I; ----------------------------- S1
for( J = 1; J <= 300; J += 3 ){
KI = KI + 2; ----------------- S2
U[J] = U[J] * W[KI]; --------- S3
V[J + 3] = V[J] + W[KI]; ----- S4
}
}
6. Example (loop normalization)
for( I = 1; I <= 100; I++){
KI = I; ----------------------------- S1
for( J = 1; J <= 300; J += 3 ){
KI = KI + 2; ----------------- S2
U[J] = U[J] * W[KI]; --------- S3
V[J + 3] = V[J] + W[KI]; ----- S4
}
}
L = 1 (Lower Bound)
U = 300 (Upper Bound)
S = 3 (Stride)
7. Example
for( I = 1; I <= 100; I++){
KI = I; ----------------------------- S1
for( J = 1; J <= 300; J += 3 ){
KI = KI + 2; ----------------- S2
U[J] = U[J] * W[KI]; --------- S3
V[J + 3] = V[J] + W[KI]; ----- S4
}
}
L = 1 (Lower Bound)
U = 300 (Upper Bound)
S = 3 (Stride)
L = 1
U = (U-L+S) / S
S = 1
Replace usage of J (loop var) within
the loop as
J = j*S – S + L
In this case,
L = 1, U = (300 – 1 + 3) / 3 = 101, S =1
J = j*3 – 3+1 = 3j-2
8. Example (loop normalization)
for( I = 1; I <= 100; I++){
KI = I; ----------------------------- S1
for( j = 1; j <= 101; j++ ){
KI = KI + 2; ----------------- S2
U[3j-2] = U[3j-2] * W[KI]; --- S3
V[3j+1] = V[3j-2] + W[KI]; --- S4
}
J = 301; ---------------------------- S5
}
L = 1 (Lower Bound)
U = 300 (Upper Bound)
S = 3 (Stride)
L = 1
U = (U-L+S) / S
S = 1
Replace usage of J (loop var) within
the loop as
J = j*S – S + L
In this case,
L = 1, U = (300 – 1 + 3) / 3 = 101, S =1
J = j*3 – 3+1 = 3j-2
9. Example (induction variable substitution)
for( I = 1; I <= 100; I++){
KI = I; ----------------------------- S1
for( j = 1; j <= 101; j++ ){
KI = KI + 2; ----------------- S2
U[3j-2] = U[3j-2] * W[KI]; --- S3
V[3j+1] = V[3j-2] + W[KI]; --- S4
}
J = 301; ---------------------------- S5
}
For j loop,
KI is not an affine function of j
Since stride is 1
To express KI = KI + 2 as an affine
function of j, We can do
Remove assignment and
Replace reference of CONST by
CONST*LOOP_VAR
Here,
Remove statement S2
And replace
2 with 2j
And apply where ever used
10. Example (induction variable substitution)
for( I = 1; I <= 100; I++){
KI = I; ------------------------------- S1
for( j = 1; j <= 101; j++ ){
U[3j-2] = U[3j-2] * W[KI+2j]; -- S3
V[3j+1] = V[3j-2] + W[KI+2j]; -- S4
}
J = 301; ------------------------------ S5
KI = KI + 202; ------------------------ S6
}
For j loop,
KI is not an affine function of j
Since stride is 1
To express KI = KI + 2 as an affine
function of j, We can do
Remove assignment and
Replace reference of CONST by
CONST*LOOP_VAR
Here,
Remove statement S2
And replace
2 with 2j
And apply where ever used
11. Example
for( I = 1; I <= 100; I++){
KI = I; ------------------------------- S1
for( j = 1; j <= 101; j++ ){
U[3j-2] = U[3j-2] * W[KI+2j]; -- S3
V[3j+1] = V[3j-2] + W[KI+2j]; -- S4
}
J = 301; ------------------------------ S5
KI = KI + 202; ------------------------ S6
}
For outer loop I
Replace References of KI with
KI = I + 0*I
KI = I
Remove S1 and
Replace all further references of KI
with I
12. Example
for( I = 1; I <= 100; I++){
for( j = 1; j <= 101; j++ ){
U[3j-2] = U[3j-2] * W[I+2j]; -- S3
V[3j+1] = V[3j-2] + W[I+2j]; -- S4
}
J = 301; ----------------------------- S5
KI = I + 202; ------------------------ S6
}
For outer loop I
Replace References of KI with
KI = I + 0*I
KI = I
Remove S1 and
Replace all further references of KI
with I
13. Example (Dead code elimination)
for( I = 1; I <= 100; I++){
for( j = 1; j <= 101; j++ ){
U[3j-2] = U[3j-2] * W[I+2j]; -- S3
V[3j+1] = V[3j-2] + W[I+2j]; -- S4
}
J = 301; ----------------------------- S5
KI = I + 202; ------------------------ S6
}
Live variable analysis
- Assignment of S3 is used by itself
- Assignment of S4 is used by itself
- Assignment of S5 is not used in the
same scope
- Assignment of S6 is not used in the
same scope
14. Example (Dead code elimination)
for( I = 1; I <= 100; I++){
for( j = 1; j <= 101; j++ ){
U[3j-2] = U[3j-2] * W[I+2j]; -- S3
V[3j+1] = V[3j-2] + W[I+2j]; -- S4
}
J = 301; ----------------------------- S5
KI = I + 202; ------------------------ S6
}
Remove dead statements
Assignments of KI and J are dead, we
can remove all assignments to those
variable
15. Example (Result)
for( I = 1; I <= 100; I++){
for( j = 1; j <= 101; j++ ){
U[3j-2] = U[3j-2] * W[I+2j]; -- S3
V[3j+1] = V[3j-2] + W[I+2j]; -- S4
}
}
Result
At this point,
All subscripts are affine functions of
loop induction variable
Ready for dependence analysis and
further apply
vectorization/optimization
16. Summary
• Loop normalization is a transformation that makes a loop run from a
standard lower bound to an upper bound in steps of one. It is used in
many compilers to simplify dependence testing.
17. Summary
• Constant propagation, replaces unknown variables with constants
known at compile time. It is performed by an algorithm on a graph
representation of data flow within the program.
18. Summary
• Induction-variable substitution, eliminates auxiliary induction
variables, replacing them with linear functions of the standard loop
induction variable. A simple variant of the induction-variable
substitution algorithm performs expression folding in loop nests.