Constant Folding
Programmers write 60 * 60 * 24 instead of 86400 because the first version explains itself: seconds, times minutes, times hours. But the multiplication has exactly one possible answer, forever. Making the running program compute it on every start is pure waste. Constant folding is the pass that does the arithmetic once, during transformation, and ships the answer.
It is the difference between a recipe that says use half of a 400 gram pack and one that says use 200 grams. Someone did the division while writing the recipe so the cook never has to. The meal is identical, one step of work moved from cooking time to writing time.
Folding introduces the third great move in the transformation toolkit. Renaming relabels, dead code elimination deletes, and folding evaluates: it runs a tiny, provably safe piece of the program early and replaces the expression with its result. The rule that makes it safe is strict. Both operands must already be literal values, and the operation must behave in the transformer exactly as it would at runtime.
How a fold pass works
- Walk the syntax tree looking for an operation whose operands are both literals, like 60 * 60.
- Compute the result using exactly the runtime's arithmetic rules, and replace the whole expression node with the literal answer.
- Repeat. Each fold can expose the next: after 60 * 60 becomes 3600, the surrounding 3600 * 24 is suddenly two literals and folds too.
- Stop when no operation has two literal operands. Anything involving a variable is left alone, because its value is not known yet.
Watch the cascade below. The slider applies one fold at a time, innermost first, and the change log shows each piece of arithmetic disappearing into its answer.
Note what did not fold: nothing, in this example, because every expression was built from literals. Add a single variable to a chain and the fold stops exactly at the point where knowledge runs out. Folding never guesses.
The details that keep it honest
The iron rule says the folded program must behave exactly like the original, and arithmetic has sharp edges. A careful folder refuses to fold division by zero, because the original program would have raised an error at runtime and the transformed one must do the same, at the same moment. It computes integer division the way the runtime does, truncation and all. It respects overflow limits rather than silently producing a number the runtime could never make.
In real multi-language tools this is a notorious bug farm. Floating point breaks associativity, so regrouping a chain of additions can change the answer in the last bits. Languages disagree on integer division of negative numbers, some truncate toward zero and some floor. String conversion rules differ everywhere. A folder must model the target runtime's semantics exactly, not the host language's, or it manufactures silent-wrong programs: output that runs without errors and computes something subtly different. Silent-wrong is the worst failure class a transform can have, precisely because nothing crashes.
Folding also composes with the last lesson. When folding evaluates a comparison like 10 > 20 down to the literal false, the branch guarded by it becomes provably dead, and dead code elimination can delete it. Optimizer pipelines alternate passes like this until nothing changes, a state they call a fixed point.
The two directions
A compiler folds constants to make programs faster. An obfuscator runs the inverse move, sometimes called constant unfolding or splitting: it takes 86400 and rewrites it as something like 43200 * 2, or a chain of shifts and additions, so the reader cannot see the meaningful value at a glance.
Here is the honest part. Unfolded constants are constant expressions, and constant expressions are exactly what folding eats. Any attacker with a compiler front end, a decent decompiler, or a pocket calculator gets the original number back mechanically. Constant splitting on its own is friction, not protection. It earns its keep only when combined with layers that stop the folder from seeing both operands as literals at all, such as feeding values through data that only exists at runtime. Level 6 covers what that takes.
Fold one by hand
Constant folding evaluates what is already fully known, one literal pair at a time, cascading until knowledge runs out, and it must mirror the runtime's arithmetic exactly to stay safe. Obfuscators run it backwards to hide meaningful numbers, and any serious attacker runs it forwards to get them back, so constant games are seasoning on top of stronger layers, never the meal.
Frequently asked questions
Is constant folding the same as constant propagation?
They are partners. Folding evaluates an operation whose operands are literals. Propagation notices that a variable was assigned a literal and substitutes that literal into later uses, which frequently creates new literal pairs for folding to eat. Real optimizers interleave the two until nothing changes.
Do obfuscated programs pay a speed cost for unfolded constants?
A small one, since the runtime recomputes each split expression when it executes, and a larger size cost from the longer text. Many runtimes with their own optimizers will quietly fold the constants again before executing, which is a neat demonstration that the protection was cosmetic to begin with.