Compiler Explorer
-
Compiler Explorer, or similar inspection tools are an invaluable tool for optimization and micro-benchmarking.
-
An alternative you might find interesting is the use of local tools to the same end. If you’re not familiar with the necessary tools you can use two of the scripts that I created for my own use:
vir_inspect.sh(requires zshsudo apt install zsh) andvir_dump_asm.sh.-
vir_inspect.sh /path/to/executableshows a filtered list of functions in the executable. Call
vir_inspect.sh /path/to/executable <pattern>and it will filter the list of functions using the last argument. If a single function remains it skips the next step.
-
Enter the number of the function you want to inspect.
-
The tool will show a disassembly of the function. If debug information is available (compiled with
-g), source code annotation will be shown. -
After the disassembly,
llvm-mcawill interpret the complete function. This is often not very useful, unless the function was carefully crafted to be interpreted byllvm-mca. But feel free to extend the script to insert# LLVM-MCA-BEGIN name0and# LLVM-MCA-END name0markers before feeding intollvm-mca. vir_dump_asm.sh <source file>will compile and dump asm.
-
-
Another alternative for Vim users: I hacked up a Compiler Explorer-like vim plugin for myself. It’s available at vim-compilerexplorer.
-
When looking at x86 asm, I recommend to use Intel syntax instead of AT&T assembler syntax. (Makes it easier when consulting Intel documentation.)
- Quick x86 asm Introduction (by Matt Godbolt):
- General purpose registers (integers, pointers):
rax,rbx,rcx,rdx,rsi,rdi,rbp,rip,rsp,r8–r15 - Floating-point and SIMD registers:
xmm0–xmm15 - ABI-specific registers:
rdi,rsi,rdx, … as function argumentsraxis the return value rspis the “stack pointer” (pushandpopimplicitly modifyrsp)rax,eax,ax,ah, andalall alias the same register: 8 Bytes, 4 Bytes, 2 Bytes, and 1 Bytes (high and low)
- General purpose registers (integers, pointers):
- Instructions (
ophere is a placeholder):op(often implicit src/dest)op dest(often in/out and implicit src)op dest, src(often in/out dest)op dest, src1, src2
- Load/store/copy instruction (move):
mov eax, edi(eax = edi)mov eax, DWORD PTR[rdi+rsi*4]“load from memory” (eax = *(int*)(rdi + rsi * 4))
- Address calculation “load effective address”
lea eax, [rdi+rsi](eax = rdi + rsi)
- Important patterns:
xor eax, eax: produces0test edi, edi: set flags (special register in the CPU)sete al: Set “a” register to0or1depending on “equal” (ZF: zero flag)
- Important help:
- Right click on an instruction on CE: “view assembly documentation”
- Interesting floating-point instructions:
- All of these instructions may have a
vprefix (e.g.vmovssinstead ofmovss), which you can ignore. It’s only a different instruction encoding. movss: move scalar single-precision (op1 = op2)addss: add scalar single-precision (op1 += op2orop1 = op2 + op3)fmadd132ss: fused multiply-add 132 (argument order:op1 = op1 * op3 + op2) scalar single-precisionmovd: move doubleword (32 bits) (op1 = op2)movsd: move scalar double-precisionaddsd: add scalar double-precision
- All of these instructions may have a
- Later we will also see instructions that use packed instead of scalar
in their mnemonic. E.g.
addpsinstead ofaddss. “packed” means SIMD.
Explore the topics we just covered
- Play with integer quirks
- promotion (how to see result types?)
- common types
- signed and unsigned overflow
x + 1 > x- arithmetic on
boolstd::unreachable()- Look at assembly of by-value and by-reference
intarguments- When does the compiler emit
rspmodification?- Try infinite recursion: look for
rsp- Try out small examples with
std::vector<T>andstd::array<T, N>- Look at asm of copy and move
- Try out different styles of iteration
tips
- open compiler diagnostics output window
- consider using “Execute the code” under “Output…”
- Under “Add new…” there’s a “Stack Usage” window