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

Add default implementations for FixedWidthInteger.dividingFullWidth #70823

Merged

Conversation

stephentyrone
Copy link
Contributor

@stephentyrone stephentyrone commented Jan 10, 2024

These are provided for FixedWidthInteger & UnsignedInteger (the base implementation, following Knuth's Algorithm D) and SignedInteger (converting to magnitudes and calling the former). Previously no default implementations were available, requiring every type to implement these operations.

These defaults will not be optimal for large fixed-width integers, so types vending Int512 or similar integers should still provide their own implementations, but they are unconditionally available as a fallback, which simplifies the process of writing such types, and work well enough as a fallback for modest fixed-width integer types like Int64 or 32b or smaller platforms or Int128 on 64b platforms.

Additionally rework the concrete implementations to guarantee that we always trap when the quotient is not representable, and to improve performance for 64b integers on arm64_32

Fixes #54509.

These are provided for FixedWidthInteger & UnsignedInteger (the base implementation, following Knuth's Algorithm D) and SignedInteger (converting to magnitudes and calling the former). Previously no default implementations were available, requiring every type to implement these operations. These defaults will not be optimal for large fixed-width integers, so types vending Int512 or similar types should still provide their own implementations, but they are unconditionally available as a fallback, which simplifies the process of writing such types, and work well enough as a fallback for modest fixed-width integer types like Int64 or 32b or smaller platforms or Int128 on 64b platforms.
@stephentyrone stephentyrone requested a review from a team as a code owner January 10, 2024 20:47
@stephentyrone
Copy link
Contributor Author

@swift-ci test

@stephentyrone
Copy link
Contributor Author

@swift-ci test

Copy link
Contributor

@phausler phausler left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The algorithm looks spot on to me; I presume we already have tests that cover this?

// the quotient digit q, satisfying q ≤ q̂ ≤ q + 2. We adjust it
// downward as needed until we have the correct q.
while q̂ > mask || q̂ &* vl > (r̂ &<< n_2 | low) {
q̂ &-= 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ the use of circumflex

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

omg

// later on (see Knuth, Algorithm D), but when the divisor is only two
// half-words (as here), that can never happen, because we use the full
// divisor in the check for the while loop.
func generateHalfDigit(high: Self, low: Self) -> Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I presume this as an inner function is 100% specialized and inlined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So long as the outer function is specialized, yes.

// on Self as a word / halfword -> halfword division get one halfword
// digit of the quotient at a time.
let n_2 = Self.bitWidth/2
let mask = Self(1) &<< n_2 &- 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depending on the register spill here; would it be worthwhile to optimize by adding additional variables like something to represent the masked value of ul | mask etc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ought not to matter, so I'll hold off until we see a case where it does.

@stephentyrone
Copy link
Contributor Author

Most normal standard library tests won't exercise it because standard library integer types specialize the operation. I wrote a bunch of standalone tests for it, which I'll see about rolling into the test suite.

Copy link
Contributor

@glessard glessard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice explanations along the way!

- make these trap when quotient is not representable
- use hardware divide for 64b integers on arm64_32
- defer to new default implementation
- add some test coverage
@stephentyrone
Copy link
Contributor Author

@swift-ci test

@stephentyrone
Copy link
Contributor Author

@swift-ci benchmark

@stephentyrone
Copy link
Contributor Author

(We don't expect benchmarks to move much from this, but it's good to make sure.)

Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love it!

@stephentyrone
Copy link
Contributor Author

As expected, benchmarks are just noisy tests, nothing related to division. Importantly, no noticeable code size changes at all.

@stephentyrone stephentyrone merged commit 7a0c4b5 into swiftlang:main Jan 12, 2024
@stephentyrone stephentyrone deleted the default-dividing-full-width branch January 12, 2024 20:25
Catfish-Man pushed a commit to Catfish-Man/swift that referenced this pull request Jan 19, 2024

Unverified

The signing certificate or its chain could not be verified.
…wiftlang#70823)

These are provided for FixedWidthInteger & UnsignedInteger (the base implementation, following Knuth's Algorithm D) and SignedInteger (converting to magnitudes and calling the former). Previously no default implementations were available, requiring every type to implement these operations.

These defaults will not be optimal for large fixed-width integers, so types vending Int512 or similar integers should still provide their own implementations, but they are unconditionally available as a fallback, which simplifies the process of writing such types, and work well enough as a fallback for modest fixed-width integer types like Int64 or 32b or smaller platforms or Int128 on 64b platforms.

Additionally rework the concrete implementations to guarantee that we always trap when the quotient is not representable, and to improve performance for 64b integers on arm64_32, and added some new test coverage for these operations.
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.

[SR-12073] dividingFullWidth is missing overflow checks
5 participants