I've been avoiding learning about compilers since I started learning to code, but this gives me that first proper glimpse into them. Fascinating video!
Its also interesting to note that at least for some c compilers, it is kind of a gamble if your optimizations work or make it worse. Even if your optimization is an upgrade, it can happen that the compiler will but values into bad spots, like putting a frequently used value way too far away for example. Every time you alter your code, its kind of luck based how your compiler compiles the program. Sometimes it even removes padding for some reason and you won't really notice until it adds up.
I only really use bit shifts in my code when I'm doing other bit math, otherwise I just use division. It's about what makes your code clearer, since the compiler can optimize it
3:30 just to mention that the restrict keyword is not needed in C++ in one particular case. If the two pointers are of incompatible types then C++'s strict aliasing rules apply and the compiler can assume that the pointers don't alias each other.
Well, it should be noted that bitshifting with signed numbers will not always yield the correct results as compared with dividing regardless. However, one of the things that I think people make the mistake of doing over and over again is that of using signed numbers in the first place. And real world testing has proven it well enough for me that unless you're doing ultra simple if/else branches most compilers won't convert it to an equivalent switch statement. One of the quotes that keeps being repeated that I really hate is that one from Knuth about optimization. A huge part of optimization is choosing the right algorithms and data structures and it's often the case that you can't just switch them out like going from using a linked list to a dynamic array type. There are real architectural decisions that must be made up front and whether you or Knuth realize it or not, they are most definitely part of optimization. As for LLVM, there are optimizations that it can't do with incomplete information, and the compiler author has to be aware of these limitations to know to provide that information. So merely using LLVM isn't a panacea unless you actually know what you're doing and how to use it.
hm its kinda wierd the thing that you say about signed and unsigned integers. i haven't seen anyone talk about that. I have also just made a video about switch optimizations and I have tested very big if statements and the compiler optimized it very well. I can even construct a case where a signed if can get a better result. So do you have any examples that I can see? I'm curious. Send them on my discord please 🙏
@@lowlevelgamedev9330 I don't use Discord, so I'll have to post an example with CE. The signed versus unsigned part can be seen easily enough by looking at the disassembly for shifting both. Signed integers will generate a sar while unsigned generate a shr. If you don't know x86 assembly, sar copies the "sign bit" down when you shift right while shr zeroes. You can actually exploit this for a branchless abs() function, which I'd wager most compilers will generate with optimizations turned on anyway, but if the compiler can't see your intent, signed shifting is more likely to lead to incorrect results. This brings to mind another issue with signed math, and that's pretty much all division, but more so modulus. Doing modulo math with negative numbers even yields different results in different languages, so be careful there. If YT let's me post a CE link I'll post that later today since I'll have to craft an example from scratch. All the prior examples I have are under NDA.
some people seem to be relating LLVM to the JVM or other JIT runtimes, but they are completely different. llvm uses AOT compilation while the JVM is JIT, and the intermediary languages also serve different purposes. with LLVM, its there to make compiler development easier, while in the JVM its there to make the conversion from the IR to machine code as fast as possible, to improve the JIT performance.
llvm only exists to make it easier for programmers to make compilers. In the olden days, you had to make a compiler that can generate assembly code for EVERY architecture that exists. Now, all you have to do is to make a compiler that can generate llvm IR and then the backend compiles that into machine code native to the target platform. Pretty much all of the new programming languages that are coming out are llvm-based for this reason: it's just a lot easier to do it this way. Hope that made sense.
Java virtual machine as the name implies is a virtual machine/ Java compiler compiles code into java bytecode, which gets interpreted by the program which is written in a native language like C (thus running on directly on the CPU) which is what JVM basically is. So the compile time order is: source code -> bytecode, and runtime order jvm program which runs on the CPU and which takes bytecode as input. whereas with LLVM it's source code -> IR, a unique part for every modern language, then a common part which turns IR into the machine code (linker aside). Runtime part is: your program -> cpu. Basically your program directly runs on the
LLVMIR is a sort of bytecode, yes. Don't let the acronym LLVM (which stands for Low Level Virtual Machine) fool you, though -- it's not a virtual machine. It translates and compiles that bytecode to a static binary (i.e. an exe) before any code written by the programmer is executed. On the other hand, the JVM simply interprets the bytecode inline and executes them immediately, because it assumes that the bytecode _is_ the program.
@@mage3690 ok yes. I Hope my message wasn't wrong, because my English skill isn't so good So I retry: I tried to find a similar code to have an idea to understand better why is useful. And the first think I had, was the idea the bytecode to have pseudo-compilation to transport your code across the platforms. Ok, the JVM wasn't a bad word in this case. Thanks you all
My opinion is campilers are smarter than humans but if you working with specyfic hardware you could optimized it even More because you know exactly what you are working with and what are bottlenecks for this exact hardware And now my question if we are limited by memory speed instead of cpu is there a way to tell compiler it or it will find it out itself Because i assume compiler will focus on cpu preformence but if memory bandwidth and latency are bottlenecking it is there a way to tell compiler to use other method but less memory intensive
I think it does more or less the same with their own fronted/IR/backends, but these are internal and not intended for external use by other projects. Or more accurately, they make no promise for stable APIs.
gcc makes assembly code that is native to the platform that you are currently on, you can specify different platforms with the -march and -mcpu compiler flags. gcc then calls the assembler to assemble the assembly code that it just generated. This creates object files (usually one object file per CPP/C file) and then the linker combines those object files into an executable. LLVM-based compilers just spit out llvm IR and then the llvm backend does all the things that LLGD explained in this video.
gcc does the same thing, more or less. gcc calls its IR GIMPLE, and you can compile to GIMPLE by simply passing the flag -fdump-tree-gimple to the compiler, and compile from GIMPLE to machine code by way of the -fgimple flag on a file with GIMPLE in it. I'm not remotely knowledgeable enough to even begin to know the differences between gcc and Clang, though. Just because it looks identical on the surface doesn't mean that it's remotely similar underneath the hood.
yo I actually have a video for that th-cam.com/video/sX52Hak4SaY/w-d-xo.html and you can also find help on my discord server (link in video description)
and then there's mojo: python syntax with optional typing that unlocks the ability to scale across threads, vectorization, and GPU/NPU hardware. from the creator of LLVM.
no, llvm uses AOT compilation while the JVM is JIT. their purposes are also completely different. the intermediary language in llvm is to make compiler development easier, while in the jvm its there to make the conversion from the IR to machine code as fast as possible, to improve the JIT performance.
@@chickenbobbobba I was talking about idea not about implementation. Yes, jvm is jit and GraaalVM allows aot compilation. But idea is still there. So c and c++ are in a way being java-like.
@@radosmirkovic8371 well no, the idea is exactly the exact thing that makes them different. javas IL is present at runtime while C's is not. and again, LLVM only uses it to make it easier to write a compiler, while the JVM uses it to aid with making cross platform but still performant code.
"I know this video is long"
It's only 13 minutes...
13 minutes with no subway sirfers nowadays is a lot tho 😭
Pff.. Everyone knows that the compiler isn't magic. The linker is.
Specifically black magic
In: source code
Out: machine code
Me: Mais qu'est-ce donc cette diablerie ?
underated comment
- LLVM contributor
Uh, linking is like the simplest step
@@sachahjkl Not with LTO enabled.
Inspecting the assembly and seeing the optimizations is so interesting sometimes.
Matt Godbolt’s *CompilerExplorer* is a great tool to see how various compilers generate assembly.
I've been avoiding learning about compilers since I started learning to code, but this gives me that first proper glimpse into them. Fascinating video!
glad you like it 💪💪
Let the compiler cook
Its also interesting to note that at least for some c compilers, it is kind of a gamble if your optimizations work or make it worse. Even if your optimization is an upgrade, it can happen that the compiler will but values into bad spots, like putting a frequently used value way too far away for example. Every time you alter your code, its kind of luck based how your compiler compiles the program. Sometimes it even removes padding for some reason and you won't really notice until it adds up.
9:16 I think "CFG" stand for "Control Flow Graph" in this instance
hm yeah maybe you are right
I only really use bit shifts in my code when I'm doing other bit math, otherwise I just use division. It's about what makes your code clearer, since the compiler can optimize it
Bit manipulation is quite fast and you can store quite a bit of state in a single byte of data.
Good timing, I did research on this exact thing like 2 days ago!
3:30 just to mention that the restrict keyword is not needed in C++ in one particular case. If the two pointers are of incompatible types then C++'s strict aliasing rules apply and the compiler can assume that the pointers don't alias each other.
that sounds like trouble 😂😂😭😭 everyonebe doing pointer aritmetics
good to know tho
@@lowlevelgamedev9330 It turns out that strict aliasing rules exist in C too. So my initial comment might not be so helpful.
Great explanation! Loved this!
Well, it should be noted that bitshifting with signed numbers will not always yield the correct results as compared with dividing regardless. However, one of the things that I think people make the mistake of doing over and over again is that of using signed numbers in the first place. And real world testing has proven it well enough for me that unless you're doing ultra simple if/else branches most compilers won't convert it to an equivalent switch statement.
One of the quotes that keeps being repeated that I really hate is that one from Knuth about optimization. A huge part of optimization is choosing the right algorithms and data structures and it's often the case that you can't just switch them out like going from using a linked list to a dynamic array type. There are real architectural decisions that must be made up front and whether you or Knuth realize it or not, they are most definitely part of optimization. As for LLVM, there are optimizations that it can't do with incomplete information, and the compiler author has to be aware of these limitations to know to provide that information. So merely using LLVM isn't a panacea unless you actually know what you're doing and how to use it.
hm its kinda wierd the thing that you say about signed and unsigned integers. i haven't seen anyone talk about that. I have also just made a video about switch optimizations and I have tested very big if statements and the compiler optimized it very well. I can even construct a case where a signed if can get a better result. So do you have any examples that I can see? I'm curious. Send them on my discord please 🙏
@@lowlevelgamedev9330 I don't use Discord, so I'll have to post an example with CE. The signed versus unsigned part can be seen easily enough by looking at the disassembly for shifting both. Signed integers will generate a sar while unsigned generate a shr. If you don't know x86 assembly, sar copies the "sign bit" down when you shift right while shr zeroes. You can actually exploit this for a branchless abs() function, which I'd wager most compilers will generate with optimizations turned on anyway, but if the compiler can't see your intent, signed shifting is more likely to lead to incorrect results. This brings to mind another issue with signed math, and that's pretty much all division, but more so modulus. Doing modulo math with negative numbers even yields different results in different languages, so be careful there. If YT let's me post a CE link I'll post that later today since I'll have to craft an example from scratch. All the prior examples I have are under NDA.
I wonder if anyone has ever optimized out the Monte Carlo estimation of pi.
Lol thats an already well documented problem
some people seem to be relating LLVM to the JVM or other JIT runtimes, but they are completely different. llvm uses AOT compilation while the JVM is JIT, and the intermediary languages also serve different purposes. with LLVM, its there to make compiler development easier, while in the JVM its there to make the conversion from the IR to machine code as fast as possible, to improve the JIT performance.
If anything, people should compare LLVM IR to GCC IR (called GIMPLE). Even Pascal p-code back in the old days was accomplishing the same task.
I love your videos ❤
Is llvm like Java Virtual Machine?
Or better, the idea is similar of bytecode?
kinda? except the bytecode gets further compiled to architecture specific machine code instead of interpreted
llvm only exists to make it easier for programmers to make compilers. In the olden days, you had to make a compiler that can generate assembly code for EVERY architecture that exists. Now, all you have to do is to make a compiler that can generate llvm IR and then the backend compiles that into machine code native to the target platform. Pretty much all of the new programming languages that are coming out are llvm-based for this reason: it's just a lot easier to do it this way. Hope that made sense.
Java virtual machine as the name implies is a virtual machine/ Java compiler compiles code into java bytecode, which gets interpreted by the program which is written in a native language like C (thus running on directly on the CPU) which is what JVM basically is. So the compile time order is: source code -> bytecode, and runtime order jvm program which runs on the CPU and which takes bytecode as input.
whereas with LLVM it's source code -> IR, a unique part for every modern language, then a common part which turns IR into the machine code (linker aside). Runtime part is: your program -> cpu. Basically your program directly runs on the
LLVMIR is a sort of bytecode, yes. Don't let the acronym LLVM (which stands for Low Level Virtual Machine) fool you, though -- it's not a virtual machine. It translates and compiles that bytecode to a static binary (i.e. an exe) before any code written by the programmer is executed. On the other hand, the JVM simply interprets the bytecode inline and executes them immediately, because it assumes that the bytecode _is_ the program.
@@mage3690 ok yes.
I Hope my message wasn't wrong, because my English skill isn't so good
So I retry: I tried to find a similar code to have an idea to understand better why is useful.
And the first think I had, was the idea the bytecode to have pseudo-compilation to transport your code across the platforms.
Ok, the JVM wasn't a bad word in this case.
Thanks you all
My opinion is campilers are smarter than humans but if you working with specyfic hardware you could optimized it even More because you know exactly what you are working with and what are bottlenecks for this exact hardware
And now my question if we are limited by memory speed instead of cpu is there a way to tell compiler it or it will find it out itself
Because i assume compiler will focus on cpu preformence but if memory bandwidth and latency are bottlenecking it is there a way to tell compiler to use other method but less memory intensive
99% of the magic happens in the optimizer and boy is it ever a moving target that doesn't rest 😅
Waiting for LLVM tutorial
Zig doesn't use LLVM now.
yo it still does, the plan to replace llvm is something that will happen in the very distant future
Is less than 13 minutes considered long nowadays...
yes without a subway surfers / minecraft parcour video 😭
@@lowlevelgamedev9330 Imma have to stop saying I'm a zoomer, I just don't qualify.
How does gcc do it for example if it doesn't use LLVM as a backend?
I don't know much about gcc but I think it does something similar
I think it does more or less the same with their own fronted/IR/backends, but these are internal and not intended for external use by other projects. Or more accurately, they make no promise for stable APIs.
gcc makes assembly code that is native to the platform that you are currently on, you can specify different platforms with the -march and -mcpu compiler flags. gcc then calls the assembler to assemble the assembly code that it just generated. This creates object files (usually one object file per CPP/C file) and then the linker combines those object files into an executable. LLVM-based compilers just spit out llvm IR and then the llvm backend does all the things that LLGD explained in this video.
gcc does the same thing, more or less. gcc calls its IR GIMPLE, and you can compile to GIMPLE by simply passing the flag -fdump-tree-gimple to the compiler, and compile from GIMPLE to machine code by way of the -fgimple flag on a file with GIMPLE in it. I'm not remotely knowledgeable enough to even begin to know the differences between gcc and Clang, though. Just because it looks identical on the surface doesn't mean that it's remotely similar underneath the hood.
@@mage3690 Guess I was wrong about that. I always thought that gcc worked like oldschool compilers.
wrong, people use zig to compile c code only
yaayaay
Blud is talking 'bout preformance and using LLVM LOL. Compile kernel with clang and try to use it LOL
GCC my beloved
...what about it? Show up some numbers ;)
@@werthorne at least clang will use cmov's when appropriate
But... C-- is already an IR for GHC...
7:30 ok, that's another C--.
Multiple people have had the thought to name a language C--. One is used on embedded platforms and has existed for a couple of decades at least.
Please give me roadmap to learn cpp for game development
yo I actually have a video for that
th-cam.com/video/sX52Hak4SaY/w-d-xo.html
and you can also find help on my discord server (link in video description)
@@lowlevelgamedev9330 thank sir
petition to make python a compiled language
Wish you luck making a compiled language that harkens back to python :)
you can actually compile python to an exe with an external tool
@@lowlevelgamedev9330 new thing known, IQ increased. Monkey brain happy
and then there's mojo: python syntax with optional typing that unlocks the ability to scale across threads, vectorization, and GPU/NPU hardware. from the creator of LLVM.
24 seconds ago, no views, yay first comment
still no views according to youtube 2 comments and one like let's go 😂
bro fell off
hi
So llvm has stolen idea from jvm and tweaked it.
no, llvm uses AOT compilation while the JVM is JIT. their purposes are also completely different. the intermediary language in llvm is to make compiler development easier, while in the jvm its there to make the conversion from the IR to machine code as fast as possible, to improve the JIT performance.
@@chickenbobbobba I was talking about idea not about implementation. Yes, jvm is jit and GraaalVM allows aot compilation. But idea is still there. So c and c++ are in a way being java-like.
@@radosmirkovic8371 well no, the idea is exactly the exact thing that makes them different. javas IL is present at runtime while C's is not. and again, LLVM only uses it to make it easier to write a compiler, while the JVM uses it to aid with making cross platform but still performant code.
@@chickenbobbobba Thanks for clarification.
@@radosmirkovic8371 also the idea of an intermediary language is not a new one, they existed well before java became a thing.
what is this crap
Early on this one yay