-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Fix recoverable [U]Int128 division-by-zero #77854
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
Fix recoverable [U]Int128 division-by-zero #77854
Conversation
This patch fixes the division-by-zero case in the following methods: - `Int128/dividedReportingOverflow(by:)` - `Int128/remainderReportingOverflow(dividingBy:)` - `UInt128/dividedReportingOverflow(by:)` - `UInt128/remainderReportingOverflow(dividingBy:)`
Looking closer at the implementation of |
Jumping on the `_slowPath(_:)` bandwagon like all other integer types.
Legend has it that the cordial ping rate is once a week. @stephentyrone – can you take a look at this pull request? Int128 and UInt128 do not perform division by zero like the smaller binary integers. It would be nice if we could fix that. |
@@ -297,8 +297,12 @@ extension Int128 { | |||
public func dividedReportingOverflow( | |||
by other: Self | |||
) -> (partialValue: Self, overflow: Bool) { | |||
_precondition(other != .zero, "Division by zero") | |||
if self == .min && other == -1 { return (.min, true) } | |||
if _slowPath(other == .zero) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was there an observed performance issue that these _slowPath
annotations are intended to address, or were they added speculatively to match the implementations for other Integer types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are speculative but match the implementation of smaller binary integer types. I can remove them if you believe they are unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More of a matter of my own curiosity. I'm ok with leaving them in, though they shouldn't matter much.
if _slowPath(other == .zero) { | ||
return (self, true) | ||
} | ||
if _slowPath(self == .min && other == (-1 as Self)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This case is somewhat interesting because the remainder does not overflow; rather the analogous division does. It's entirely plausible to me that this was originally defined "wrong" on the other integer types, but we're probably stuck with the current behavior. In any case, I think it merits a comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I return the equivalent of (0, false) in my project. So, I considered asking about this specific interaction, but I figured Swift chose C++ semantics ± trapping on undefined behavior. Is there a particular comment you have in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was the specified behavior—I was the one who aligned the actual behavior with the specification in #14219—but for this reason the wording of the corresponding precondition failure (at least at the time) was that "division results in overflow in remainder operation."
The current corresponding documentation for this operation on the other integer types correctly documents the subtlety in this regard: "Returns the remainder after dividing this value by the given value, along with a Boolean value indicating whether overflow occurred during division."
[Edit: Ah, so, refreshing my memory—my involvement in this was to make the *ReportingOverflow
methods return a result instead of (clearly erroneously) trapping at runtime. It's always been the case (since before Swift 3 at least) that, for example, Int.min % -1
traps at runtime and complains about overflow. Hence, since I had to make remainderReportingOverflow
return something for overflow
, the line of thinking must have been essentially what @oscbyspro describes, that the "obvious" result here was to report true
since the corresponding trapping operator does, in fact, trap and complain about overflow even if the notional operation doesn't.]
_precondition(other != .zero, "Division by zero") | ||
if _slowPath(other == .zero) { | ||
return (self, true) | ||
} | ||
// Unsigned divide never overflows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment seems ironic now, doesn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a way, I suppose. I would say that the ironic part is the overflow return label, however, since it represents both proper overflow and another kind of failure (division by zero). Still, I can remove it if you find it unfitting. Generally, I don't mess with comments I did not write.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine to leave it, I think.
I have paraphrased @stephentyrone's and @xwu's review comments to the best of my ability.
@swift-ci test |
@oscbyspro do you have bandwidth to make another PR for the 6.1 branch? If not, I can do so for you. |
I can do it with some patience on your part. I'm currently reading the documentation for how Swift handles release branches: |
Why don't you give it a go, and if you get stuck I'll help. The quick version, though, is check out release/6.1, create a new branch based on that, and cherry-pick your change to that branch. Then create a PR against release/6.1 using that branch. Write a PR message that links back to this PR against main and clearly indicates that it is a cherry-pick. Looking at an example may be helpful: #77699 |
I don't usually |
This patch fixes the division-by-zero case in the following methods:
Int128/dividedReportingOverflow(by:)
Int128/remainderReportingOverflow(dividingBy:)
UInt128/dividedReportingOverflow(by:)
UInt128/remainderReportingOverflow(dividingBy:)
Documentation: /dividedReportingOverflow(by:)
Documentation: /remainderReportingOverflow(dividingBy:)