From a maintenance perspective, it's really appealing to have a lot of small clearly defined passes so that you can have good separation of concerns. But over time, they often end up needing to interact in complex ways anyway.
For example, you might think you can do lexical identifier resolution and type checking in separate passes. But then the language gets extension methods and now it's possible for a bare identifier inside a class declaration to refer to an extension method that can only be resolved once you have some static type information available.
Or maybe you want to do error reporting separately from resolution and type checking. But in practice, a lot of errors come from resolution failure, so the resolver has to do almost all of the work to report that error, stuff that resulting data somewhere the next pass can get to it, and then the error reporter looks for that and reports it.
Instead of nice clean separate passes, you sort of end up with one big tangled pass anyway, but architected poorly.
Also, the performance cost of multiple passes is not small. This is especially true if each pass is actually converting to a new representation.
There's also a question of data about the trees (like, a flow graph) being recomputed for each nanopass. Also expensive.
The Nanopass dsl just gives the user a nicer syntax to specify the transformations.
I'm creating a language/compiler now, and I'm quite certain that I did not have enough passes initially, but I hope I'm at a good spot now - but time will tell.
https://andykeep.com/pubs/dissertation.pdf
Also see the this text:
https://blog.sigplan.org/2021/04/06/equality-saturation-with...
Bottlenecks are changing and it's pretty interesting.
The optimal number of passes/IRs depends heavily on what language is being compiled. Some languages naturally warrant this kind of an architecture that would involve a lot of passes.
Compiling Scheme for instance would naturally entail several passes. It could look something like the following:
Lexer -> Parser -> Macro Expander -> Alpha Renaming -> Core AST (Lowering) -> CPS Transform -> Beta / Eta Reduction -> Closure Conversion -> Codegen