>> operator panics in debug build, works in release build


#1

In a debug build, the >> operator causes a panic if you shift by a number of bits greater than or equal to the bit width of the left-hand operand; in a release build it returns zero. The latter is the behaviour I want; is there an alternative operator or function which does this in both debug and release builds?

To be a bit more concrete, I’d like to have the following code return 0 rather than panicking, regardless of the build type:

fn extract_top_bits(value: u32, nbits: u32) -> u32 {
    value >> (32 - nbits)
}
...
assert!(extract_top_bits(my_val, 0) == 0)

Is there any way to do this?

Edit: in case it matters, I’m using Rust 1.7.0


#2

value.checked_shr(32 - nbits).unwrap_or(0) will do it. The behavior in release builds is actually unspecified/hardware defined I believe.


#3

Thanks! I should have mentioned that I’m very concerned about generating efficient assembly for this function, because I’m planning to use something similar to it inside several very hot loops.

On x86_64, your solution generates machine code with a compare and a conditional move instruction to zero the result when necessary. That should be fairly cheap, but I’d rather avoid it altogether if possible.

In terms of generated code, the best I’ve been able to come up with so far is:

(value >> 1) >> (31 - nbits)

which would avoid the overflow in my particular case because I know it’ll always be shifting by more than 1 bit. It definitely feels like fighting the compiler though!


#4

Have you tried overflowing_shr ?


#5

Thanks for the suggestion! If I write

match val.overflowing_shr(32 - nbits) {
    (_, true) => 0,
    (res, false) => res
}

then the emitted code is almost identical to the checked_shr solution posted above (the difference is a cmovbel instruction instead of a cmovbl, everything else is the same). The result is the same if I use an if instead of the match. I can’t find a way to make the compile avoid the compare and conditional move.


#6

You could use conditional compilation to choose an implementation depending on whether you’re compiling with debug mode or release mode.


#7

Compiler and standard library are trying hard to not let you go in unspecified/hardware defined zone, I think.


#8

The compiler is generating a compare and conditional move because shr doesn’t set the result to zero for overlong shifts - it masks the count off at 5 bits. See page 4-399 of the Instruction Set Reference


#9

So I see! I’d never thought to check that before seeing the replies here, I’d always assumed shr would fill the result with zero bits instead (like ARM’s lsr instruction does). This has actually uncovered a bug in my original c++ version of the code too - so thank you very much to all of you who replied!

So far it looks like (val >> (31 - nbits)) >> 1 will be my best option, but I’ll benchmark all the different solutions and post the results for future reference.

Thanks again to all of you!


#10

Finding little bugs in the C/C++ code you are porting to Rust/D/Ada is rather common.


#11

Just to follow up: I’ve been trying to benchmark the different approaches so I can post the results, but Rust’s optimiser is too smart for me & manages to elide most of the code I’m trying to benchmark. As such, I don’t have anything reliable post. If I figure out a way around that, I’ll post the results when I do. Thanks everyone!


#12

You can use test::black_box on nightly: https://doc.rust-lang.org/book/benchmark-tests.html#gotcha-optimizations


#13

Page 4-339 actually


#14

Thanks, I did see that page, but I’m more comfortable sticking with the stable releases for now - I’m still a bit of a Rust newbie, so I don’t want to risk getting confused by a compiler bug just yet. :slight_smile: I’ll definitely revisit it when the test module lands in the stable branch though.


#15

you can always define your own blackbox with

#[inline(never)]
fn black_box<T>(t: T) -> T { t }