4 ms·
The natural evolution of compiler toolchains that live long enough on top of LLVM, eventually every one matures into having their own IR. Even clang is now in
by pjmlp 6mo ago
The natural evolution of compiler toolchains that live long enough on top of LLVM, eventually every one matures into having their own IR.
Even clang is now in the process of doing the same.
> We're going to use Clojure JVM to get our baseline benchmark numbers and then we'll aim to beat those numbers with jank.
> Note that all numbers in this post are measured on my five year old x86_64 desktop with an AMD Ryzen Threadripper 2950X on NixOS with OpenJDK 21. When I say "JVM" in this post, I mean OpenJDK 21.
In 2026, a better baseline would be the Java 26 implementations of OpenJDK, OpenJ9, and GraalVM, with JIT cache across several execution runs.
> In the native world, we don't currently have JIT optimization. It could exist, but LLVM doesn't have any implementation for it and neither does any major C or C++ compiler
Yes they kind of have, that is partially what PGO is used for, to get the program behaviour during training runs, and feed it back into the compilation toolchain.
Also while it isn't native code per se, when targeting bytecode environments like IBM i, WebAssembly, CLR, among others, with C or C++, there is certainly the possibility of having a JIT in the picture.
> Finally, just because jank is written in C++ doesn't mean that we can escape Clojure's semantics. Clojure is dynamically typed, garbage collected, and polymorphic as all get out.
Which is why, benchmarks should also take into account compilers for Common Lisp and Scheme compilers.
Anyway, great piece of work, and it was a very interesting post to read, best wishes to the author finding some support.
- stavros 6mo agoIsn't the main benefit of LLVM that you get tons of backends for free? What does having your own IR give you that's worth this tradeoff?
- eigenspace 6mo agoThese compilers aren't replacing LLVM, they are adding a compilation step with its own IR where they do certain optimizations and translations *before* handing things off to LLVM. Basically, the idea is to do as much 'high level' optimization and transformation stuff as you can in your own IR, and then let LLVM handle the low-level stuff and the targeting of specific hardware vendors.
- stavros 6mo agoThat makes sense, thanks. Is this IR at a level where the optimisations can't just be added to LLVM then?
- eigenspace 6mo agoI don't know much about Jank's implementation, but I can speak to how it's done in Julia (dynamic, high performance language with lispy semantics but matlaby syntax, JIT compiled to LLVM). I think the big thing is just that LLVM can't really be made to closely model everyone's different weird langauge semantics. In practice, the less C-like your language is, the more hoops you will likely need to jump through in order to prepare your code to be handed off to LLVM if you want to get a good result out of it, otherwise it just wont understand your code well enough to make good optimizations, or may not have the proper optimizations implemented. Trying to modify LLVM to fit your purposes is a bit of an uphill battle too. You either have to try and convince all the stakeholders that each one of your proposed modifications are worth it (when they're typically just not needed by C-like languages), or you need to maintain a fork which is a nightmare. Like, just to take one example, Julia has a world-age system I describe here: https://news.ycombinator.com/item?id=48151251#48177215 https://news.ycombinator.com/item?id=48151251#48177215 which most other LLVM users would have no use for, and would just add complexity and overhead for them so I don't think any julia people ever even thought about trying to upstream that. Julia is a somewhat extreme example. It actually has like 2.5 different IRs internally because it just does a lot of compiler transforms before handing things off to LLVM. We've generally just been on a trajectory of moving more and more stuff over to the Julia side because it gives us maximal control.
- mswphd 6mo agoAnother example: For years rust was limited on performance optimizations in LLVM. Specifically, it was difficult to get LLVM to properly optimize for Rust's generated code, namely where one can make strong aliasing (and non-aliasing) statements using `noalias`. This is a (pre-existing iirc) LLVM attribute. Despite being a pre-existing feature motivated by C-like languages, typical C/C++ code does not leverage this attribute that much. So there were a surprising number of bugs in the handling of the attribute, and it took a number of years (I didn't follow things closely, but >= 3 for sure, maybe as much as 6?) before they got ironed out enough where it could be enabled.
- amelius 6mo agoI suppose you can always translate your own IR to the one supported by LLVM. That would be the first backend I'd write, if I was making my own IR.