| I built a nanopass-like compiler using Clojure (plus a pattern-matching library[1] that I created) to compile a subset of Python all the way down to bytecode for Untether AI's inference accelerator, a RISC ISA extended with an at-memory compute array. The python subset allowed full control of the array via type-inference instead of intrinsic ops. I learned how to build a compiler using the nanopass approach and I still think it was a good way to learn, but I would not build another real compiler that way. Building a lot of passes was great for initial development of the compilation pipeline, but was terrible for maintenance and relatively limited for creating optimizations. Bugfixes in an early pass would frequently require changes across several subsequent ones, especially if any change in representation were required. About 8 months in, I adopted the a Sea of Nodes[2] approach for the optimizer, type inference, and scheduling but kept my nanopass ingestion, allocation and codegen passes. At the 12 month mark, the v1 compiler was functional but had limitations. For the next leg of the project I decided to switch fully to Sea of Nodes. The final compiler was much better; orders of magnitude faster, more flexible, much easier to debug and test, more features, and about the same amount of code. Sea of Nodes takes the core idea of isolating passes to its logical conclusion by narrowing the scope of each change down to a single transformation on a single graph node. Operations are worklist based and can happen in any order, eliminating the pass ordering problem. The IR is a graph that follows simple and consistent semantics from parsing through all phases down to code generation. Going from 20+ IR dialects/sub-dialects to a single graph representation was a huge plus. [1] https://github.com/pangloss/pattern
[2] https://github.com/SeaOfNodes/Simple |