Rewrite optimizer to have room for other optimizations
Currently the optimizer needs to read the AST 2*n times if it has n different optimizations to perform, which is highly inefficient.
We should be able to read the AST at most 2 times to perform all the optimizations.
And how we can do it ?
I don't know yet
I'm coming back with some ideas and key points:
- we should be able to iterate over the AST at most 2 times (as said in the original issue)
- first iteration to count variables uses/refs, functions, and whatever
- second iteration to create a new AST from the old one, without the unwanted code
- I think everything can stay inside the optimizer class (no need to have the same pipeline pattern we have in the macro processor)
After some time spent thinking about this issue, I think I know how we can rewrite the optimizer
- the first phase will be a recursive AST node visitor, dispatched in different methods to handle different types of nodes
- this will allow us to apply special algorithms per node type (function call, condition, etc)
- it can mark nodes with the
NodeType::Unusedto mark them for deletion
- the second phase should create a new AST, removing unused code, and transforming it
- this can stay in a single recursive method AFAICT
The new optimizer should be able to
- mark nodes as unwanted (unused)
- transform nodes based on the context (for example to tell the compiler that it should POP after compiling a node)
Transformations won't be complex, only wrapping or unwrapping nodes in other nodes.
I suggest to update our nodes so that they use less memory, making them easier to copy (our nodes are big objects, which is bad because we instanciate them very often). It would be nice if we could remove the filename attribute and use a string_view, pointing to a string owned by the parser, but this will need to refactor the import method of the parser so that all the filenames are stored in the same instance (since each time we find an (import "x.ark") we instanciate a sub parser).