-1

I was learning about how switch-statements work under the hood in C, so I decided to look at the compiled code. From what I understand, C uses a jump table to implement switch-statements. However, I don't see any labels or jump instructions in the assembly code.

I am on Apple silicon, so it is ARM assembly. Please take a look and tell me what you think. Thanks :)

C code:

int main (int argc, char *argv[]) {
    int one;
    int two;
    int three;
    int four;
    
    switch (1) {
        case 1:
            one = 1; 
            break;
        case 2:
            two = 2; 
            break;
        case 3:
            three = 3;
            break;
        default:
            four = 4;
            break;
    }
    
    return 0;
    
}

ARM code:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 14, 0 sdk_version 14, 4
    .globl  _main                           ; -- Begin function main
    .p2align    2
_main:                                  ; @main
    .cfi_startproc
; %bb.0:
    sub sp, sp, #32
    .cfi_def_cfa_offset 32
    mov x8, x0
    mov w0, #0
    str wzr, [sp, #28]
    str w8, [sp, #24]
    str x1, [sp, #16]
    mov w8, #1
    str w8, [sp, #12]
    add sp, sp, #32
    ret
    .cfi_endproc
                                        ; -- End function
.subsections_via_symbols
7
  • 12
    The compiler is free to generate any code it pleases, if the end result is indistinguishable. The code does nothing anyway. All it needs to do is return 0; Commented May 21 at 20:41
  • 1
    Since nothing is done with those variables that are assigned, I'm kind of surprised that the compiler didn't reduce this to just the return. Commented May 21 at 20:43
  • 2
    Pretty sure it will reduce it to just the return with -O3 (assuming gcc and derivatives)
    – Eugene Sh.
    Commented May 21 at 20:46
  • 2
    There's an approach to getting the compiler to show real code for some sample coding approach. Provide inputs as parameters, and provide outputs that are actually used. Hide functions you don't want in-lined by using (forward or external) declaration instead of definition for them. With this approach the compiler focuses just on the code snippet of interest.
    – Erik Eidt
    Commented May 21 at 20:47
  • 2
    Tje compiler has performed constant propagation and has discarded the unreachable cases. While it can generate a jump table, you'll need to write code for which a jump table is actually the right choice to get the compiler to generate such. Don't underestimate how smart compilers are. If you as an assembly programmer would not use a jump table for some code, it's likely the compiler won't either.
    – fuz
    Commented May 21 at 21:16

1 Answer 1

1

A C compiler is free to transform the program under the so-called as-if rule. This means that with sufficient optimization, the OP program can be reduced to just returning 0.

To avoid such optimizations, a common approach is to make variables volatile. With this the complete translation of the OP code can be seen:

main:
        sub     sp, sp, #32
        mov     w0, 1
        str     w0, [sp, 28]
        ldr     w0, [sp, 28]
        cmp     w0, 2
        beq     .L2
        cmp     w0, 3
        beq     .L3
        cmp     w0, 1
        bne     .L4
        str     w0, [sp, 12]
.L5:
        mov     w0, 0
        add     sp, sp, 32
        ret
.L3:
        str     w0, [sp, 20]
        b       .L5
.L2:
        str     w0, [sp, 16]
        b       .L5
.L4:
        mov     w0, 4
        str     w0, [sp, 24]
        b       .L5

As seen, in this case, the compiler still does a series of comparisons, presumably because that is more efficient than a lookup table.

Possibly, if there a more cases, it will use a lookup table, but in any case a modern compiler will go far to make the most efficient code so we can focus on writing the most readable code.

3
  • And now that you have update to ARM64, the statement the compiler still does a series of comparisons, presumably because that is more efficient than a lookup table. is no longer true. .L2 is case 2:, etc. It used the case value to set the constant as they are identical (from SSA). Commented May 22 at 13:29
  • @artlessnoise The code uses a series of comparisons to jump to the code for each case so I do not see how the statement is wrong.
    – nielsen
    Commented May 22 at 14:22
  • The prior code did not even have 'case' blocks (no labels and the entire code as 'if' statements). Yes, it could use jump tables instead of comparisons. But the table size typically needs to be larger and contiguous for jump tables. It is pretty typical for the dispatch to be 'if' statements. The ARM32 code you had did not even implement 'case blocks', but did the statements with the dispatch (due to ARM32 conditional execution). The difference is context sensitive. I assume your sentence referred to the entire switch/case structure and not JUST dispatch. Commented May 22 at 14:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.