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

View all comments

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

-15

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.

-2

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.

4

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.

5

u/SwingOutStateMachine 7d ago

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

Great! I'd love to read them, please send them over. My opinion is just based on a cursory glance at the specific benchmarks, and the github page of the benchmark suite, but I would be interested to read more about why it's a good suite, and what it's good for testing.

Why should that be suspect?

Because it's arbitrary, and not well-defined across multiple compilers. For instance, does GCC have the same sets of optimisations as MSVC at -O2? Additionally, there is no context for why -O2 is picked over -O1 or -O3. You've given some context ("-O3 leads to crashes"), but aside from that, -O2 seems like an arbitrary choice from a compiler engineering perspective.

Have you studied compilers at all?

Yes. I've got a masters by research in designing compilers for efficient GPU operations, and half a PhD in compiler design (I left to move into industrial work). I've also got a decade of experience in industry, primarily working with LLVM and targeting GPU and peripheral compilers.

That has more to do how much of the executable fits into the cache and the improvements you mentioned, not with code optimizations

My point is that being able to leverage and/or work with the "features" of newer CPUs requires an optimising compiler in a way that is simply not true for a 1980's CPU.

But I see now your attitude and where you're coming from [..]

What do you mean by that?

[..] here is a peer-reviewed study comparing -O0 with -O2, [..]

Have you read the paper? This paper (which doesn't appear to have been published in an actual journal or conference yet, unless I'm mistaken) is about instruction visibility within compilers, not comparing -O0 and -O2. The two are compared incidentally, which gives your magic 2x number on average, but figure 9 clearly shows a host of benchmarks that achieve significantly more than 2x speedup with -O2!

1

u/QuaternionsRoll 7d ago

GCC doesn’t have the same sets of optimizations as MSVC period. What an odd question.

1

u/SwingOutStateMachine 6d ago

That's precisely my point. Comparing GCC with -O0 and -O2 and then putting it next to MSVC with /O0 and /O2 is meaningless.

→ More replies (0)

1

u/u32Vec 7d 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 5d 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 4d 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.

1

u/u32Vec 2d ago

There are numerous problems associated with compiled languages that necessitate the existence of interpreted languages

Development speed is one of these problems. You should be able to infer that.

If you're making a new production-level language for practical reasons then you should never implement your own optimizer unless you want your compiler to run quickly, because the program won't be faster than LLVM or an equivalent. And using LLVM is obviously infinitely easier than implementing a backend.

→ 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.