Compiler Explorer
godbolt.org
godbolt.org
General stuff: we're always looking for help; everything's open source on GH: https://github.com/compiler-explorer/ (the base project, our cloud setup, all our build scripts, etc). The most valuable way to help us is with issues and PRs, or hang out on our Discord (https://discord.gg/zNNgyRKh). Then spread the word, and last we welcome sponsors on GH (https://github.com/sponsors/mattgodbolt) or Patreon (https://www.patreon.com/mattgodbolt).
For a few bucks, he will send you authentic Godbolt stickers for your laptop lids, for the ultimate in geek cred.
I'm pretty sure you already know of sharplab.io. A lot of us C# devs prefer it over Compiler Explorer for a bunch of reasons, but the main one is really the speed.
My guess is that Sharplab gets it speed because it hooks directly into the JIT compiler itself. There is no overhead from touching disk or the linker.
.NET 7 will be officially released in a month or so.
Currently C#/F#/VB support in Compiler Explorer relies on prejitting which has various issues.
Edit: Oh wow I didn't realize the website does other languages, thanks!
Maybe there is a way to plug hsdis into the workflow.
EDIT: Actually there is already a ticket discussing this, https://github.com/compiler-explorer/compiler-explorer/issue...
$ curl -sL https://godbolt.org/api/languages
Id | Name
csharp | C#
fsharp | F#
vb | Visual Basic
go | Go
c | C
c++ | C++
fortran | Fortran
assembly | Assembly
circle | C++ (Circle)
circt | CIRCT
hlsl | HLSL
cppx | Cppx
crystal | Crystal
dart | Dart
erlang | Erlang
carbon | Carbon
hook | Hook
cppx_blue | Cppx-Blue
cppx_gold | Cppx-Gold
mlir | MLIR
cuda | CUDA C++
analysis | Analysis
python | Python
racket | Racket
ruby | Ruby
typescript | TypeScript Native
d | D
ada | Ada
cpp_for_opencl | C++ for OpenCL
openclc | OpenCL C
llvm | LLVM IR
cpp2_cppfront | Cpp2-cppfront
rust | Rust
ispc | ispc
java | Java
kotlin | Kotlin
nim | Nim
pony | Pony
scala | Scala
solidity | Solidity
clean | Clean
pascal | Pascal
haskell | Haskell
ocaml | OCaml
swift | Swift
zig | Zig
$ CL-USER> (defun do-something (a b)
(format t "a: ~a~%b: ~a~%~%" a b))
DO-SOMETHING
CL-USER> (disassemble #'do-something)
; disassembly for DO-SOMETHING
; Size: 216 bytes. Origin: #x537D0F66 ; DO-SOMETHING
; 0F66: 498B4510 MOV RAX, [R13+16] ; thread.binding-stack-pointer
; 0F6A: 488945F8 MOV [RBP-8], RAX
; 0F6E: 840425F87F0050 TEST AL, [#x50007FF8] ; safepoint
; 0F75: 498BB5E8050000 MOV RSI, [R13+1512] ; tls: *STANDARD-OUTPUT*
; 0F7C: 4883FEFF CMP RSI, -1
; 0F80: 480F443425B05B1450 CMOVEQ RSI, [#x50145BB0] ; *STANDARD-OUTPUT*
; 0F89: 488975E0 MOV [RBP-32], RSI
; 0F8D: 4883EC10 SUB RSP, 16
; 0F91: 488B1570FFFFFF MOV RDX, [RIP-144] ; "a: "
; 0F98: 488B7DE0 MOV RDI, [RBP-32]
; ...
And of course it works for the functions that are defined by the standard, too: CL-USER> (disassemble #'format)
; disassembly for FORMAT
; Size: 581 bytes. Origin: #x52A12CCD ; FORMAT
; CCD: 840425F87F0050 TEST AL, [#x50007FF8] ; safepoint
; CD4: 48817DF817810050 CMP QWORD PTR [RBP-8], #x50008117 ; NIL
; CDC: 0F85CB000000 JNE L4
; CE2: 4881EC90000000 SUB RSP, 144
; CE9: 4883E4F0 AND RSP, -16
; CED: 488D7C240F LEA RDI, [RSP+15]
; CF2: 48C747F1E5000000 MOV QWORD PTR [RDI-15], 229
; CFA: 48C747F93E000000 MOV QWORD PTR [RDI-7], 62
; D02: 4881ECB0000000 SUB RSP, 176
; D09: 4883E4F0 AND RSP, -16
; D0D: 488BC4 MOV RAX, RSP
; ...
CL-USER> (disassemble #'first)
CL-USER> (disassemble #'+)If you're talking about the speed of other languages than C#, then most of the time is in the compilers itself, not the fairly limited pre- and post-processing we do. We have a pretty sophisticated setup (at least, I think it is...) for handling the 1,500 compiler/language combinations, hundreds of libraries etc (around 2TB of data), but there's always tradeoffs between fast start-up time, manageability and simplicity of adding new compilers, and the run-time performance of running the compilers etc.
1. You can figure out things about the assembly even without understanding assembly (e.g. lines of source translating into 0 lines of output vs many lines of output).
2. You have labels.
3. You can figure out some of the assembly on your own. Say: `mov %r1 %r2` - it probably moves what's in entity %r1 into a similar entity %r2, or vice-versa. etc.
4. You can see what the executable outputs
5. and most importantly: You can read compiler warnings and errors...
push rbp
this is going to take the contents of rbp and push it onto the top of the stack - this will probably also change the stack pointer mov rbp, rsp
move goes left <-, like a = 5, not 5 = a.
so, copy the updated stack pointer into rbp mov DWORD PTR [rbp-4], edi
now, I'm not 100% sure, but I believe this guy puts edi just under the value we pushed to the top of the stack mov eax, DWORD PTR [rbp-4]
Take that value, and put it into eax, I'm not 100% sure why it's not just mov eax edi. imul eax, eax
integer multiply, this is the part that does the double. pop rbp
restore rbp (which we messed with) ret
and we're done.there are at least three holes in my understanding - but those three are not _that_ hard to track down.
1, does the stack pointer actually auto increment? (I think it does) 2, imul overflow and setting sign flags and such. - that shouldn't be hard to run down.
3, what is the c calling convention? it looks like the argument is top of stack, but also in edi - is that shuffling really needed? I think there's a bucket of implicit behavior there that's kinda scary.
I would _hope_ unless linking to a library, whatever called this, just did the imul eax eax.
My understanding may be deeply flawed, but explaining my assumptions and my understanding does two things.
1, it helps me learn.
2, it helps others re-evaluate their assumptions and possibly see from a different viewpoint.
I'm not saying spam compiler lists. But a clear and well thought out question can certainly advance discussion. It forces people to formalize their assumptions.
With -O3, the code is:
imul edi, edi
mov eax, edi
ret
Yep, the calling convention for x86-64 on Linux and macOS passes the first six integer arguments in rdi, rsi, rdx, rcx, r8, and r9, and then spills to stack.Having originally learned the basics of assembly on the chronically register-deprived x86, it took me a while to get used to the fact that standard CCs now pass things in registers (and rsi and rdi in particular, retaining their ancient names while being completely general-purpose these days).
notice also there is a 128-byte space ("red zone") before %rsp that keeps its contents between function calls but preserved by OS during interrupts. So, very temporary values (between function calls) can be used with negative offsets to %rsp. Not all compilers utilize this.
About this code (note opposite order of register movement - there are two main styles of displaying x86 assembly code): pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
the comment was: Compiler allocates some space for local values on function enter. That's why it subtracts value from %rsp on enter. This doesn't depend on whether %rbp is used as frame pointer. After that, this place is used with positive offsets upon %rsp. Also, if this function calls another one, %rsp shall be aligned on 16-byte boundary for each call, so, in that case compiler shall subtract 8 from %rsp on each enter.I think most of the confabulation of C++ is necessary to get the semantics needed for it to work right. Especially with all of the optimizations that compilers are expected to make. I found the reasoning behind switching from just rvalues/lvalues to the 5+ types they have now to be fascinating, for example.
Instruction counts are only useful if everything is guaranteed to be in registers.
It was a completely unnecessary instruction from a correctness perspective, because it had no effect on the answer. However, it was important for performance; removing the instruction made the calculation slower.
It would be fun to non-destructively randomize the instruction stream and have an ML model learn how to remove hazards.
Now any load from the L3 cache memory or from the main memory takes much more time than any other instruction (not counting exceptions generated by instructions, which include many memory accesses that slow them down, or deprecated instructions that are kept for backwards compatibility and that are executed by long microcode sequences).
> I have a colleague who has really no idea what he's talking about with respect to machine performance, and who did not have the requisite knowledge of how to peep at the assembly code of a given function with the standard tools like objdump, who now loves to send everyone godbolt links in slack, along with his suppositions about which function will be faster, based entirely on vibes (mostly, instruction count).
This, however, just no.
Write program -> compile -> disassemble w/ some mapping -> make notes -> repeat.
Eventually your brains pattern recognition starts to allow you to do neat things with disassembles of programs without source code.
I do totally get how some people learn just enough to be annoying. Generally I still think that's not a good reason to gatekeep them.
Paste in assembly code, check "Trace Table" and run, then "Open Trace". Not sure if it will help with your annoying colleague, but it gives a much more concrete idea about how a processor will execute any given code.
Or, if you want to channel their energy into something slightly more direct, there's also https://quick-bench.com/ which allows easy micro-benchmarking. Still not guaranteed to be relevant to any real-world scenario, but more data-driven than "vibes".
Even on JVM and .NET there are ways to dump it, while on the various JVM implementations it requires a plugin if not using a debug build, on the .NET side, you can use show Assembly on Visual Studio, or make use of WinDBG with SOS plugin.
Here's an example: https://godbolt.org/z/qjsErWzcs
https://fouronnes.github.io/cppiceberg/
(Entries are clickable.)
Wrote him a message and told him how much we appreciated his work at my previous workplace and how we used it all the time to settle debates about C++ code.
Wonderful guy, back then he was very pleasantly surprised that people actually used his website.
Much like you could argue that Dr Frankenstein was the real monster in that story, I'd say Matt Goldbot was the real Compiler Explorer all along.
Discussed 3mo ago on HN: https://news.ycombinator.com/item?id=32079227
Outside of decompiling some code on Godbolt, peeking into the assembly on VS Code, I've also been practising with Rust's inline assembly, quite pleasant.
Compiler Explorer - https://news.ycombinator.com/item?id=24066570 - Aug 2020 (48 comments)
Compiler Explorer - https://news.ycombinator.com/item?id=18671993 - Dec 2018 (44 comments)
RMSbolt – An implementation of the Godbolt compiler-explorer for Emacs - https://news.ycombinator.com/item?id=18104167 - Sept 2018 (9 comments)
Compiler bug in MSVC? - https://news.ycombinator.com/item?id=16142512 - Jan 2018 (2 comments)
C compiler undefined behavior: calls never-called function - https://news.ycombinator.com/item?id=15147335 - Sept 2017 (83 comments)
GodBolt: Enter C, get Assembly - https://news.ycombinator.com/item?id=13182726 - Dec 2016 (151 comments)
Compiler Explorer – now with side-by-side compiler comparison - https://news.ycombinator.com/item?id=12627295 - Oct 2016 (6 comments)
Compiler Explorer - https://news.ycombinator.com/item?id=11671730 - May 2016 (38 comments)
Interactive C++ Assembly Explorer - https://news.ycombinator.com/item?id=9861294 - July 2015 (8 comments)
See What Your C Function Compiles To - https://news.ycombinator.com/item?id=9808870 - June 2015 (2 comments)
C/C++ Fiddle - https://news.ycombinator.com/item?id=7593109 - April 2014 (40 comments)
Interactively watch how your C++ gets compiled. - https://news.ycombinator.com/item?id=4020814 - May 2012 (1 comment)
I don't know what an Optiver salary would be (though I can guess: I work in finance too), but no, it's absolutely not a living wage :), lucky though I am to have anything for an Open Source project.
I do it as it's something I feel strongly about; it's got my name (and reputation!) staked on it (not as I'd planned it); and it's given me enormous opportunities too!
The story that I heard, was that he was arguing with someone about some compiler optimization, and created the site to prove a point.
Obviously the results will be identical but because the iterators look fancy (and are easier to think about) maybe there's some object getting constructed, it might be a lot heavier - right? Nope, same machine code. Matt initially did this much more manually, the Godbolt web site is just that same idea getting further enriched over time.
This is even more striking for something like Rust's iterators that don't look at all like it's just the C-style for loop, there's a call to make the iterator IntoIterator::into_iter() and then repeated calls to its next() method, sounds expensive - but nope, once again the optimiser can see what's going on here and emit the same machine code. Having a tool like Godbolt to confirm (or sometimes refute) the belief that these things are actually the same is really useful, as even after confirming that optimisation is needed if the proposed "optimisation" doesn't change the machine code it wasn't an optimisation, just making the program needlessly harder to understand.
The Compiler Explorer backstory starts here: https://youtu.be/bSkpMdDe4g4?t=4m57s
> Having a tool like Godbolt to confirm (or sometimes refute) the belief that these things are actually the same is really useful, as even after confirming that optimisation is needed if the proposed "optimisation" doesn't change the machine code it wasn't an optimisation, just making the program needlessly harder to understand.
I'm not sure I understand this. Wouldn't you expect higher-order code to be easier to optimise, since it comes closer to telling the compiler what you want to do, so that the compiler can figure it out, rather than forcing the compiler to divine the big-picture intention of a bunch of low-level instructions?
And, if high-level code generates exactly the same machine code as low-level code, isn't that an argument in favour of high-level code—it lets you code declaratively, saying what you mean—rather than against high-level code? An optimisation might be optimising for intelligibility, not just run-time … and, while an experienced low-level C programmer might find the low-level code more readable, surely the non-expert maintenance programmer who comes afterwards will be grateful not to have to recognise the low-level patterns but rather have them spelled out in high-level code?
Optimization is an NP-hard problem. What compiler backends do these days is mostly to pattern match known optimizable code blocks. Some of the other optimizations are an approximation of the actual solution. The order of optimization type being made also affects the result.
So in a perfect world where we could solve NP-hard problems, higher-level code (with more constraints put on it -- as in Rust traits, not C++ templates) would be easier to optimize. But since we don't live in that utopia, nope.
> Optimization is an NP-hard problem. What compiler backends do these days is mostly to pattern match known optimizable code blocks. Some of the other optimizations are an approximation of the actual solution. The order of optimization type being made also affects the result.
Right, and that's what I meant—although I certainly see why it sounded like I was referring to some infallible and perfect optimisation process.
To be precise—and sticking with the theme of iterators from my parent, though there's nothing particularly special except that it's a familiar pattern—if there's one high-level iterator construct, isn't it more likely that the average programmer will write each invocation of an iterator in the way that the compiler expects; whereas, if each user has to roll their own iterator, then different average programmers will roll different iterators, and it's more likely that a programmer will write something so baroque that the compiler doesn't realise it can apply a known optimisation?
This assumes a "sufficiently smart compiler", whereas if you write the exact low level basic for loop (or unroll it manually or whatever) you want you can be confident that even a pretty dumb compiler is still going to output something close to optimal.
It just seems like most compilers are "sufficiently smart" for this sort of thing nowadays (and have been for quite some time). But it's not always the case, and the code you think would obviously be optimized by the compiler isn't always. So it pays to check these things if you're writing code for something where potentially eking out extra performance really matters.
There is a noscript/ basic (x)html version AND it does handle the _canonical_ gcc aka the last C compilable gcc (4.7.4).
Many of the optimization tricks hidden deep into those massive and complex software will be yours now.
for (i = 0; i < 65536; i++)
gets compiled as .L2:
add rax, 8
cmp rax, 524288
jne .L4
Is there any fundamental reason why the loop increments with 8 instead of 1? Or is this completely arbitrary? Thanks.The unoptimized code increments by 1 to 65535, but the memory accesses use scaling. Well, not exactly. We see this:
lea rdx, [0+rax*8]
mov rax, QWORD PTR [rsp-120]
add rax, rdx
movsd xmm0, QWORD PTR [rax]
This LEA here, though it means "load effective address" is not actually an effective address calculation. The base address is zero, and so this is just LEA being exploited to multiply RAX by 8, and get that into RDX. RAX is then clobbered with the base address of an array, to which the scaled displacement is added and then finally used to make an access.You can also use an "execute only" view instead of the default "show me the assembly": https://godbolt.org/z/cfvEvnK56
Hope that helps!
Very typical for today's world where javascript coder counts as software engineer.
>>> Very typical for today's world where javascript coder counts as software engineer.
Oh, hello software engineer from yesterday's world.
Curious thing is, you yourself dropped into gcc studying tool discussion clearly having no idea what I was criticizing in it.