Skip to content
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

Open
moiseev mannequin opened this issue Jan 12, 2018 · 8 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. standard library Area: Standard library umbrella

Comments

@moiseev
Copy link
Mannequin

moiseev mannequin commented Jan 12, 2018

Previous ID SR-6749
Radar None
Original Reporter @moiseev
Type Bug
Additional Detail from JIRA
Votes 1
Component/s Standard Library
Labels Bug
Assignee @natecook1000
Priority Medium

md5: 273170fd9a64aa342c5478509c78bfeb

relates to:

  • SR-743 When Developing a UnsignedIntegerType Larger Than 64 Bits, 64+ Left Shifts Won't Compile with Optimizations

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 where bitWidth.nonZeroBitCount == 1, otherwise the shift amount should be obtained by shift % target.bitWidth.
In case this branching is added, we need to make sure that it can be eliminated by the compiler for builtin types.

@moiseev
Copy link
Mannequin Author

moiseev mannequin commented Jan 12, 2018

Thanks @xwu for pointing at the issue!

@xwu
Copy link
Collaborator

xwu commented Feb 8, 2018

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.

@swift-ci
Copy link
Collaborator

swift-ci commented Mar 2, 2018

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.

@natecook1000
Copy link
Member

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:

  • unchecked shift (bad, since it's not really unchecked and there's no unsafety)

  • bitwidth-bound shift (descriptive, but not exactly a beautiful turn of phrase)

  • limited shift (nondescriptive, but not evocative of the wrong thing like wrapping or maybe masking)

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).

@belkadan
Copy link
Contributor

belkadan commented Mar 2, 2018

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.

@xwu
Copy link
Collaborator

xwu commented Mar 3, 2018

@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).

@natecook1000
Copy link
Member

@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 << operator is acting like you describe—shifting by the requested amount in a hypothetical "as large as necessary" type, then masking back to the value's bit width. The &<< operator masks the shift amount first, so shifting by 8 is equivalent to shifting by 0, and shifting by 9 is equivalent to shifting by 1.

@belkadan
Copy link
Contributor

belkadan commented Apr 6, 2018

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.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. standard library Area: Standard library umbrella
Projects
None yet
Development

No branches or pull requests

4 participants