Skip to content

Commit e50f3ba

Browse files
mundaymstephentyrone
authored andcommitted
[stdlib] Fix exact floating point to integer cast overflow detection (#16960)
LLVM specifies that the result of fptosi and fptoui instructions are undefined if the target type cannot represent the value exactly. On IBM Z (s390x) these instructions currently saturate when overflow occurs. This means that the round-trip used to detect overflow succeeds in situations where the conversion is not actually exact. For example, casting Int32.max to a Float32 via a sitofp instruction results in Int32.max + 1. This is inexact. However if we then convert back to an Int32 via a fptosi instruction the result is clamped to Int32.max and so the round trip has resulted in the same value. We therefore cannot rely on round trips alone to verify the exactness of this cast portably. This commit modifies the conversion routines so that they do not rely on undefined behavior and avoid using round trips in general.
1 parent 165c5e8 commit e50f3ba

File tree

2 files changed

+32
-9
lines changed

2 files changed

+32
-9
lines changed

stdlib/public/core/FloatingPointTypes.swift.gyb

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,8 +1578,18 @@ extension ${Self} {
15781578
% for self_ty in all_integer_types(word_bits):
15791579
% That = self_ty.stdlib_name
15801580
% ThatBuiltinName = self_ty.builtin_name
1581-
% srcBits = self_ty.bits
15821581
% sign = 's' if self_ty.is_signed else 'u'
1582+
% srcBits = self_ty.bits
1583+
% maxExponent = (1 << (ExponentBitCount - 1)) - 1
1584+
% maxBitLength = SignificandBitCount + 1 # implicit leading one
1585+
% if self_ty.is_signed:
1586+
% # The magnitude of the minimum value of a signed integer type is always
1587+
% # representable in floating point (assuming a large enough exponent)
1588+
% # because only one bit is set. All other possible magnitudes can be
1589+
% # represented using one bit less than the original source type.
1590+
% maxBitLength += 1
1591+
% end
1592+
15831593
/// Creates the closest representable value to the given integer.
15841594
///
15851595
/// - Parameter value: The integer to represent as a floating-point value.
@@ -1594,20 +1604,28 @@ extension ${Self} {
15941604
/// can't be represented exactly, the result is `nil`.
15951605
///
15961606
/// - Parameter value: The integer to represent as a floating-point value.
1597-
% if srcBits < SignificandBitCount:
1607+
% if srcBits <= maxBitLength and srcBits - 1 <= maxExponent:
15981608
@available(*, message: "Converting ${That} to ${Self} will always succeed.")
15991609
% end
16001610
@inlinable // FIXME(sil-serialize-all)
16011611
@inline(__always)
16021612
public init?(exactly value: ${That}) {
1603-
_value = Builtin.${sign}itofp_${ThatBuiltinName}_FPIEEE${bits}(value._value)
1604-
1605-
% if srcBits >= SignificandBitCount:
1606-
guard let roundTrip = ${That}(exactly: self),
1607-
roundTrip == value else {
1613+
% if srcBits - 1 > maxExponent:
1614+
// Check that the exponent will be in range.
1615+
guard value.magnitude >> ${maxExponent} <= 1 else {
16081616
return nil
16091617
}
16101618
% end
1619+
% if srcBits > maxBitLength:
1620+
// The magnitude of the integer must be representable by the significand.
1621+
// Omit trailing zeros that can be encoded by the exponent.
1622+
let mask: ${That}.Magnitude = ~((1 << (${SignificandBitCount} + 1)) - 1)
1623+
let absolute = value.magnitude
1624+
guard (absolute >> absolute.trailingZeroBitCount) & mask == 0 else {
1625+
return nil
1626+
}
1627+
% end
1628+
_value = Builtin.${sign}itofp_${ThatBuiltinName}_FPIEEE${bits}(value._value)
16111629
}
16121630
% end # all_integer_types
16131631
}

stdlib/public/core/Integers.swift.gyb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3292,10 +3292,15 @@ public struct ${Self}
32923292
/// - Parameter source: A floating-point value to convert to an integer.
32933293
@_transparent
32943294
public init?(exactly source: ${FloatType}) {
3295-
self._value = Builtin.fpto${u}i_FPIEEE${FloatBits}_${BuiltinName}(source._value)
3296-
if ${FloatType}(self) != source {
3295+
guard source > ${str(lower)}.0 && source < ${str(upper)}.0 else {
3296+
// The source is out of bounds (including infinities).
3297+
return nil
3298+
}
3299+
guard source == source.rounded(.towardZero) else {
3300+
// The source is a fraction or NaN.
32973301
return nil
32983302
}
3303+
self._value = Builtin.fpto${u}i_FPIEEE${FloatBits}_${BuiltinName}(source._value)
32993304
}
33003305

33013306
% if FloatType == 'Float80':

0 commit comments

Comments
 (0)