We have separate symbolic (logical) rewrites which are applied in .optimize() and execution (physical) rewrites which are applied in .execute(). Both share some logic. For example, there's and()/or() reduction logic which is applied in Expression logical rewrite (simplify_untyped), Array and Expression logical rewrite (Binary::reduce), and Array physical rewrite (Boolean kernel).
Instead of duplicating this logic, we can have a unified step that, given an Array, does rewrites for a long as it can, and for every rewrite returns a new result which is reduced up to the point you need execution for further optimization. For example,
add(x, true) in this unified rule is reduced to <x>, and returns <x> if you need to execute x and you can't do symbolic reductions on it. Then execute re-runs the rules with some "execute" flags and you actualy execute and apply other optimizations.
We have separate symbolic (logical) rewrites which are applied in .optimize() and execution (physical) rewrites which are applied in .execute(). Both share some logic. For example, there's and()/or() reduction logic which is applied in Expression logical rewrite (simplify_untyped), Array and Expression logical rewrite (Binary::reduce), and Array physical rewrite (Boolean kernel).
Instead of duplicating this logic, we can have a unified step that, given an Array, does rewrites for a long as it can, and for every rewrite returns a new result which is reduced up to the point you need execution for further optimization. For example,
add(x, true) in this unified rule is reduced to
<x>, and returns<x>if you need to execute x and you can't do symbolic reductions on it. Then execute re-runs the rules with some "execute" flags and you actualy execute and apply other optimizations.