Switch Lowering in GCC
Posted by signa11 2 days ago
Comments
Comment by LarsKrimi 1 day ago
It could not because that would ensure that 7 values for each case would be handled by function calls instead of the default case - 0/8 is 0, but 3/8 is also 0
Comment by rep_lodsb 1 day ago
cmp eax,40
ja <return 0>
test eax,7
jnz <return 0>
jmp JumpTable[rax]Comment by LarsKrimi 1 day ago
A rough benchmark of a version of that against the original shows it to be close to twice as fast for unpredictable input with GCC (4.5 ns vs 2.72 ns) on my old machine. With predictable input where x values often are the same the original version is quicker
int sparse_8_jump(int x, int val) {
unsigned int idx = (unsigned int)x >> 3;
if (idx > 5)
return 0;
if (x & 7)
return 0;
switch (idx) {
case 0:
return f0(val);
case 1:
return f1(val);
case 2:
return f2(val);
case 3:
return f3(val);
case 5:
return f4(val);
default:
return 0;
}
}Comment by ack_complete 1 day ago
mov ecx, eax
ror eax, 3
cmp eax, 5
ja <return 0>
jmp JumpTable[rcx]Comment by Findecanor 1 day ago
... unless the size of immediate parameters would have affected code size. In the example code however, all constants fit in an `imm8` parameter to `cmp`.
Comment by rurban 1 day ago
Comment by jcranmer 1 day ago
Perfect hashing for switch tables is useful mostly for large, sparse switches, but hot large, sparse switches are rare, and the closest examples I can think of (e.g., parse tables) are themselves likely generated from other tools, which could itself be changed in lieu of the compiler. It really doesn't come across as a useful optimization to try implementing in a compiler.
Comment by bobmcnamara 1 day ago
Comment by flohofwoe 1 day ago
Comment by bobmcnamara 15 hours ago
As a massage, I wish there was a flag to set a minimum branch target size. If all the branch targets for a switch are the same size then you can often turn the jump table into a multiply-accumulate, or sometimes even a shift and add.
Comment by nh2 1 day ago
For example, my bug report that `+= 2` is broken has not been acknowledged (read?) by anyone for 3 years:
const size_t n = 13; // triggers the issue
#pragma omp parallel for
for (uint32_t i = 0; i < n; i += 2) { // counts PAST 13!
https://github.com/llvm/llvm-project/issues/70687Comment by rurban 1 day ago
Comment by SubjectToChange 1 day ago
Comparing open issues in the llvm-project repository is basically equivalent to comparing the open issues in GCC, binutils-gdb, glibc, and various utilities. Moreover, the LLVM project is intended as a compiler infrastructure/library framework, so it naturally has a larger surface area for bugs/issues relative to GCC. I mean, it’s not like GCC offers a generic API to interact with GIMPLE, or any other of its numerous internal representations.
Comment by rurban 1 day ago
gcc: 244 fails
clang: 826 fails
Both should be zero. In all my compilers I'm thriving for zero.Comment by aw1621107 1 day ago
That's a bit surprising to me given how heavily Rust uses the underlying mechanisms. Do you mean specifically how restrict is used in Clang or is there still something in LLVM that is broken?
Comment by rurban 1 day ago
Comment by aw1621107 1 day ago
For what it's worth, according to Godbolt the test program in your probe seems to work correctly for Clang 12.0.0 and later, which IIRC corresponds to when Rust (re-(re-?))enabled emitting noalias.
Comment by stefan_ 1 day ago
Ahh, there it is! This issue isn't being tended to because it will resolve itself with the imminent death of OpenMP.
Comment by SubjectToChange 1 day ago
Sure, OpenMP is not a pretty API, nor is it likely to yield best-in-class performance. But OpenMP is highly portable, it enjoys first-class support GCC and LLVM/clang, and it’s very straightforward to get “good enough” performance with minimal code changes. Personally, I think it’s wonderful.
Comment by jamiejquinn 1 day ago
Comment by RossBencina 1 day ago
Comment by rurban 1 day ago
Comment by Sopel 1 day ago
Comment by garaetjjte 1 day ago
Comment by tester756 1 day ago
>The LLVM patch was rejected a decade ago
And what was the reasoning?
Comment by ahartmetz 1 day ago
Comment by saagarjha 1 day ago
Comment by ahartmetz 1 day ago
Comment by rurban 1 day ago
Comment by saagarjha 1 day ago
Comment by erk__ 1 day ago
Comment by zamalek 1 day ago
Sometimes a decision tree is definitely faster. When developing System.HashCode, I used a switch (which I think always compiles to a jump table in .Net - at least my case did back then). This was for the trailing non-block sized bytes, I think. It was either suggested, or I decided to try, a decision tree and it was significantly faster. This was _many_ years back, so I don't have the figures, but they can be better in some scenarios.
Comment by tsukikage 1 day ago
Comment by Findecanor 1 day ago
Each `jmp` to f0..fX is an optimisation of a `call` and a `ret`, and probably done in a separate pass after the switch-case code has already been expanded.
The `mov edi, esi` at the top of the function is also an optimisation, most likely done during the register allocation pass. This was only possible because all functions take the same value in the same register. Otherwise, there would probably have been also a `mov` before each of the calls.
Comment by andikleen2 1 day ago
Comment by cowlevel 1 day ago
Comment by jacquesm 12 hours ago
Comment by yksalun 1 day ago
Comment by Andi 1 day ago
Comment by p-t 1 day ago
Comment by sylware 1 day ago
Comment by ahartmetz 1 day ago
It is still possible that a later pass arranges jump targets to avoid false sharing of their entries (addresses) in prediction mechanisms and such.
Comment by sylware 1 day ago
Obvsiously engaging in pleasing as much as it can speculative/BTB hardware (to the cost of memory tables!)
Comment by ahartmetz 1 day ago
Comment by sylware 21 hours ago
I did dive in LLVM a long time ago, and OMG... we have to get rid of it.
On the long term, gcc and clang are cooked, better move to binary specifications with various ISA implementations. Ofc here, we would need a bit of optimization knowledge in order to write assembly code not too much under-performant (because the goal here would not be speed, but independence from compilers!)
But it is not that much horrible: most performant micro-architectures use the same speculative concepts, then it is a matter to find the sweet spot between all of them: a good generic conditional branch machine instruction layout of a cache line with some additional care for non-conditional branch, and use branchless logic as much as you can. It is a matter to know such patterns: hopefully all those "coding AIs" can help (never tested them, cannot access them).
Comment by arsalan17 1 day ago
Comment by yarkwright 1 day ago