New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[SR-6749] Describing the "masking shift" operations in terms of bit-masks is not always correct #49298
Comments
Thanks @xwu for pointing at the issue! |
In Rust, I believe the same operation is known as a "wrapping shift." I wonder if that's a more apt description. In any case, `DoubleWidth` now implements the intended behavior, so the remaining issue here is how properly to describe it. |
Comment by Daryle Walker (JIRA) I just made a forum post about this subject. I was wondering if I should have made a bug report, but I guess I didn't have to. |
I still don't think "masking shift" is a bad name for this operator, even if the docs could be better, since it describes what's happening in all cases unless someone goes off their rocker and creates a 7-bit integer. (Off their rocker: me.) The goal of the operator, however, is never to have this wrapping behavior happen — it's what you use when you have static knowledge that your shift amount is <= the bitWidth. By masking, you and the compiler can agree that there's no branch needed, so you get slightly faster code without zero branching and zero risk of undefined behavior (which a negative or too large shift would be). The docs are confusing because they give an example I don't think anyone would ever intentionally write—relying on the wrapping behavior to use some out of bounds value for the shift amount. Other possible naming options:
With all due respect to Rust, to me "wrapping" is a terrible description of this behavior, and doesn't at all match up with the rest of their wrapping operations (like addition, etc, for which that adjective makes sense). |
I commented on Daryle's post, but I'll mention it here too: the name refers to masking the result, not the shift amount. That makes sense for any fixed-width integer. Note that there's not necessarily zero branching involved, because even for concrete types not every architecture's low-level shift implementation behaves the way masking-shift is specified. |
@belkadan The documentation doesn't explain the name in that way currently, but I agree that the name is quite elegant when viewed from that standpoint (and more correct than what's described currently). |
@belkadan That isn't quite right—the "smart shift" operators (<< and >>) do the kind of shift you're talking about, while the masking shift operators (&<< and &>>) mask the amount of the shift so that it's less than the type's bit width. let x: UInt8 = 7
(x << 7, x &<< 7) // (128, 128)
(x << 8, x &<< 8) // (0, 7)
(x << 9, x &<< 9) // (0, 14) In this examples, the |
Aargh. I guess that's not the behavior I would have chosen. Anyway, I think Daryle's original comment is correct, then. You wouldn't be able to implement this with a bit mask if the type's bit width is not a power of two. |
Additional Detail from JIRA
md5: 273170fd9a64aa342c5478509c78bfeb
relates to:
Issue Description:
Masking shifts only perform masking when the type's bit width is the power of two, which is not in general the case.
There are a few ways we can approach the problem. The path of least resistance could be : since the operators in question only exist on the `FixedWidthInteger` protocol, we can document that all the types that conform to it should be of width that is power-of-two.
Another possibility is to describe these operations in more abstract terms, and provide a correct implementation for
DoubleWidth<T>
, that would use actual masking only in the case wherebitWidth.nonZeroBitCount == 1
, otherwise the shift amount should be obtained byshift % target.bitWidth
.In case this branching is added, we need to make sure that it can be eliminated by the compiler for builtin types.
The text was updated successfully, but these errors were encountered: