Switching it up a bit

xania.org2025年12月23日 12:00

Written by me, proof-read by an LLM. Details at end.

The standard wisdom is that switch statements compile to jump tables. And they do - when the compiler can't find something cleverer to do instead.

Let's start with a really simple example:

Here the compiler has spotted the relationship between and the return value, and rewritten the code as: - pretty neat. No jump table, just maths!xif (x < 5) return (x+1) * 100; else return 0;

If we mix up the code a bit so there's no obvious relationship between the input and the return value:

Still no jump table: Now the compiler has built a bespoke lookup table () and then uses to index into it (after checking it's in bounds).CSWTCH.1x

For "dense" case statements, like the ones above, the compiler can be smart. But even with relatively sparse inputs, the compiler can work its magic. Consider this "is it whitespace?" routine:1

That avoids any kind of jump table; and in fact even avoids a branch:still

The compiler has built a bitmask where each bit says "should we consider this character to be whitespace". To fit the range of bits needed to cover all the whitespace characters, the compiler indexes into the bitmask with . The bit test instruction () will test any bit position, but our 32-bit bitmask only has meaningful data in positions 0-31. The compiler checks that to ensure we're within the valid range of the bitmask (covering tab at position 0 through space at position 23), and replaces the result with 0 for anything outside this range.(x - 9)(x - 9) <= 24bt2

Just to see what else the compiler can generate, let's take a look at both a dense and sparse example that the compiler can't replace with a table (you'll need to scroll around in the Compiler Explorer panes to see more):

For the dense case, the compiler does make a jump table, and indexes by to jump to the right routine. For the sparse case, the compiler has to fall back to essentially a set of statements, comparing and branching. However, it's clever enough to compare a "mid-range" value first (), and if the value is greater, jumps to code that only looks at the and . So it's essentially a binary serarch tree of comparisons.xfuncif()2511x528448653

Different compilers employ quite different tricks, so take some time to see what clang does for all the above examples.

Write clear switch statements; let the compiler decide whether that means multiplication, bitmasks, or jump tables. It's pretty darned good at it!

See that accompanies this post.the video

This post is day 23 of , a 25-day series exploring how compilers transform our code.Advent of Compiler Optimisations 2025

← | →Clever memory tricksWhen compilers surprise you

This post was written by a human () and reviewed and proof-read by LLMs and humans.Matt Godbolt

.Support Compiler Explorer on or , or by buying CE products in the PatreonGitHubCompiler Explorer Shop

is_whitespace(char):subedi,9; edi = x - 9 (`\t`)moveax,8388631; eax = 0b100000000000000000010111btrax,rdi; test bit edi in the eax bitmasksetcal; al = (bit was set) ? 1 : 0xoredx,edx; edx = 0cmpdil,24; compare edi with 24cmovnbeax,edx; replace al with edx (0) if not belowret; return

  1. Of course you should use . isspace()

  2. The instruction uses the bit position modulo the operand size. In this particular case the compiler emits a so values of (x-9) greater than 64 would potentially map onto some of the set bits. btbt rax, rdi

  3. If you comment out the , at least for GCC, you'll see the compiler goes back to compare-and-branch. case 4