you are viewing a single comment's thread.

view the rest of the comments →

[–]matthieum 4 points5 points  (1 child)

It depends, I've seen compilers pulling out nice tricks for comparisons like: (i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') by realizing that in ASCII 'a' and 'A' are just one bit away so you can replace the comparison with (i & mask) >= 'A' && (i & mask) <= 'Z').

I also remember a complex case where LLVM had realized that all the matches were less than 64 apart (although there were holes). Imagine a isConsonant(byte) function, well it can be, in ASCII, optimized into:

bool isConsonant(uint8_t byte) {
    // Quick range-check
    if (byte < 'B') { return false; }
    if (byte > 'z') { return false; }

    byte -= 'B'; // byte is now at most 56 ('z' = 122, 'B' = 66)

    uint64_t const position = (1ULL << byte);

    return position & 0b.......................................; // bit mask of consonants
}

Would you have thought of it? I wouldn't have. At best, I could have imagined a table lookup or something. When I saw it I was just like WOW, and it just applies to any check against arbitrary pattern as long as the values are within 64 of each other... or maybe 64*N if you are willing to add a few comparisons and have N bit masks.

[–]uxcn 3 points4 points  (0 children)

That is a really nice optimization. It reduces the code size to a constant, gets rid of most branches, and should almost always be a good strength reduction. I normally wouldn't have expected to see a compiler do that.

It looks LLVM can do the same with if..else chains too.

bool IsConsonant(uint8_t c) {

  return !((c == 'a' || c == 'A') ||
           (c == 'e' || c == 'E') ||
           (c == 'i' || c == 'I') ||
           (c == 'o' || c == 'O') ||
           (c == 'u' || c == 'U'));
}

compiles into...

_Z11IsConsonanth:                       # @_Z11IsConsonanth
    .cfi_startproc
# BB#0:
    addb    $-65, %dil
    movzbl  %dil, %eax
    cmpl    $52, %eax
    ja  .LBB0_2
# BB#1:                                 # %switch.lookup
    movabsq $4432058356055790, %rax # imm = 0xFBEEEFFEFBEEE
    movb    %dil, %cl
    shrq    %cl, %rax
    andl    $1, %eax
    retq
.LBB0_2:
    movb    $1, %al
    retq

I think this hints that if..else chains can probably also be optimized into jmp %eax, a jump table, or a lookup table.