Skip to content

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

Merged

Conversation

oscbyspro
Copy link
Contributor

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

Dividing by zero is not an error when using this method. For a value x, the result of x.dividedReportingOverflow(by: 0) is (x, true).

Documentation: /remainderReportingOverflow(dividingBy:)

Dividing by zero is not an error when using this method. For a value x, the result of x.remainderReportingOverflow(dividingBy: 0) is (x, true).

This patch fixes the division-by-zero case in the following methods:

- `Int128/dividedReportingOverflow(by:)`
- `Int128/remainderReportingOverflow(dividingBy:)`
- `UInt128/dividedReportingOverflow(by:)`
- `UInt128/remainderReportingOverflow(dividingBy:)`
@oscbyspro oscbyspro requested a review from a team as a code owner November 27, 2024 14:24
@oscbyspro
Copy link
Contributor Author

Looking closer at the implementation of Int128 and UInt128, I see that I'll have to add preconditions to /(a:b:) and %(a:b:) following this change. It also looks like Int128's division operator doesn't trap on ordinary overflow either. I'll mark this pull request as a draft until I have updated /(a:b:) and %(a:b:).

@oscbyspro oscbyspro marked this pull request as draft November 28, 2024 13:56
@oscbyspro oscbyspro marked this pull request as ready for review November 29, 2024 10:38
@oscbyspro
Copy link
Contributor Author

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) {
Copy link
Contributor

@stephentyrone stephentyrone Dec 4, 2024

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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)) {
Copy link
Contributor

@stephentyrone stephentyrone Dec 4, 2024

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.

Copy link
Contributor Author

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?

Copy link
Collaborator

@xwu xwu Dec 5, 2024

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.
Copy link
Collaborator

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?

Copy link
Contributor Author

@oscbyspro oscbyspro Dec 5, 2024

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.

Copy link
Contributor

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.
@stephentyrone
Copy link
Contributor

@swift-ci test

@stephentyrone
Copy link
Contributor

@oscbyspro do you have bandwidth to make another PR for the 6.1 branch? If not, I can do so for you.

@oscbyspro
Copy link
Contributor Author

I can do it with some patience on your part.

I'm currently reading the documentation for how Swift handles release branches:

@stephentyrone
Copy link
Contributor

stephentyrone commented Dec 7, 2024

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

@oscbyspro
Copy link
Contributor Author

oscbyspro commented Dec 7, 2024

I don't usually git cherry-pick in my projects so I got a bit confused when the cherry-picked hashes were different. I suppose I thought git would combine the changes of each commit in isolation. In any case, I appreciate your streamlined instructions.

@stephentyrone stephentyrone merged commit 4d43fa9 into swiftlang:main Dec 7, 2024
5 checks passed
@oscbyspro oscbyspro deleted the fixes/128-bit-division-by-zero branch December 8, 2024 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants