Constant Folding

Our demonstration for Chapter 3 is elegant and easy to extend. Unfortunately, it does not produce wonderful code. However the naive construction of the LLVM module will perform some minimal transformations to generate a module which not a literal transcription of the AST but preserves the same semantics.

The “dumb” transcription would look like:

  1. ready> def test(x) 1+2+x;
  2. define double @test(double %x) {
  3. entry:
  4. %addtmp = fadd double 2.000000e+00, 1.000000e+00
  5. %addtmp1 = fadd double %addtmp, %x
  6. ret double %addtmp1
  7. }

The “smarter” transcription would eliminate the first line since it contains a simple constant that can be computed at compile-time.

  1. ready> def test(x) 1+2+x;
  2. define double @test(double %x) {
  3. entry:
  4. %addtmp = fadd double 3.000000e+00, %x
  5. ret double %addtmp
  6. }

Constant folding, as seen above, in particular, is a very common and very important optimization: so much so that many language implementors implement constant folding support in their AST representation. This technique is limited by the fact that it does all of its analysis inline with the code as it is built. If you take a slightly more complex example:

  1. ready> def test(x) (1+2+x)*(x+(1+2));
  2. define double @test(double %x) {
  3. entry:
  4. %addtmp = fadd double 3.000000e+00, %x
  5. %addtmp1 = fadd double %x, 3.000000e+00
  6. %multmp = fmul double %addtmp, %addtmp1
  7. ret double %multmp
  8. }

In this case, the left and right hand sides of the multiplication are the same value. We’d really like to see this generate tmp = x+3; result = tmp*tmp instead of computing x+3 twice.

Unfortunately, no amount of local analysis will be able to detect and correct this. This requires two transformations: reassociation of expressions (to make the adds lexically identical) and Common Subexpression Elimination (CSE) to delete the redundant add instruction. Fortunately, LLVM provides a broad range of optimizations that we can use, in the form of “passes”.