4 ms·
Rust decided for overflows to cause panics in debug mode, but continue normally in release mode. They have special function calls for the few times that you ac
by bvinc 8y ago
Rust decided for overflows to cause panics in debug mode, but continue normally in release mode. They have special function calls for the few times that you actually WANT overflow. This seemed to he the only good option. It allows the programmer to express intent. In the future, if hardware allows for cheap hardware traps in the case of unintentionally overflow, they reserve the right to use them in release mode. C seems pretty hopeless since there is no way to know whether or not the coder intended on overflow or not.
- millstone 8y ago> It allows the programmer to express intent. Can the programmer express the intent that this will not overflow? i.e. is it possible to coerce the Rust compiler to optimize (x*2)/2 to x? "Cheap hardware traps" cannot substitute for such a compile-time optimization. > C seems pretty hopeless since there is no way to know whether or not the coder intended on overflow or not. Well this doesn't matter: if signed arithmetic overflows the code is undefined, regardless of intent. In practice comments can express intent, and unsigned arithmetic can with some finessing produce most codegen. Explicit operators would be a big improvement though.
- lyinsteve 8y agoI don’t know much about rust specifically, but the overflow traps happen in debug (i.e. -O0) builds, and are not emitted when optimizations are enabled. LLVM’s constant folding and canonicalization passes will convert (x * 2) / 2 from %0 = mul i64 %x, i64 2 %1 = div i64 %0, i64 2 to %0 = shl i64 %x, i64 1 %1 = shr i64 %0, i64 1 Which constant folding will reduce to just %x.
- millstone 8y agoWell ok but (x*3)/3 will be a lot worse :)
- Franciscouzo 8y agoNot by a lot, the divide by constant can be optimized to a multiplication, if you're doing it in a loop (why would you care if you do it only once), you can reach a throughput of one multiplication per cycle, so (x*3)/3 would take 2 cycles on average.
- millstone 8y agoThis is a silly line of argument; optimizing (x*3)/3 to x unlocks further CSE, inlining, etc. so the benefit may be arbitrarily large. Rust made the right tradeoff here but it is a tradeoff!
- BeeOnRope 8y agoHow do the two shifts reduce to x? The top bit is zeroed as a result of those shifts.
- CryZe 8y agoThe point is rather that Rust has a bunch of methods on integer for dealing with overflows in a variety of ways, like saturating, wrapping or returning an Option with a None value on overflow (there's also one that returns a tuple with the carry).
- millstone 8y agoThat's all good but it misses my point: the one redeeming quality of C's integer overflow non-semantics is that it unlocks further compiler optimizations, and that's the mode which AFAICT Rust doesn't support. For example, a for loop from 0-through-N (N:i32) will execute N times in C, but N-or-infinity times in Rust. That small difference of infinity controls how the loop condition is tested, whether it can be turned into a simple counter or not.
- jcranmer 8y ago> For example, a for loop from 0-through-N (N:i32) will execute N times in C, but N-or-infinity times in Rust. Eh, not quite true. If N is a 64-bit or an unsigned integer but your loop index is a signed 32-bit integer, then you will have this behavior. In Rust, the idiomatic for loop will have the index variable always be the same size as the condition variable, which prevents the infinity case from happening. In C, if you have a 64-bit condition but a 32-bit unsigned loop index variable, you get the N-or-infinity case as well. In other words, if your language is subtly redesigned to make you jump through hoops to do things like have mismatched integer types in for loops, then the need to rely on undefined behavior to reclaim that performance is lessened.
- saagarjha 8y ago> In the future, if hardware allows for cheap hardware traps in the case of unintentionally overflow, they reserve the right to use them in release mode. This seems awfully close to implementation-defined behavior to me…I'm surprised to see something like this, which allows for (IMHO) unsafe behavior, from Rust.
- cpeterso 8y agoRust specifies that both signed and unsigned integer overflow will wrap as two’s complement. The Rust developers want to discourage overflow because it is a common source of logic bugs, but the behavior is not unsafe. https://huonw.github.io/blog/2016/04/myths-and-legends-about-integer-overflow-in-rust/ https://huonw.github.io/blog/2016/04/myths-and-legends-about...
- saagarjha 8y agoWhat I was trying to say is that it's kinda odd to me that code behaves differently debug and release builds–to me it seems like it could make it easy to write code that works in one configuration but is subtly broken, without realizing the issue, because the programmer was complacent about checking which build they were using. Changing behavior based on whether hardware traps are available makes it even worse. I see how it's not "unsafe" in the usual sense (e.g. memory corruption), but I really would prefer an all-or-nothing overflows just don't or overflows always trap (usually with a way to override this for specific computations, either for performance or for properties of 2s complement arithmetic).