Doing something like this is not as easy as one may think. Whe you as a human look at "x * 2 * 3 * 4" you probably see
a (singular) chain of multiplications, basically one variable and then some tail thats multiplied onwards. But for the compiler * is a left associative binary operation. So the compiler sees "(((x*2)*3)*4)".
This is then parsed into the following binary tree:
*
/ \
* 4
/ \
* 3
/ \
x 2
For the compiler every single * node is a non constant node, because the left side of the multiplication is always a subtree containing a variable. To be able to reckognize that it is equivalent to one variable multipled with a constant subtree, this tree must be transformed into a right associative tree:
*
/ \
x *
/ \
2 *
/ \
3 4
The operations here are pretty simple, as it's a simple tree invert, but there are more complex trees:
x * (2 * y) * 3
*
/ \
* 3
/ \
x *
/ \
2 y
Of course there are options to make this easier, e.g. you could use a flattend n-ary tree to reason over it, which makes it much, as you can then simply remove the non constant subtrees. While this works for such trivial examples, as soon as you have mixed multiplication and addition this won't work anymore.
Then you would need to normalize your formular, by repeatedly applying the distributive law. But this is in exp-space (and exp-time). E.g.
(a * 2) + (b * 3) + (c * 4)
= (a + b + c) * (2 + b + c) * (a + 3 + c) * (2 + 3 + c) * (a + b + 4) * (2 + b + 4) * (a + 3 + 4) * (2 + 3 + 4)
So a sum of
x terms consisting of products of
n factors you will get x^n terms in when normalizing using the distributive law (in this case 2^3=8). And all of this just to find a single constant subexpression that is not worth optimizing away because it blows this formular up so much
So what would happen for complex arithmetic expressions is, you would hit compile, then your FPC freezes for quite some time and then crash because it ran out of memory.
Of course there are heuristics and other things, for example GCC compiles the following non trivial expression:
int compute(int num) {
return 1 + (num * 3) + (4 * 5);
}
Into the single instruction:
But this comes at a cost, and that cost is that C compilation is really really slow. Compiling FPC with FPC takes a minute or so. Compiling GCC with GCC takes around 6 hours.
The FPC is a very fast compiler compared to many C compilers like Clang or GCC. But the reason is not because the FPC is so much better optimized, but because it simply does much less stuff. And this sort of optimization is the sort of stuff FPC does not do compared to it's C counterparts