Next: Flow graph simplification, Up: ICR optimize [Contents]
#|
When an optimization is disabled, code should still be correct and not ridiculously inefficient. Phases shouldn’t be made mandatory when they have lots of non-required stuff jammed into them.
|#
This pass is optional, but is desirable if anything is more important than compilation speed.
This phase is a grab-bag of optimizations that concern themselves with the flow of values through the code representation. The main things done are type inference, constant folding and dead expression elimination. This phase can be understood as a walk of the expression tree that propagates assertions down the tree and propagates derived information up the tree. The main complication is that there isn’t any expression tree, since ICR is flow-graph based.
We repeat this pass until we don’t discover anything new. This is a bit of feat, since we dispatch to arbitrary functions which may do arbitrary things, making it hard to tell if anything really happened. Even if we solve this problem by requiring people to flag when they changed or by checking to see if they changed something, there are serious efficiency problems due to massive redundant computation, since in many cases the only way to tell if anything changed is to recompute the value and see if it is different from the old one.
We solve this problem by requiring that optimizations for a node only depend on the properties of the CONT and the continuations that have the node as their DEST. If the continuations haven’t changed since the last pass, then we don’t attempt to re-optimize the node, since we know nothing interesting will happen.
We keep track of which continuations have changed by a REOPTIMIZE flag that is set whenever something about the continuation’s value changes.
When doing the bottom up pass, we dispatch to type specific code that knows how to tell when a node needs to be reoptimized and does the optimization. These node types are special-cased: COMBINATION, IF, RETURN, EXIT, SET.
The REOPTIMIZE flag in the COMBINATION-FUN is used to detect when the function information might have changed, so that we know when there are new assertions that could be propagated from the function type to the arguments.
When we discover something about a leaf, or substitute for leaf, we reoptimize the CONT for all the REF and SET nodes.
We have flags in each block that indicate when any nodes or continuations in the block need to be re-optimized, so we don’t have to scan blocks where there is no chance of anything happening.
It is important for efficiency purposes that optimizers never say that they did something when they didn’t, but this by itself doesn’t guarantee timely termination. I believe that with the type system implemented, type inference will converge in finite time, but as a practical matter, it can take far too long to discover not much. For this reason, ICR optimization is terminated after three consecutive passes that don’t add or delete code. This premature termination only happens 2% of the time.
Next: Flow graph simplification, Up: ICR optimize [Contents]