Skip to content

[stdlib] Add fast paths for generic floating-point-to-integer conversion #33889

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
60 changes: 60 additions & 0 deletions stdlib/public/core/IntegerTypes.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,66 @@ public struct ${Self}

% end

@_alwaysEmitIntoClient
internal init<T: BinaryFloatingPoint>(_unchecked source: T) {
switch (T.exponentBitCount, T.significandBitCount) {
% for (FloatType, FloatBits, FloatExponentBits, FloatSignificandBits) in [
% ('Float16',16,5,10), ('Float',32,8,23), ('Double',64,11,52), ('Float80',80,15,63)]:
% if FloatType == 'Float16':
#if !((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64))
% elif FloatType == 'Float80':
#if !(os(Windows) || os(Android)) && (arch(i386) || arch(x86_64))
% end
case (${FloatExponentBits}, ${FloatSignificandBits}):
% if FloatType == 'Float16':
guard #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) else {
break
}
% end
let source_ = source as? ${FloatType} ?? ${FloatType}(
sign: source.sign,
exponentBitPattern:
${FloatType}.RawExponent(truncatingIfNeeded: source.exponentBitPattern),
significandBitPattern:
${FloatType}.RawSignificand(truncatingIfNeeded: source.significandBitPattern))
self._value = Builtin.fpto${u}i_FPIEEE${FloatBits}_${BuiltinName}(source_._value)
return
% if FloatType == 'Float16' or FloatType == 'Float80':
#endif
% end
% end
default: break
}
self = Self._convert(from: source).value!
}

@_alwaysEmitIntoClient
@inline(__always)
public init<T: BinaryFloatingPoint>(_ source: T) {
_precondition(source.isFinite,
"Floating-point value cannot be converted to ${Self} because it is either infinite or NaN")
_precondition(source > ${str(lower)}.0,
"Floating-point value cannot be converted to ${Self} because the result would be less than ${Self}.min")
_precondition(source < ${str(upper)}.0,
"Floating-point value cannot be converted to ${Self} because the result would be greater than ${Self}.max")
self.init(_unchecked: source)
}

@_alwaysEmitIntoClient
@inline(__always)
public init?<T: BinaryFloatingPoint>(exactly source: T) {
// The value passed as `source` must not be infinite, NaN, or exceed the
// bounds of the integer type; the result of `fptosi` or `fptoui` is
// undefined if it overflows.
guard source > ${str(lower)}.0 && source < ${str(upper)}.0 else {
return nil
}
guard source == source.rounded(.towardZero) else {
return nil
}
self.init(_unchecked: source)
}

@_transparent
public static func == (lhs: ${Self}, rhs: ${Self}) -> Bool {
return Bool(Builtin.cmp_eq_Int${bits}(lhs._value, rhs._value))
Expand Down
2 changes: 1 addition & 1 deletion test/Constraints/generics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ let arr = [BottleLayout]()
let layout = BottleLayout(count:1)
let ix = arr.firstIndex(of:layout) // expected-error {{referencing instance method 'firstIndex(of:)' on 'Collection' requires that 'BottleLayout' conform to 'Equatable'}}

let _: () -> UInt8 = { .init("a" as Unicode.Scalar) } // expected-error {{initializer 'init(_:)' requires that 'Unicode.Scalar' conform to 'BinaryInteger'}}
let _: () -> UInt8 = { .init("a" as Unicode.Scalar) } // expected-error {{initializer 'init(_:)' requires that 'Unicode.Scalar' conform to }}

// https://bugs.swift.org/browse/SR-9068
func compare<C: Collection, Key: Hashable, Value: Equatable>(c: C)
Expand Down
17 changes: 17 additions & 0 deletions test/SILOptimizer/floating_point_conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ func convert<
U(value)
}

func convert<
T: BinaryFloatingPoint, U: BinaryInteger
>(_ value: T, to: U.Type) -> U {
U(value)
}

// Check that the following functions can be optimized to concrete conversions.

// CHECK-LABEL: sil @$s4test0A13DoubleToFloatySfSdF
Expand All @@ -30,6 +36,17 @@ public func testFloatToDouble(_ x: Float) -> Double {
return convert(x, to: Double.self)
}

// CHECK-LABEL: sil @$s4test0A13DoubleToInt64ys0D0VSdF
// CHECK: bb0(%0 : $Double):
// CHECK: [[ARG:%[0-9]+]] = struct_extract %0
// CHECK: [[CNV:%[0-9]+]] = builtin "fptosi_FPIEEE64_Int64"([[ARG]] : $Builtin.FPIEEE64)
// CHECK-NEXT: [[RET:%[0-9]+]] = struct $Int64 ([[CNV]] : $Builtin.Int64)
// CHECK-NEXT: return [[RET]]
// CHECK-NEXT: } // end sil function '$s4test0A13DoubleToInt64ys0D0VSdF'
public func testDoubleToInt64(_ x: Double) -> Int64 {
return convert(x, to: Int64.self)
}

// Check that the following functions can be optimized to no-ops.

// CHECK-LABEL: sil @$s4test0A6DoubleyS2dF
Expand Down