1

Why is the result of

uint32_t s = 64;
uint64_t val = 1ull << s;

and

uint64_t s = 64;
uint64_t val = 1ull << s;

1? But

uint64_t val = 1ull << 0x40;

gets optimized to 0? I really don't understand why it equals 1. It does no matter whether I use my VC++ or g++ compiler.

And how can I ensure that 1ull << s equals 0 when s equals 64, what's in my opinion is the correct result? I also need the imo. correct result in my program.

JulianW
  • 897
  • 9
  • 23
  • Which version of Visual C++ are you using? I get 0 in all the cases with VC++ 2017. Compiling in `x64 Release` mode. – sgarizvi Mar 27 '19 at 11:47
  • @sgarizvi, I think your compiler is optimizing the shift away. If you check the same thing in Debug, I think you'll be able to reproduce OP's results. – yzt Mar 27 '19 at 11:50
  • @yzt... Indeed, you are right. – sgarizvi Mar 27 '19 at 11:51

1 Answers1

2

This is because on x64, the instruction SHL (when operating on a 64-bit source/destination operand) only uses the bottom 6 bits of the shift amount. In effect, you are shifting by 0 bits.

From the "Intel 64 and IA-32 Architecture Software Developer's Manual" (can be downloaded from Intel in PDF form, which is hard to link into,) under the entry for "SAL/SAR/SHL/SHR - Shift" instructions:

The count is masked to 5 bits (or 6 bits if in 64-bit mode and REX.W is used). The count range is limited to 0 to 31 (or 63 if 64-bit mode and REX.W is used).

As commented below, it is also an "Undefined Behavior" in the C++ language to shift an integer by more bits than its size. (Thanks to @sgarizvi for the reference.) The C++ standard, under Section 8.5.7 (Shift Operators) states that:

The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand...

That's why the compiler is producing code that gives different results under different conditions (constant or variable shift count, optimized or not, etc.)

About how to "fix" it, I have no clever tricks. You can do something like this:

template <typename IntT>
IntT ShiftLeft (IntT x, unsigned count) {
    // Optional check; depends on how much you want to embrace C++!
    static_assert(std::is_integral_v<IntT>, "This shift only works for integral types.");

    if (count < sizeof(IntT) * 8)
        return x << count;
    else
        return 0;
}

This code would work for signed and unsigned integer types (left-shift is the same for signed and unsigned values.)

yzt
  • 8,873
  • 1
  • 35
  • 44
  • More generally: This is because the behaviour of shifting by the width of the integer (or more) is undefined in C++. – eerorika Mar 27 '19 at 11:51
  • Yes makes sense, thank you for that. – JulianW Mar 27 '19 at 12:01
  • @yzt.. In the [C++ Standard draft document](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf), it is mentioned under *Section 8.5.7 (Shift Operators)* that ..... `The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand` – sgarizvi Mar 27 '19 at 12:17
  • @sgarizvi Also thanks @yzt I'm only considering s <= 64, I don't have to care about s > 64, so I'm able to stay with the solution: `1ull << (s - 1) << 1`. – JulianW Mar 27 '19 at 12:31