benf.org :  other :  cfr :  Javac's constant folding.

Some observations about constant folding.

In the unlikely case you don't know - constant folding is the act of precomputing trivial literal functions, such as addition / concatenation etc, at compile time. The Java Language Specification refers to these as 'constant expressions', which is why you might not find it when googling!

The relevant information is in section 15.28 of the JLS, here.

'A compile-time constant expression is an expression denoting a value of primitive type or a String that does not complete abruptly and is composed using only the following:' (long list of operators, look at the JLS - but includes most arithmetic operators)

There are a couple of (probably obvious) interesting features here.

Strings

String concatentation is a compile time constant, and the final constant is the interned value - meaning that if you do see stringBuilder.append("This " + "little " + "piggy") for some reason, it's not creating a pointless intermediate StringBuilder - see info on StringBuilder vs concatenation.

Floating point constants

Again, from the JLS - A compile-time constant expression is always treated as FP-strict (ยง15.4), even if it occurs in a context where a non-constant expression would not be considered to be FP-strict. - that is to say all floating point constant folding is performed as if the function was strictfp - you MAY (may, in outstandingly rare circumstances!) therefore see a difference between runtime evaluation and folding!

Abrupt completion

Consider

int a = 1 + 2 + 3 + 4 / 0;
double b = 1.0 + 2.0 + 3.0 + 4.0 / 0.0;
These will compile down to
int a = 6 + 4/0;
double b = Double.Infinity;

as calculation of integer divide by 0 throws, whereas floating point divide by 0 is Inf. It's interesting that this didn't compile to an explicit throw, but in keeping with javac's pattern of keeping bytecode as close to java as possible for debugging purposes (and simplicity!)

Associativity matters...

Consider

a | 1 | 2 | 4 | 8 | 16
You might think this could be folded it a | 31, but it doesn't happen, because the LHS is never a constant. OR is an interesting one here, as I can't think of any cases where a tree transform across the constants would be invalid. (no scope for overflow etc)
a | (1 | 2 | 4 | 8 | 16)
is folded as one might expect.

From a decompiler's point of view, that's quite handy, as you can recover the original masking!
(Here's a snippet from java.util.Collections.singletonSpliterator)
@Override
public int characteristics() {
   int n = t != null ? 256 : 0;
   return n | 64 | 16384 | 1024 | 1 | 16;
}

In practice, the only thing this does is bloat the code slightly - once it gets to the JIT, performance is not affected (though my JIT did emit a load of 'or $LIT, %edx'!).
If you run with -Xint, you can see noticable effects, but you're probably slightly bonkers already.


This yahoo groups post is an interesting (if ancient) discussion of the topic.


Last updated 03/2015