1

Compiling the following code with MSVC

#include <mmintrin.h>
#include <utility>

static auto bit_width(unsigned long x) {
    unsigned long i;
    _BitScanReverse(&i, x);
    ++i;
    if (x == 0) { i = 0; }
    return i;
}

auto foo(unsigned long x) {
    __assume(x > 0);
    return bit_width(x);
}

auto bar(unsigned long x) {
    if (x > 0) {
        return bit_width(x);
    } else {
        std::unreachable();  // implemented as __assume(0) in MSVC
    }
}

generates the following, where foo got optimized and bar didn't (compiling to the same asm as the general-case function that checks for zero).

bit_width(unsigned long)
        bsr     eax, ecx
        xor     edx, edx
        inc     eax
        test    ecx, ecx
        cmovne  edx, eax
        mov     eax, edx
        ret     0

foo(unsigned long) PROC
        bsr     eax, ecx
        inc     eax
        ret     0

bar(unsigned long) PROC
        bsr     eax, ecx
        xor     edx, edx
        inc     eax
        test    ecx, ecx
        cmovne  edx, eax
        mov     eax, edx
        ret     0

MSVC's documentation suggests that __assume(0) as an "unreachable" assertion should help with optimization, but I can't actually make it do this.

Is it possible to use std::unexpected() to optimize code such as the above on MSVC? If so, how?


__assume(x>0) works but isn't portable; Clang has an equivalent __builtin_assume. These are different from GNU C __builtin_expect(x>0, 1) which would only hint the compiler that's usually the case, like [[likely]], not a promise which can affect correctness.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
user541686
  • 205,094
  • 128
  • 528
  • 886
  • Ahh, I see. Yeah I got stuck on comparing `__assume()` to `std::unreachable()` and missed the difference in args. I edited to help future readers avoid the same error when skimming, by explicitly saying that MSVC `__assume(0)` *is* `std::unreachable` (a fact I didn't know and wasn't expecting when trying to understand the question yesterday), and changing the code comment so it can't be misread as saying that the whole if/else/unreachable structure is equivalent to using `__assume(x>0)`. – Peter Cordes Aug 19 '23 at 23:42
  • It does work in some cases in current MSVC, e.g. https://godbolt.org/z/xan93v4a1 is the example from their docs, fixed up to be less weird (not `main` so no implicit return 0) and to use the portable C++23 `std::unreachable()` so we can compare with clang. So the warning about using `__assume` wrong leading to wrong code isn't just applicable to old compiler versions, it does make a difference still. Just not here, because MSVC's optimizer isn't smart enough to derive value-range information from this `if` and apply it to a different `if`, at least not in this case. – Peter Cordes Aug 19 '23 at 23:51
  • (Removed my incorrect first comment. https://godbolt.org/z/Er4rG5s15 compares the same code with GCC and clang, which do optimize. And in case anyone's wondering, `bsr` leaves its destination register unmodified in the input=0 case, but the `_BitScanReverse` treats the `int*` operand as write-only and doesn't init the destination reg. So that's not an option. If we had BMI1, `32-lzcnt(x)` would give the 1+BSR result we want, including for `lzcnt(0) = 32` – Peter Cordes Aug 20 '23 at 00:38

0 Answers0