
Go backward to Announcements
Go up to Top
Go forward to Value Numbering
Constant Folding and Algebraic Transformations (cont.)
- Performing computations involving constants
can improve code quality.
- To identify many of the important situations in which we can
improve code by doing constant calculations at compile time
we must be able to apply algebraic laws to program expressions.
- The tough question is how to organize the process of applying
arithmetic identities to find constants and otherwise simplify
expressions. Our solution will be to attempt to use arithmetic
identities to transform all expressions into a canonical form
that exposes constant sub-terms.
- Given the value of additive constants, a good first approximation
of the desired canonical form would be:
K + 
where K represents some constant and
represents
the non-constant part.
- While the preceding hopefully gets the point across, K +
is not an ideal canonical form:
- While additive constants are most important, multiplicative
constants can also lead to opportunities for partial
constant folding:
- As a result, a useful canonical form is:
K + M 
where K and M are constants and
is some expression (out of
which you have hopefully sucked all the constants already).
- Expressions in canonical form could be represented as syntax trees,
but the use of a specialized structure type containing
components corresponding to K , M and
will make
the code much, much simpler (no long chains of ->internal.child[i]).
- Given the use of such a specialized data structure, your constant
folder will consist of two main functions:
- canonize( expr) -- will take an expression syntax
tree and return its canonical form, and
- treeify( canon ) -- will take an expression in canonical
form and map it back to tree form (in as efficient a way
as possible).
- To "fold" the constants in an expression you will say:
treeify( canonize( expr ))
- Canonize will work a lot like other expression processing functions:
- It will consist of a large switch statement with a case
for each expression node type.
- It will typically apply itself recursively to the sub-expressions
representing the operands of the root operator.
- If an operand can be reduced to a constant, it will come
back in the form "K + 0 * alpha".
- If all the operands of any operator are constants, canonize
can apply the operator and return a constant.
- For most operators, if the operands are not both constant,
canonize must basically just return "0 + 1 *
".
The
included, however may not quite be
the original expression.
- Canonize may do more interesting things when the operator at the
root of an expression is +, - or *. (Integer division is too messy
to think about).
- As an example, lets consider multiplication.
- Given the input
' *
',
canonize will apply itself recursively to rewrite the input
in the form
(K1 + M1*
) * (K2 + M2 *
)
- If all the K's and M's are non-zero, canonize can't do much
(other than resort to the result suggested above).
- If all the K's happen to be 0, canonize can improve things
by multiplying the M's together at compile time and
returning:
0 + (M1*M2)*(
*
)
- If either M is zero, canonize can distribute the associated
K over the other term to obtain something like:
(K1*K2) + (K1*M2)*
- To make this all work, treeify has to be smart enough to do
a few things:
- recognize that 0+x = x,
- recognize that 1*x = x,
- recognize that k+(-x) = k-x,
- etc.
- To maximize the effectiveness of these transformations on
address arithmetic, canonize should treat refvar nodes specially.
- First, it should canonize the base address expression.
- It should then add any additive constant in the canonized
form to the refvar's displacement.
- It should only re-treeify the base address after setting
the canonized form's additive constant to zero.
Computer Science 434
Department of Computer Science
Williams College
