Skip to content

Commit 7985896

Browse files
Provide an implementation of init?<T:BinaryInteger>(exactly:T) on each stdlib FP type. (#32632)
Previously these always went through the FloatingPoint-provided default implementation, which is not particularly efficient. Also try removing inlinable from the generic _convert hooks, since we probably never want to actually inline them.
1 parent 1bc9159 commit 7985896

File tree

2 files changed

+52
-4
lines changed

2 files changed

+52
-4
lines changed

stdlib/public/core/FloatingPoint.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,7 +1746,6 @@ extension BinaryFloatingPoint {
17461746
)
17471747
}
17481748

1749-
@inlinable
17501749
public // @testable
17511750
static func _convert<Source: BinaryFloatingPoint>(
17521751
from source: Source
@@ -1935,7 +1934,6 @@ extension BinaryFloatingPoint {
19351934

19361935
extension BinaryFloatingPoint where Self.RawSignificand: FixedWidthInteger {
19371936

1938-
@inlinable
19391937
public // @testable
19401938
static func _convert<Source: BinaryInteger>(
19411939
from source: Source

stdlib/public/core/FloatingPointTypes.swift.gyb

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,7 @@ extension ${Self} {
10341034
_value = Builtin.sitofp_Int${word_bits}_FPIEEE${bits}(v._value)
10351035
}
10361036

1037-
// Fast-path for conversion when the source is representable as a 64-bit int,
1037+
// Fast-path for conversion when the source is representable as int,
10381038
// falling back on the generic _convert operation otherwise.
10391039
@inlinable // FIXME(inline-always)
10401040
@inline(__always)
@@ -1044,14 +1044,64 @@ extension ${Self} {
10441044
let asInt = Int(truncatingIfNeeded: value)
10451045
_value = Builtin.sitofp_Int${word_bits}_FPIEEE${bits}(asInt._value)
10461046
} else {
1047-
let asUInt = Int(truncatingIfNeeded: value)
1047+
let asUInt = UInt(truncatingIfNeeded: value)
10481048
_value = Builtin.uitofp_Int${word_bits}_FPIEEE${bits}(asUInt._value)
10491049
}
10501050
} else {
1051+
// TODO: we can do much better than the generic _convert here for Float
1052+
// and Double by pulling out the high-order 32/64b of the integer, ORing
1053+
// in a sticky bit, and then using the builtin.
10511054
self = ${Self}._convert(from: value).value
10521055
}
10531056
}
10541057

1058+
// Fast-path for conversion when the source is representable as int,
1059+
// falling back on the generic _convert operation otherwise.
1060+
@_alwaysEmitIntoClient @inline(never)
1061+
public init?<Source: BinaryInteger>(exactly value: Source) {
1062+
if value.bitWidth <= ${word_bits} {
1063+
// If the source is small enough to fit in a word, we can use the LLVM
1064+
// conversion intrinsic, then check if we can round-trip back to the
1065+
// the original value; if so, the conversion was exact. We need to be
1066+
// careful, however, to make sure that the first conversion does not
1067+
// round to a value that is out of the defined range of the second
1068+
// converion. E.g. Float(Int.max) rounds to Int.max + 1, and converting
1069+
// that back to Int will trap. For Float, Double, and Float80, this is
1070+
// only an issue for the upper bound (because the lower bound of [U]Int
1071+
// is either zero or a power of two, both of which are exactly
1072+
// representable). For Float16, we also need to check for overflow to
1073+
// -.infinity.
1074+
if Source.isSigned {
1075+
let extended = Int(truncatingIfNeeded: value)
1076+
_value = Builtin.sitofp_Int${word_bits}_FPIEEE${bits}(extended._value)
1077+
% if bits == 16:
1078+
guard self.isFinite && Int(self) == extended else {
1079+
% else:
1080+
guard self < 0x1.0p${word_bits-1} && Int(self) == extended else {
1081+
% end
1082+
return nil
1083+
}
1084+
} else {
1085+
let extended = UInt(truncatingIfNeeded: value)
1086+
_value = Builtin.uitofp_Int${word_bits}_FPIEEE${bits}(extended._value)
1087+
% if bits == 16:
1088+
guard self.isFinite && UInt(self) == extended else {
1089+
% else:
1090+
guard self < 0x1.0p${word_bits} && UInt(self) == extended else {
1091+
% end
1092+
return nil
1093+
}
1094+
}
1095+
} else {
1096+
// TODO: we can do much better than the generic _convert here for Float
1097+
// and Double by pulling out the high-order 32/64b of the integer, ORing
1098+
// in a sticky bit, and then using the builtin.
1099+
let (value_, exact) = Self._convert(from: value)
1100+
guard exact else { return nil }
1101+
self = value_
1102+
}
1103+
}
1104+
10551105
% for src_type in all_floating_point_types():
10561106
% srcBits = src_type.bits
10571107
% That = src_type.stdlib_name

0 commit comments

Comments
 (0)