Skip to content

Add default implementations for FixedWidthInteger.dividingFullWidth #70823

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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 50 additions & 21 deletions stdlib/public/core/IntegerTypes.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -1569,12 +1569,17 @@ ${assignmentOperatorComment(x.operator, True)}
#endif
% end

% if bits == 64:
#if _pointerBitWidth(_64) || arch(arm64_32)
// On 32b architectures we fall back on the generic implementation,
// because LLVM doesn't know how to codegen the 128b divide we use.
% end
/// Returns a tuple containing the quotient and remainder of dividing the
/// given value by this value.
///
/// The resulting quotient must be representable within the bounds of the
/// type. If the quotient of dividing `dividend` by this value is too large
/// to represent in the type, a runtime error may occur.
/// to represent in the type, a runtime error will occur.
///
/// - Parameter dividend: A tuple containing the high and low parts of a
/// double-width integer. The `high` component of the value carries the
Expand All @@ -1585,34 +1590,58 @@ ${assignmentOperatorComment(x.operator, True)}
public func dividingFullWidth(
_ dividend: (high: ${Self}, low: ${Self}.Magnitude)
) -> (quotient: ${Self}, remainder: ${Self}) {
// FIXME(integers): tests
% # 128-bit types are not provided by the 32-bit LLVM
% if word_bits == 32 and bits == 64:
% # FIXME(integers): uncomment the above after using the right conditional
% # compilation block to exclude 64-bit Windows, which does not support
% # 128-bit operations
fatalError("Operation is not supported")
% else:
// FIXME(integers): handle division by zero and overflows
_precondition(self != 0, "Division by zero")
% if not signed:
_precondition(self > dividend.high, "Quotient is not representable")
% end
% if dbits <= 64:
% DoubleType = ('Int' if signed else 'UInt') + str(dbits)
let a = ${DoubleType}(dividend.high) &<< ${bits} |
${DoubleType}(dividend.low)
let b = ${DoubleType}(self)
let (q, r) = a.quotientAndRemainder(dividingBy: b)
% if signed:
// remainder is guaranteed representable, but the quotient might not be
// so we need to use a conversion that will check for us:
guard let quotient = ${Self}(exactly: q) else {
_preconditionFailure("Quotient is not representable")
}
return (quotient, ${Self}(truncatingIfNeeded: r))
% else:
// quotient and remainder are both guaranteed to be representable by
// the precondition we enforced above and the definition of division.
return (${Self}(truncatingIfNeeded: q), ${Self}(truncatingIfNeeded: r))
% end
% else:
let lhsHigh = Builtin.${z}ext_Int${bits}_Int${dbits}(dividend.high._value)
let shift = Builtin.zextOrBitCast_Int8_Int${dbits}(UInt8(${bits})._value)
let lhsHighShifted = Builtin.shl_Int${dbits}(lhsHigh, shift)
let lhsLow = Builtin.zext_Int${bits}_Int${dbits}(dividend.low._value)
let lhs_ = Builtin.or_Int${dbits}(lhsHighShifted, lhsLow)
let rhs_ = Builtin.${z}ext_Int${bits}_Int${dbits}(self._value)

let quotient_ = Builtin.${u}div_Int${dbits}(lhs_, rhs_)
let remainder_ = Builtin.${u}rem_Int${dbits}(lhs_, rhs_)
let a = Builtin.or_Int${dbits}(lhsHighShifted, lhsLow)
let b = Builtin.${z}ext_Int${bits}_Int${dbits}(self._value)
let q = Builtin.${u}div_Int${dbits}(a, b)
let r = Builtin.${u}rem_Int${dbits}(a, b)

let quotient = ${Self}(
Builtin.truncOrBitCast_Int${dbits}_Int${bits}(quotient_))
let remainder = ${Self}(
Builtin.truncOrBitCast_Int${dbits}_Int${bits}(remainder_))
% if signed:
// remainder is guaranteed representable, but the quotient might not be
// so we need to check that the conversion succeeds:
let (quotient, overflow) =
Builtin.s_to_s_checked_trunc_Int${dbits}_Int${bits}(q)
_precondition(!Bool(overflow), "Quotient is not representable")
% else:
// quotient and remainder are both guaranteed to be representable by
// the precondition we enforced above and the definition of division.
let quotient = Builtin.truncOrBitCast_Int${dbits}_Int${bits}(q)
% end
let remainder = Builtin.truncOrBitCast_Int${dbits}_Int${bits}(r)

return (quotient: quotient, remainder: remainder)
% end
return (quotient: Self(quotient), remainder: Self(remainder))
% end
}
% if bits == 64:
#endif
% end

/// A representation of this integer with the byte order swapped.
@_transparent
Expand Down
162 changes: 161 additions & 1 deletion stdlib/public/core/Integers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3383,6 +3383,17 @@ extension FixedWidthInteger {
//===--- UnsignedInteger --------------------------------------------------===//
//===----------------------------------------------------------------------===//

// Implementor's note: UnsignedInteger should have required Magnitude == Self,
// because it can necessarily represent the magnitude of every value and every
// recursive generic constraint should terminate unless there is a good
// semantic reason for it not to do so.
//
// However, we cannot easily add this constraint because it changes the
// mangling of generics constrained on <T: FixedWidthInteger & UnsignedInteger>
// to be <T: FixedWidthInteger where T.Magnitude == T>. As a practical matter,
// every unsigned type will satisfy this constraint, so converting between
// Magnitude and Self in generic code is acceptable.

/// An integer type that can represent only nonnegative values.
public protocol UnsignedInteger: BinaryInteger { }

Expand Down Expand Up @@ -3491,9 +3502,124 @@ extension UnsignedInteger where Self: FixedWidthInteger {
/// For unsigned integer types, this value is always `0`.
@_transparent
public static var min: Self { return 0 }

@_alwaysEmitIntoClient
public func dividingFullWidth(
_ dividend: (high: Self, low: Magnitude)
) -> (quotient: Self, remainder: Self) {
// Validate preconditions to guarantee that the quotient is representable.
precondition(self != .zero, "Division by zero")
precondition(dividend.high < self,
"Dividend.high must be smaller than divisor")
// UnsignedInteger should have a Magnitude = Self constraint, but does not,
// so we have to do this conversion (we can't easily add the constraint
// because it changes how generic signatures constrained to
// <FixedWidth & Unsigned> are minimized, which changes the mangling).
// In practice, "every" UnsignedInteger type will satisfy this, and if one
// somehow manages not to in a way that would break this conversion then
// a default implementation of this method never could have worked anyway.
let low = Self(dividend.low)

// The basic algorithm is taken from Knuth (TAoCP, Vol 2, §4.3.1), using
// words that are half the size of Self (so the dividend has four words
// and the divisor has two). The fact that the denominator has exactly
// two words allows for a slight simplification vs. Knuth's Algorithm D,
// in that our computed quotient digit is always exactly right, while
// in the more general case it can be one too large, requiring a subsequent
// borrow.
//
// Knuth's algorithm (and any long division, really), requires that the
// divisor (self) be normalized (meaning that the high-order bit is set).
// We begin by counting the leading zeros so we know how many bits we
// have to shift to normalize.
let lz = leadingZeroBitCount

// If the divisor is actually a power of two, division is just a shift,
// which we can handle much more efficiently. So we do a check for that
// case and early-out if possible.
if (self &- 1) & self == .zero {
let shift = Self.bitWidth - 1 - lz
let q = low &>> shift | dividend.high &<< -shift
let r = low & (self &- 1)
return (q, r)
}

// Shift the divisor left by lz bits to normalize it. We shift the
// dividend left by the same amount so that we get the quotient is
// preserved (we will have to shift right to recover the remainder).
// Note that the right shift `low >> (Self.bitWidth - lz)` is
// deliberately a non-masking shift because lz might be zero.
let v = self &<< lz
let uh = dividend.high &<< lz | low >> (Self.bitWidth - lz)
let ul = low &<< lz

// Now we have a normalized dividend (uh:ul) and divisor (v). Split
// v into half-words (vh:vl) so that we can use the "normal" division
// 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.

let vh = v &>> n_2
let vl = v & mask

// For the (fairly-common) special case where vl is zero, we can simplify
// the arithmetic quite a bit:
if vl == .zero {
let qh = uh / vh
let residual = (uh &- qh &* vh) &<< n_2 | ul &>> n_2
let ql = residual / vh

return (
// Assemble quotient from half-word digits
quotient: qh &<< n_2 | ql,
// Compute remainder (we can re-use the residual to make this simpler).
remainder: ((residual &- ql &* vh) &<< n_2 | ul & mask) &>> lz
)
}

// Helper function: performs a (1½ word)/word division to produce a
// half quotient word q. We'll need to use this twice to generate the
// full quotient.
//
// high is the high word of the quotient for this sub-division.
// low is the low half-word of the quotient for this sub-division (the
// high half of low must be zero).
//
// returns the quotient half-word digit. In a more general setting, this
// computed digit might be one too large, which has to be accounted for
// 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.

// Get q̂ satisfying a = vh q̂ + r̂ with 0 ≤ r̂ < vh:
var (q̂, r̂) = high.quotientAndRemainder(dividingBy: vh)
// Knuth's "Theorem A" establishes that q̂ is an approximation to
// 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

r̂ &+= vh
if r̂ > mask { break }
}
return q̂
}

// Generate the first quotient digit, subtract off its product with the
// divisor to generate the residual, then compute the second quotient
// digit from that.
let qh = generateHalfDigit(high: uh, low: ul &>> n_2)
let residual = (uh &<< n_2 | ul &>> n_2) &- (qh &* v)
let ql = generateHalfDigit(high: residual, low: ul & mask)

return (
// Assemble quotient from half-word digits
quotient: qh &<< n_2 | ql,
// Compute remainder (we can re-use the residual to make this simpler).
remainder: ((residual &<< n_2 | ul & mask) &- (ql &* v)) &>> lz
)
}
}


//===----------------------------------------------------------------------===//
//===--- SignedInteger ----------------------------------------------------===//
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -3622,6 +3748,40 @@ extension SignedInteger where Self: FixedWidthInteger {
// Having handled those special cases, this is safe.
return self % other == 0
}

@_alwaysEmitIntoClient
public func dividingFullWidth(
_ dividend: (high: Self, low: Magnitude)
) -> (quotient: Self, remainder: Self) {
// Get magnitude of dividend:
var magnitudeHigh = Magnitude(truncatingIfNeeded: dividend.high)
var magnitudeLow = dividend.low
if dividend.high < .zero {
let carry: Bool
(magnitudeLow, carry) = (~magnitudeLow).addingReportingOverflow(1)
magnitudeHigh = ~magnitudeHigh &+ (carry ? 1 : 0)
}
// Do division on magnitudes (using unsigned implementation):
let (unsignedQuotient, unsignedRemainder) = magnitude.dividingFullWidth(
(high: magnitudeHigh, low: magnitudeLow)
)
// Fixup sign: quotient is negative if dividend and divisor disagree.
// We will also trap here if the quotient does not fit in Self.
let quotient: Self
if self ^ dividend.high < .zero {
// It is possible that the quotient is representable but its magnitude
// is not representable as Self (if quotient is Self.min), so we have
// to handle that case carefully here.
precondition(unsignedQuotient <= Self.min.magnitude,
"Quotient is not representable.")
quotient = Self(truncatingIfNeeded: 0 &- unsignedQuotient)
} else {
quotient = Self(unsignedQuotient)
}
var remainder = Self(unsignedRemainder)
if dividend.high < .zero { remainder = 0 &- remainder }
return (quotient, remainder)
}
}

/// Returns the given integer as the equivalent value in a different integer
Expand Down
Loading