External mode compiler: Compile-time evaluate most constant subexpressions #5482
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This replaces yesterday's optimization of negative integer constants.
While the code explicitly implements constant subexpression evaluation only for individual operations (unary or binary), one at a time, it usually does also work for longer constant subexpressions, where one operation's result may get further merged into another's, all at compile time.
For unary operations, this is the easiest - a VM push instruction's last immediate operand can get repeatedly updated with subsequent unary operations' results.
For binary operations, we first get a second
push_imm
optimized intopush_imm_imm
, then the binary operation optimizes that "back" into a singlepush_imm
of the result, which can get further optimized into apush_imm_imm
to accommodate a third constant input and then into apush_imm
again with the second binary operation's result, and so on.This also works for mixes of unary and binary operations.
That said, there's still an unimplemented case here as documented in a comment, which makes optimization stop when encountered:
Our multi-push instructions on one hand help here (we can detect that a binary op has two immediate operands with one check and without keeping track of more than the last instruction), but on the other they hurt (there are many combinations, including those where a binary op's operands may have been split across two pushes anyway).
The compile-time evaluation is done by invoking tiny pieces of temporary VM code, with the VM
assign_pop
instruction tricked into it patching an immediate operand in the final VM code.While this is fun and something I've been meaning to add for years, there's a reason why I didn't approach it sooner - there's almost no speedup from it on our existing external modes, where we don't commonly write constant subexpressions in performance-critical places. The occasional
-1
constants are pretty much the only cases where this matters in practice so far (and this was also taken care of by a more specialized change yesterday).My new test case, which now gets optimized a lot:
The
33+(33+33)
part triggers the yet unimplemented case mentioned above.