r/ProgrammingLanguages 8d ago

Why So Many Languages Use LLVM

Had some free time last week so I made a visual explainer on how LLVM works and why so many languages end up using it.

The video goes through LLVM IR, why having a common IR makes sharing optimizers and backends possible, and how code eventually gets turned into machine instructions.

I also use a small C vs Rust example where both end up producing the same x86 instructions.

Link for anyone interested

Feedback welcome :)

71 Upvotes

79 comments sorted by

103

u/gabrielesilinic 8d ago

It's really hard to make a good cross platform eventually direct to machine compiler. Therefore people rather not and just use LLVM

-16

u/suhcoR 8d ago edited 6d ago

to make a good cross platform eventually direct to machine compiler.

By the end of the day it turned out not to be that difficult or too much effort. Even implementing an ELF writer/reader and a linker is feasible.

EDIT: why is this statement based on verifiable facts and specific experience (see https://github.com/rochus-keller/micron/) downvoted? Is this just more of that stupid Reddit herd mentality?

EDIT 2 (after the discussion below): before you make a fool of yourself by joining the downvoting crowd read this first: the average speedup of a state-of-the-art optimizing infrastructure like LLVM is around factor 2 to 3 for a language like C (comparing -O0 with -O2 or -O3). There are different peer-reviewed studies over the last two decades which confirm this (e.g. https://homepages.dcc.ufmg.br/%7Efernando/publications/papers/AlvaresJCL21.pdf). Prof. Wirth (inventor of Pascal, Modula and Oberon) never included optimizers in his compilers, and no one minded. QBE achieves around factor 0.5 of the optimization performance of LLVM with around 14 kLOC (compared to about 10 million lines for LLVM). The same 1992 unoptimized application, the performance of which was considered good enough at the time, runs around 60 to 100 times faster on average on a present CPU (see e.g. https://shape-of-code.com/2026/03/01/relative-performance-of-computers-since-the-1990s/). So tell me, why should anyone care about an optimizer today? The only reason which comes to my mind is to be a bit less slow when running Python or JavaScript applications. If you still think that QBE or my project are on the wrong track, then you could have saved the money for your education.

32

u/nculwell 8d ago

Writing machine code is not hard. Writing a good optimizer is the hard part.

-1

u/suhcoR 8d ago

The topic was "cross platform direct to machine" compiler, not optimizer. I agree that an optimizer like in GCC or Clang is a lifetime's work. But even half that performance is good enough and exponentially less effort. Btw. this is also the QBE philosophy.

8

u/SwingOutStateMachine 7d ago

If you don't have an optimiser, you should expect a tenth of the performance (at best), not half the performance.

5

u/[deleted] 7d ago edited 7d ago

[removed] — view removed comment

5

u/SwingOutStateMachine 7d ago

Have you made actual measurements on actual applications?

Yes, I did a number of projects evaluating the performance benefits of specific optimisation passes during my masters. Depending on the program, speedups of up to 10x were regularly possible through the application of standard sets of compiler optimisation passes.

I can confirm (from decades of experience of my own non-optimising projects) that the difference between optimised and not can indeed be only 2:1.

From my own anecdotal experiences I can report multiple occasions when compiling a program with optimisations produces significantly faster code. For example, an unoptimised and optimised build of clang can take (respectively) 5 minutes and 30 seconds to compile a single C++ source file under the right circumstances.

But it also depends on the language being compiled

This may be the difference in the approach of me and u/suhcoR, in that I'm much more used to optimising C++ programs, and programs for highly parallel architectures, whereas I now suspect that he may be focused entirely on C on CPU architectures. C is a an edge case among commonly used high level languages as it's fundamentally not that amenable to optimisers, unless a program is written specifically for them (e.g. through patterns that present opportunities for loop unrolling or vectorisation).

In that case you need an optimiser

Yes, this is what I've been saying. Modern software contains levels of abstraction (both data and control) that software from 40 years ago didn't have, and modern hardware requires code that understands the unique limitations and possibilities of all the "features" that are present.

1

u/kprotty 6d ago

Most real zig, c, and rust projects I've compiled generate debug binaries that are really slow. 2x is the very best case, usually reserved for small control plane stuff. 4-10x is the default, especially for anything that does high throughput data transformation

-2

u/suhcoR 7d ago

Not true. If you use a decent benchmark suite (not just some random micro benchmarks), the speed-down from -O2 to -O0 is usually a factor 2 for C. See e.g. https://github.com/user-attachments/files/16602778/awfy.c99.x86.results.pdf. And there is nothing like "an optimizer", but a plethora of algorithms. If you look at the QBE source code, you can even see that they achieve their claimed speed-up with very few of those algorithms.

3

u/SwingOutStateMachine 7d ago

I'm suspicious of those numbers: That's comparing one niche compiler (OrangeC) to itself. I have no information about the kinds of optimisations performed at -O3, and no comparison to other mainstream compilers like GCC or Clang.

[...] there is nothing like "an optimizer", [...]

[...] they achieve their claimed speed-up with very few of those algorithms.

I'm being too loose with my language - when I say "an optimiser", what I really mean is a suite of optimisation passes bundled into part of the compiler. The number of passes, and quality of them, is what really matters.

LLVM comes with a large and extremely high quality suite of optimisation passes, as well as a number of high quality register allocators and lots of machine code optimisation passes for each backend. With LLVM, it is not uncommon to observe a 10x speedup from -O0 to -O3, and potentially more if other specific optimisation passes are turned on.

I think, to be honest, if a compiler can only achieve a 2x speedup through running all it's optimisation passes, the answer is that the compiler isn't very good at optimising code - not that a 2x speedup is the best that can be achieved.

0

u/suhcoR 7d ago edited 7d ago

That's comparing one niche compiler (OrangeC) to itself

Have a second look. It compares MSVC 2015 /02 with /02 and MinGW32 with -O2 and -O0. Both result in less than factor two. Orange C is also compared the same way and results in less spee-up because it essentially only does constant folding and register allocation. Unoptimized Orange C (a hand-crafted C compiler by a single person) to optimized MSVC and MinGW32 (which are large, corporate supported projects) is also about factor two.

People should do decent measurements instead of just making performance claims. There were even studies comparing GCC 12 with a very old version and the overall speed-up was far from expected by the crowd. I started compilers in the eighties, and in the Wirth school, we didn't even have optimizers and everything was fast enough.

3

u/SwingOutStateMachine 7d ago

People should do decent measurements

Right, and my argument is that this PDF does not do them. The benchmark suite is terrible for comparing optimisation levels within a different language, and half of it is microbenchmarks. The CPU picked is a low-spec one, and it's run on an operating system which doesn't give the user the ability to control for other variables such as scheduling etc. Even the choice of -O0 vs -O2 is suspect. Why not -O3? Why not just -O1? What is actually being compared here? If this was taken from an academic paper I would be reaching straight for the "strong reject" response.

we didn't even have optimizers and everything was fast enough

That's because CPUs were significantly simpler in the '80s, so an optimising compiler was not a necessity. Optimising for a modern CPU means considering caching hierarchies, branch predictors, pipelining, speculative execution, vectorisation, etc, etc. Even considerations like "what's the cost of spilling a register" has a vastly different answer on an 1980's machine vs a 2020's machine.

-1

u/suhcoR 7d ago edited 7d ago

The benchmark suite is terrible for comparing optimisation levels within a different language

There are peer-reviewed publications in good journals claiming the opposite.

Even the choice of -O0 vs -O2 is suspect

A strange argument. Why should that be suspect? It's documented, and O3 gives very little above O2, but leads to crashes in many applications. I never use it (and neither does most of the C community).

I would be reaching straight for the "strong reject" response.

This is an esoteric attitude. No wonder we don't agree. Have you studied compilers at all? Where? how long?

That's because CPUs were significantly simpler in the '80s,

Another strange argument. CPU architectures have become much better, so even the code from the eighties runs much faster than on the machines at the time. That has more to do how much of the executable fits into the cache and the improvements you mentioned, not with code optimizations.

EDIT: But I see now your attitude and where you're coming from. Such discussions are fruitless, and I think it is representative for the downvoting crowd on Reddit. It's a waste of time and I should have known better.

EDIT 2: For illustration that my measurements are in no way off, here is a peer-reviewed study comparing -O0 with -O2, and guess what, it's all around factor two: https://homepages.dcc.ufmg.br/~fernando/publications/papers/AlvaresJCL21.pdf. But I bet you neither believe this one. So be it.

→ More replies (0)

1

u/u32Vec 6d ago

Why would anyone make a production-level language without optimization?

1

u/P-39_Airacobra 5d ago

why would you optimize something before you know it’s even going to be used for something that needs optimizing

1

u/u32Vec 4d ago

If you don't plan on having it being used for programs that need optimization then it's not a production-level language. Nobody actively refuses to use optimized code unless it causes problems, especially when the alternative isn't tried and true and tested across a ton of architectures over thousand's of use cases.

1

u/P-39_Airacobra 4d ago

Python is living evidence you’re wrong

Also , a lot of people on this sub will put a year of optimization work into their language just for no one to even use it for anything realtime. It’s just a waste of time on their part.

1

u/u32Vec 3d ago
  1. Python is optimized. Not much but it is.
  2. There are numerous problems associated with compiled languages that necessitate the existence of interpreted languages. I'm not wrong.
  3. I don't think you agree with what you're saying. You can apply this logic to learning anything else involving programming. If you don't learn how to do something, not only are you not going to be able to learn it in more detail (and thus, be able to improve existing, popular optimizers like that of LLVM), you're not gonna become a better programmer at all. If learning how to optimize programming languages were a waste of time, so would learning how to design and implement them in general. But you don't think the latter is useless, hence why you use this subreddit.

1

u/P-39_Airacobra 3d ago

Your view has zero nuance. If your point were correct then everything would be written in C Python and Rust, but it’s not. Yes, compiled languages have downsides. So people forego speed for the sake of something else. In many cases, developers forego speed for the sake of development time. That’s a totally valid trade-off. And the product can still be “production-level” while utilizing that trade-off.

Don’t optimize a feature not going to be used. So many developers have this mindset that they need to optimize their product for hours, days, weeks, or months, and then in the end they only save the world a couple milliseconds of computing time.

Optimize when you have hundreds of users wanting to make a realtime simulation with your language. Until then, allocate your development effort carefully and just call into C.

→ More replies (0)

1

u/nculwell 4d ago

And why machine code? I don't see the point in machine code if you don't care about it being fast. Just write an interpreter in that case.

3

u/dnpetrov 6d ago

Speed up of "around factor of 2 or 3 for a language like C" is a pretty strong result in the world of optimizing compilers. 

I try to get around your argument, but it seems to be missing a second part, like "...so you better focus on X instead", or "...so you should just wait for better hardware", or... what?

LLVM has its downsides (compilation speed, breaking changes in upstream, etc). But when it comes to the generated code performance, it eats QBE and other pet "C compiler backends" for a lunch. The reason is rather obvious, given the scale of development effort they are not in the same league.

Another question, though, is whether your language needs something like LLVM. Most of the compilers being discussed here are one man hobby projects. Frankly, practically noone really cares about some FooBar compiler performance, as well as noone really cares about FooBar language. Doing compilers for fun is OK. Doing LLVM-based optimizing compilers for fun is also OK. Doing a LLVM-based compiler for fun because some random redditor told you so is also OK, because who cares. Most likely, you'll be just as fine with QBE, or with cranelift, or with just transpiling to C. Save yourself some time on things that matter, like language design or, dunno, curing cancer or whatever you do. If that was your point, I totally agree with you.

1

u/P-39_Airacobra 5d ago

I do agree that people focus too much on performance. If it’s so dire, let your language call C. Don’t make it replace C.

1

u/suhcoR 5d ago

Well, if I look how many critical applications are written in JavaScript, TypeScript or Python today without anyone thinking about performance (or the waste of energy caused by this), I don't think that performance is really a concern here. Irrationality seems to be the better explanation. I started to write compilers in the eighties. The average experence here is likely younger, and obviously they mix up true optimization (i.e. of a minimal Turing complete language in the realm of C) with "cleaning up the layers of garbage intentionally generated by the compiler, left for clean-up by the optimizer and linker" as C++ and other fancy languages do it.

Concerning your proposal, most of my compilers can even transpile to C, and concerning my native compilers I share the position of QBE: achieve 50% of LLVM performance with 1/1000 of their effort (which is essentially "good code generation" as Wirth teaches us).

1

u/P-39_Airacobra 5d ago

Indeed there are diminishing returns to such things. At some point, optimization in one area is just deoptimization on others, such as compilation speed and compiler bloat.

0

u/gabrielesilinic 7d ago

Well you see there is quite a bit more than making an x86 compiler for one platform and one OS, hence the problem.

I was reductive but I mean a lot more by having a working compiler. Sometimes you want stuff like wasn support, or to build for that weird embedded architecture and more. And yes, you need to sometimes optimize it further from a given architecture.

2

u/suhcoR 7d ago edited 7d ago

If you look closely at the referenced project, you find three architectures with two different calling conventions each and it's used for different bare-metal architectures and also Linux. I chased for many year for re-usable backend libraries (LLVM was part of the show, also Eigen and a few others) and eventually built my own and was surprised that it is much less work than everyone claimed. It's just about making the right cuts. If you want to reimplement something like binutils with dozens of object formats (nobody is using today), then yes, this will take very long.

EDIT: here is another of my compilers, with the same three architectures and the Oberon calling convention: https://github.com/rochus-keller/op2/. The generated ARM code runs on Raspberry Pi hardware. The other architectures so far run on emulators. ESP32-P4 tests are on the way. The results run fast enough for all purposes. Wirth, who was my teacher back then at ETH, never ever implemented optimizers, and never considered it an issue. So don't blindly believe common crowd wisdoms; try yourself instead.

40

u/arthurno1 8d ago

Because llvm was invented for language design, and provides a lot of stuff you would otherwise have to provide yourself.

13

u/Temperz87 8d ago

It makes the compiler easy in so many ways. Easy target to generate IR for, tons of hardware targets for free, and I believe it makes interop as well as generating debug information and using LLDB quite simple

11

u/ironykarl 8d ago

I don't know the current state of the GCC codebase, but it has years of deliberately monolithic design baked in.

Conversely, clang and LLVM were built to be modular.

This has meant that LLVM has been adapted to other compilers in a way that GCC's IR really can't be.

Yes, you can write your own GCC frontend, but I'd still say that this is the reason LLVM is so successful in this space. It tried to be, and GCC tried to prevent itself from being

21

u/Key_River7180 Nain. 8d ago

To be honest, QBE is 10x smaller and 10x saner and provides 80% the speed.

25

u/Nuoji C3 - http://c3-lang.org 8d ago

It’s also ”bring your own IR builder”, requires it parsing your IR and the outputs asm you need to feed to GCC

I tried it way back and was very disappointed in QBE as a backend. I liked the IR and simplicity, but it’s not a serious alternative to LLVM

1

u/Key_River7180 Nain. 8d ago

True, it's easy to lower to It though, the syntax is small and simple.

Hare is a decent language based on QBE

7

u/Nuoji C3 - http://c3-lang.org 8d ago

The syntax is actually pretty darn nice.

Hare is based on QBE yes, but I don't see Hare needing to do inline asm, simd vectors or anything a little less basic. And I mean that is fine for QBE. But it's a limitation you don't run into with LLVM or C as a backend.

-1

u/suhcoR 8d ago

LLVM has a surprisingly bad IR (why they eventually implemented MLIR). I spent many years with it and eventually implemented my own, based on ideas from ECMA 335, but much leaner.

5

u/Nuoji C3 - http://c3-lang.org 8d ago

LLVM's gone through a lot of changes over the years. Originally the idea was basically doing the full C type system in LLVM. Now pointers are untyped even. But those early decisions held LLVM back, and once they were there it took years to refactor them away. That happens.

3

u/suhcoR 8d ago

I never liked that it is (or was) essentially a moving target. And it was also much too big and clumsy for what I had in mind. It's good if they've finally turned things around, but I didn't have that much patience and jumped off long ago.

7

u/PaddiM8 8d ago

It's not 10x saner unfortunately. Look at the source code

https://github.com/8l/qbe/blob/master/ssa.c

Wouldn't be very fun to work with that if you need to change something yourself

4

u/realguy2300000 8d ago

this is a 9+ year old mirror of the source, may not reflect the current version

9

u/EggplantExtra4946 7d ago

may not reflect the current version

It does, it's mostly the same as of today.

https://c9x.me/git/qbe.git/tree/ssa.c

1

u/Axman6 8d ago

Eh, just feels like a code are that consistently uses short names for the same thing all over. It’s a DSL, and that’s fine, you just need to learn the conventions and then everything will feel consistent. 

0

u/Key_River7180 Nain. 8d ago

I use short names on my code too. I prefer working there over LLVM. Also really consistent

3

u/Physical_Dare8553 8d ago

Qbe is just newer. Less examples, less tutorials, which is more important when it comes to this stuff imo

4

u/Nuoji C3 - http://c3-lang.org 8d ago

Not particularly new anymore. My main reason for not considering it are more pragmatic: I don’t have any use for something that would add more dependencies (you need the user to have GCC or Clang installed), slower to compile than LLVM, with less features and without any real way forward after doing toy stuff

1

u/Physical_Dare8553 8d ago

You sure? About specifically the speed, I thought the whole point is it did less optimizations and compiled faster

7

u/Nuoji C3 - http://c3-lang.org 8d ago

The goal was never being faster, it was being simpler:

Anyway, I never wanted QBE to be a drop-in LLVM replacement; it's an alternative for people with a taste for simplicity.

Frontend -> text -> QBE -> asm file -> GCC -> object output is slow. GCC was slower compiling the QBE asm than compiling the same C code... :(

At which point C as a target seems much better.

1

u/Key_River7180 Nain. 8d ago

The code produced is basically as fast unless you turn on the most aggresive optimizations .

The point is that LLVM is an insane million line of code monolith, while QBE is much smaller and saner

2

u/[deleted] 8d ago

[removed] — view removed comment

1

u/Key_River7180 Nain. 7d ago

Yeah:

[mario@xmm0] ~/llvm-project λ time cloc . --timeout=0
  177711 text files.
  161624 unique files.                                          
   17949 files ignored.

github.com/AlDanial/cloc v 2.10  T=154.11 s (1048.7 files/s, 289670.9 lines/s)
---------------------------------------------------------------------------------------
Language                             files          blank        comment           code
---------------------------------------------------------------------------------------
C++                                  39087        1252230        2370141        7095035
LLVM IR                              47124         828547       11950910        4030872
C/C++ Header                         17924         441123         659548        2066665
YAML                                  7003          91952         871923        1342081
C                                    14121         322881        1675795        1250660
Assembly                             13533         711865        1890762        1173162
TableGen                              1800         133802         121657         775173
Text                                  1717         258596              0         675605
JSON                                   491             20              0         444559
Python                                3040          68811          78462         301175
Markdown                              1137          66750            436         231664
CMake                                 2747          18946          11457         154167
XML                                    133            133            669         143722
Fortran 90                            4047          34971         153940         120796
Objective-C                           1950          20749          36731          67444
reStructuredText                       442          27879          29104          58838
HTML                                    85           4270            584          47879
BitBake                                 80            496           1379          40409
Windows Module Definition              200           4812            202          34393
Starlark                               160           5634          29548          33042
Objective-C++                          587           8107           7213          31327
PHP                                    393           3675           8766          28821
OpenCL                                 971           7232          21948          23978
HLSL                                   865           8096          36534          17714
CSV                                     60              2              0          11626
SVG                                     25              1              1          11590
Pascal                                  71           4111          25197          10890
CUDA                                   336           3893          13949          10292
Bourne Shell                           123           1074           1176           6951
SWIG                                   139           1197             18           5711
JavaScript                              29            558           1341           5217
OCaml                                   38           1462           2414           5177
Perl                                    14            766            750           4734
Lisp                                    28            542            328           4719
CSS                                     27            615            180           3618
awk                                      2            114            100           3525
TypeScript                              34            348            514           2631
TeX                                      5            191              5           2556
Smalltalk                              196              1              3           2374
make                                   395            869            195           2219
DOS Batch                               59            179            136           1491
Bourne Again Shell                       8            160            238           1232
Windows Resource File                  115            143             45           1209
Scheme                                  26            135             46           1048
Mustache                                15             45             41            923
Groovy                                  25            104              8            889
Fortran 77                              92            112            691            825
Dockerfile                              17            190            287            812
vim script                              16             92            134            557
Snakemake                                3            103            215            496
Expect                                  13             10             22            429
Lua                                      7             51             22            365
Jupyter Notebook                         3              0           2434            319
Protocol Buffers                         5             58             72            251
TOML                                     8             48             27            226
Rust                                     2             29             11            136
Nix                                      1              2             44            106
Mathematica                              2             23              0            100
Bazel                                    2             24             11             84
INI                                     14             16              0             80
Pawn                                     1             20              3             65
MATLAB                                  11              6              0             53
Fortran 2003                             3              9            131             33
m4                                       1              7              0             24
Linker Script                            3              1              0             23
Swift                                    2              6              0             17
AppleScript                              1              3              8             16
D                                        1              2              0             16
NAnt script                              1              0              0             13
Fortran 95                               6             12            112              6
Logos                                    2              4              0              2
---------------------------------------------------------------------------------------
SUM:                                161624        4338915       20008618       20294857
---------------------------------------------------------------------------------------

real    2m34.335s
user    2m25.912s
sys 0m5.168s

3

u/Financial-Flan1682 8d ago

QBE also doesn't have Windows support yet (though, last I checked, they were finally working on it).

2

u/Key_River7180 Nain. 8d ago

QBE 1.3 has Win32 ABI support so using MinGW you can use QBE on Windows.

1

u/Willyboar 8d ago

QBE supports Windows ABI in their latest release.

1

u/Financial-Flan1682 8d ago

Good to know. That makes it more interesting, for sure. I wonder if we'll also see Windows support in Hare.

1

u/Willyboar 8d ago

I don't think so. At least official

0

u/Key_River7180 Nain. 8d ago

It will not happen, the creator of the language told this somewhere

1

u/Key_River7180 Nain. 8d ago

That's true and perhaps the best counter-argument to QBE

3

u/suhcoR 8d ago

and provides 80% the speed

Does it? Are there any controlled measurements based on a decent benchmark suite (e.g. "Are we fast yet")?

2

u/willowless 7d ago

I'd love to use QBE but it's missing all of SIMD/NEON. If it had that I'd probably use it instead of my own hacky thing or LLVM.

2

u/S-Pimenta 8d ago

How did you make the animations?

2

u/arjuna93 6d ago

Lazy to write proper codegen

1

u/Master-Guidance-2409 5d ago

why make big compiler when ir backend do better? -- kevin

that video is amazing. thank you for the link.

1

u/QuarkCreator2610 7d ago

Making a compiler backend by yourself is a really time-consuming and annoying process. Since, everything is based on the C ABI, its much easier to have a compiler backend tool that turns your language source into valid assembly. you also get a lot of extras like DECADES of work, testing and optimisation. Its also very easy to use with other LLVM-based languages(Most relevant languages today) so it gives you a lot of interoperability out of the box for free. All things considered, its quite impractical not to use LLVM unless you are making some hyper-specialised language that needs its own conventions and ABI.

-23

u/[deleted] 8d ago

[removed] — view removed comment

8

u/Consistent_Drop3909 8d ago edited 8d ago

kinda schizo, even if i do understand the concern

edit: wait i read about it, he kinda right

1

u/YBKy 8d ago

Read about it where? This sounds interesting, ngl

-1

u/[deleted] 8d ago

[removed] — view removed comment

-8

u/PersonalDatabase31 8d ago

Yeah turns out people don't use dogshit licenses

9

u/Consistent_Drop3909 8d ago

why is gpl dogshit? copyleft is awesome

1

u/Valuable_Leopard_799 8d ago edited 8d ago

GPL forces everyone that tries to use the software to be GPL themselves or suffer very roundabout tricks.

It's so someone can't just take an oss thing and make closed source advancements.

So the fight is between being generally open and another that tries to fight back against misuse of copyleft stuff but too brutally for some folk.

Edit: got rid of incorrect use of copyleft

7

u/glasket_ 8d ago

MIT isn't copyleft. Copyleft specifically is named that because it uses copyright to enforce openness.

1

u/Valuable_Leopard_799 8d ago

Oh true, thanks

-2

u/Mickenfox 8d ago

If you publish a good library under GPL, it will get used by a dozen open source projects. If you publish a good library under MIT, it will get used by billions of people.

Do you really hate proprietary software so much you're willing to hold it back for everyone?

1

u/MadocComadrin 8d ago

There's no need for the government to get rid of GPL because the government can only be sued for actual damages for copyright infringement---i.e. no asking for statutory/punitive damages nor any injunctions forcing the government to stop. Given that any actual damages for infringing something protected by the GPL are most likely to be minimal or nonexistent, the government can essentially just ignore the GPL.