QBE vs. LLVM
c9x.me
c9x.me
Notable mentions,
IBM's research project on PL.8 while developing their first RISC designs.
https://rsim.cs.uiuc.edu/arch/qual_papers/compilers/auslande...
https://pdfs.semanticscholar.org/3288/fc042cd474f0ec93d67753...
https://rishiheerasing.net/modules/hca2102/paper/cocke.pdf
The Amsterdam Compiler Toolkit,
https://github.com/davidgiven/ack
https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.12...
The more the merrier.
edit: libFIRM vs LLVM https://pp.ipd.kit.edu/firm/LLVM.html
TenDRA is designed for front-ends to compile to ANDF, an "architecture-neutral distribution format" which is kind of like LLVM bitcode but different. Sadly it never really gained any lasting momentum as an open source project, though it currently has a few active maintainers.
And much more recently there's Cranelift https://github.com/bytecodealliance/wasmtime/tree/main/crane... which is written in Rust and is being used as a back-end for WebAssembly and Rust.
Though it's more for source transformation and analysis.
Comparatively, writing frontends is quite easy these days and many examples of compiling to IR exist.
[0] https://trigraph.net/powdertoy/R216/manual.md
Backends are hard, unfortunately.
However, since you are targeting the Z80, you might be able to just take the IR and transform it yourself? I haven't touched an LLVM backend for a while, so I don't know what the process is atm, but if you imagine that a lot of a modern compiler is dealing with instruction scheduling and the like , which the Z80 doesn't really have a need for.
LLVM is very good for the industry, but it is a little over-engineered(?). Maybe that's a bit harsh, but the difficulty is definitely thrown at you earlier than hacking on most compilers.
It's been 20 years or more, I played with an older version that required an Icon interpreter as part of the build chain, and I think they had a later one that did away with that requirement.
The code generator is basically built from a declarative description of tree matching/rwriting templates with a cost model, and the compiler will find the optimal cost match.
Functional style isn't too bad. Maximal munch lines up with pattern matching, then linear scan is easy enough if you don't need gcc-tier results.
GHC has bits of its backend that are shared across architectures, maybe I should have a look at that...
If the idea is just to learn how they work, it is easier to just dump an IR that is compatible with a macro assembler, then just call the assembler with your macro definitions on the generated IR.
It won't win prices in performance, but one gets to learn how things work and will have a compiler with proper binaries at the end, and one can fine tune the macros to improve the quality of the code anyway.
That said, those kind of olden CPU's aren't a terribly snug fit for LLVM, due to their peculiarities. You'll have to bend LLVM quite a bit to bend it to your will, which also means that you need to get quite familiar with it as well.
edit: this tutorial is even older still, but for me it was one of the best resources, next to checking other simple backends: https://jonathan2251.github.io/lbd/llvmstructure.html
This is so true. Rust does it here: https://github.com/rust-lang/rust/tree/master/compiler/rustc... and it's a lot of code for no good reasons.
Even for non-toy compilers, I think it's a waste of human resources that every frontend has to implement the C ABI.
Plus, while generating C is simple, you're entirely on your own. With LLVM IR, you benefit from its infrastructure, the optimization passes and other LLVM-based tools, so you don't need to reinvent the wheel as much. In the end, you'll need some kind of IR anyway, and LLVM is a good place to start.
Well, for starters, you have to import all of C's undefined behavior, so it would be basically impossible to implement, e.g., 2's complement signed integer arithmetic.
In practice though pretty much every compiler that uses that as an optimization has a flag to disable it and chances are you'll be needing to use other system and/or compiler specific stuff anyway.
LLVM gives you the freedom to decide what optimizers to use. But it's true it is too big for many applications, even if there are build options for different sizes.
Remember that there's no reason why you can't have an IR in between your language and the backend.
If your language fits, (based on the CPS it probably won't but still) you can use a subset of it as a representation of that IR and use lowering to rewrite expensive constructs into smaller ones, greatly reducing the surface area you have to work with.
LLVM is not KISS. Very, very far from it.
https://github.com/ashton314/lambda-x86
New one:
I have done the latter, and would advise against it. The IR is powerful, but quite involved. The documentation is sometimes lacking. It's an interesting project, but you probably won't get far.
Linking against LLVM means you have to use their C++ interface, and every time you rebuild your compiler, it's going to be slow, with all the templates they use. Your compiler will be a large executable (clang and zig are about 90 MB), and you'd have to rebuild that every time you change your compiler. I would hate having to do that in my spare time, which is why I went the other route.
LLVM is very powerful. There's all these optimizations you get, and debug info. But it's not free. The integration is going to be hard and you'll have to spend quite some time on it.
The next time I would start a language project I would build an interpreter in a dynamic language, and possibly even use a parser generator. https://craftinginterpreters.com/ has a nice path laid out. Focus on what you actually want to do. Once you get somewhere, once you actually like your language, once you actually start a project in your own language, and have an actual need for more, you can write another compiler with more bells and whistles.
The key difficulty in adding garbage collection to code emitted with tools like this is being able to accurately tell what the root set is.
(I know LLVM has support for this in theory, but I have found nothing that tells how to actually use it)
[0] https://en.wikipedia.org/wiki/Common_Intermediate_Language
Elaborating: Until recently, your best option for running CIL in an AOT type environment would be Mono AOT, which has it's own limitations.
Nowadays, however, we do have CoreRT in a state that lets you do AOT, still with limitations around things like reflection. But, it's fairly doable nowadays, I would think.
I thought this wasn't universally available (that it was only in a special version of the JDK - https://openjdk.java.net/projects/graal/). But it appears to be everywhere, albeit experimentally.
This sounds like it should be wrapped as an helper library on top of LLVM and embedded inside LLVM. Why it is not the case ?
%v =w add %v, 1.
defeat the purpose of using SSA in the first place? If this form is allowed, then I can not rely on `%v` having an immutable value which is much of the reason why we use SSA in the first place.Both WASM and javascript docs do this, and it is also the same here https://c9x.me/compile/doc/il.html
I think LLVM's IR document is not easy to read, this one is not better.
I don't want to reimplement a compiler for the IR language, I want to know how to use it, how to generate code using it. I need to see examples, i.e. how to implement threading, coroutine, weakly typed variables, reflection ... not the BNF.
gcc used to be the missing link, but they switched to C++ and now you can't bootstrap it with something like tcc.
GNU GUIX works on the boostraping issue, they strive to reduce the seed size as much as possible: https://guix.gnu.org/blog/tags/bootstrapping/
So, essentially, a beginner-friendly intermediate layer on top of LLVM.
and
> it is programmed in non-fancy C99 without any dependencies.
These are contradictions. C99 is not a "very hackable" language. If the compiler isn't designed to make fast binaries then they should have written it in a higher level language that has less foot guns.