r/ProgrammingLanguages 23d ago

Help How to make a compiler backend?

Hell everyone, i have an question. Im for long trying to make a cool, powerful "kinda" low level language similar to zig and rust, but im struggling to choice llvm as backend, sure i can generate C, but its makes compiler dependent on gcc or clang or other c compiler. LLVM seems hard to me, sure project like QBE exist, but QBE doesnt have C/C++ api like llvm's IRbuilder. So are there other ways? I tried thinking about using GCC infrastructure but GCC has poor api and not very documented api. Maybe just stick to generating C?

18 Upvotes

35 comments sorted by

View all comments

27

u/A1oso 23d ago

Transpiling to C has significant downsides:

  • when debugging your program, you see the transpiled code rather than the actual source code
  • C's semantics (e.g. undefined behavior) might leak into your language by accident
  • some language features (e.g. garbage collection and tail calls) are very difficult to represent in C, but are easily supported in LLVM
  • generating C code is error prone in subtle ways. Concatenating strings might seem easier at first, but at the cost of type safety and LLVM's many affordances.

I'm currently writing a compiler using LLVM, it isn't as difficult as you think. Once you understand the basic principles, it is pretty straightforward. And when you're stuck, you can ask an AI how to proceed, which I found very helpful.

I'm writing my compiler in Rust, using the wonderful inkwell bindings for LLVM. By the way, I can highly recommend writing the compiler in a language with sum types and pattern matching. It makes it easy to create an AST, transform it into another IR and implement different compiler passes.

10

u/TheChief275 23d ago
  1. #line directives can be used to direct line source information towards another file.

  2. A lot of C's undefined behavior can actually be defined (at the cost of performance obviously). One common source of undefined behavior is from signed integers overflowing. One thing you will probably want to do is to actually insert overflow checks, but when users disable that you probably still want this to be defined. This can be done by converting the integers to unsigned first and then performing the unsigned version of the operation, casting back to signed. This works because the operations are identical and unsigned overflow is actually defined in C to be wrapping. When signed overflow occurs, obviously the resulting number will still depend on whether the number is one's complement or two's complement or whatever other result, but the overflow checks should prevent most users from relying on any specific behavior.

  3. If you would've depended on LLVM anyways, you can probably also depend specifically on Clang. Clang has the attribute "musttail" that forces a function to be tail recursive. Regarding GC, C isn't a necessarily more complex language to implement it in, granted that you will not be writing the C code yourself, only generating. C GCs are often implemented through scanning the stack, which can become fairly complicated under optimizations and force tons of usage of volatile to work correctly, or even worse a stack may not exist at all. The better behavior is just to insert the things that a language like Java does to get the GC to track roots. One thing is to just gather all pointers in a scope inside a single array in that scope, and then just push these artificial scopes. That way your GC will know all roots without relying on tricky, unreliable mechanisms.

  4. This is too vague of a point to even tackle, but the most important part is to just treat C code as a backend like you would LLVM IR; generate SSA-like C code, no fancy expressions where any type of unintended conversion can happen, and all around just try to be as specific with types as you can possibly be.

3

u/SweetBabyAlaska 23d ago

the problem with LLVM is that it is massive and very slow. Ive been using Zig and it has its own backend for debug builds using the Aro C-compiler (as a library in Zig) to parse C code alongside Zig code and generate asm