Skip to content

Commit b1683c8

Browse files
committed
Remove as much checked math as possible from buffer pointers.
This patch removes as much checked math as I was able to find from the unsafe buffer pointers types. In particular, I removed two kinds of checked math: 1. Checked math that was provably unable to trap due to prior operations done in the same function, or in a function that must have been called before the current one. For example, in index(_:offsetBy:limitedBy:), when we do the final index addition we have already validated that it must be in-bounds. Therefore, it cannot overflow. 2. Checked math that can only fail if the user is misusing the indices. As previously discussed with Karoy and Andrew, the unsafe raw buffer types are not obligated to crash if you misuse their indices, they are free to invoke undefined behaviour. In these cases, I added defensive overflow checks in debug mode. The result of this change should be reductions in code size in almost all usage sites of the raw buffer types.
1 parent b0bade6 commit b1683c8

File tree

3 files changed

+52
-13
lines changed

3 files changed

+52
-13
lines changed

stdlib/public/core/UnsafeBufferPointer.swift.gyb

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle
143143
// optimizer is not capable of creating partial specializations yet.
144144
// NOTE: Range checks are not performed here, because it is done later by
145145
// the subscript function.
146-
return i + 1
146+
// NOTE: Unchecked math because unsafe pointers are not required to
147+
// behave sensibly if you misuse their indices. We validate in
148+
// debug mode.
149+
let result = i.addingReportingOverflow(1)
150+
_debugPrecondition(!result.overflow)
151+
return result.partialValue
147152
}
148153

149154
@inlinable // unsafe-performance
@@ -153,7 +158,12 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle
153158
// optimizer is not capable of creating partial specializations yet.
154159
// NOTE: Range checks are not performed here, because it is done later by
155160
// the subscript function.
156-
i += 1
161+
// NOTE: Unchecked math because unsafe pointers are not required to
162+
// behave sensibly if you misuse their indices. We validate in
163+
// debug mode.
164+
let result = i.addingReportingOverflow(1)
165+
_debugPrecondition(!result.overflow)
166+
i = result.partialValue
157167
}
158168

159169
@inlinable // unsafe-performance
@@ -163,7 +173,12 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle
163173
// optimizer is not capable of creating partial specializations yet.
164174
// NOTE: Range checks are not performed here, because it is done later by
165175
// the subscript function.
166-
return i - 1
176+
// NOTE: Unchecked math because unsafe pointers are not required to
177+
// behave sensibly if you misuse their indices. We validate in
178+
// debug mode.
179+
let result = i.subtractingReportingOverflow(1)
180+
_debugPrecondition(!result.overflow)
181+
return result.partialValue
167182
}
168183

169184
@inlinable // unsafe-performance
@@ -173,7 +188,12 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle
173188
// optimizer is not capable of creating partial specializations yet.
174189
// NOTE: Range checks are not performed here, because it is done later by
175190
// the subscript function.
176-
i -= 1
191+
// NOTE: Unchecked math because unsafe pointers are not required to
192+
// behave sensibly if you misuse their indices. We validate in
193+
// debug mode.
194+
let result = i.subtractingReportingOverflow(1)
195+
_debugPrecondition(!result.overflow)
196+
i = result.partialValue
177197
}
178198

179199
@inlinable // unsafe-performance
@@ -183,7 +203,12 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle
183203
// optimizer is not capable of creating partial specializations yet.
184204
// NOTE: Range checks are not performed here, because it is done later by
185205
// the subscript function.
186-
return i + n
206+
// NOTE: Unchecked math because unsafe pointers are not required to
207+
// behave sensibly if you misuse their indices. We validate in
208+
// debug mode.
209+
let result = i.addingReportingOverflow(n)
210+
_debugPrecondition(!result.overflow)
211+
return result.partialValue
187212
}
188213

189214
@inlinable // unsafe-performance
@@ -193,11 +218,19 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle
193218
// optimizer is not capable of creating partial specializations yet.
194219
// NOTE: Range checks are not performed here, because it is done later by
195220
// the subscript function.
196-
let l = limit - i
221+
// NOTE: Unchecked math because unsafe pointers are not required to
222+
// behave sensibly if you misuse their indices. We validate in
223+
// debug mode.
224+
let maxOffset = limit.subtractingReportingOverflow(i)
225+
_debugPrecondition(!maxOffset.overflow)
226+
let l = maxOffset.partialValue
227+
197228
if n > 0 ? l >= 0 && l < n : l <= 0 && n < l {
198229
return nil
199230
}
200-
return i + n
231+
232+
// No need to check here, for the same index misuse reasons.
233+
return i &+ n
201234
}
202235

203236
@inlinable // unsafe-performance
@@ -233,7 +266,8 @@ extension Unsafe${Mutable}BufferPointer: ${Mutable}Collection, RandomAccessColle
233266

234267
@inlinable // unsafe-performance
235268
public var indices: Indices {
236-
return startIndex..<endIndex
269+
// Not checked because init forbids negative count.
270+
return Indices(uncheckedBounds: (startIndex, endIndex))
237271
}
238272

239273
/// Accesses the element at the specified position.

stdlib/public/core/UnsafeRawBufferPointer.swift.gyb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ extension Unsafe${Mutable}RawBufferPointer: ${Mutable}Collection {
172172

173173
@inlinable
174174
public var indices: Indices {
175-
return startIndex..<endIndex
175+
// Not checked because init forbids negative count.
176+
return Indices(uncheckedBounds: (startIndex, endIndex))
176177
}
177178

178179
/// Accesses the byte at the given offset in the memory region as a `UInt8`
@@ -253,7 +254,10 @@ extension Unsafe${Mutable}RawBufferPointer: ${Mutable}Collection {
253254
@inlinable
254255
public var count: Int {
255256
if let pos = _position {
256-
return _assumeNonNegative(_end! - pos)
257+
// Unsafely unwrapped because init forbids end being nil if _position
258+
// isn't.
259+
_internalInvariant(_end != nil)
260+
return _assumeNonNegative(_end._unsafelyUnwrappedUnchecked - pos)
257261
}
258262
return 0
259263
}
@@ -595,7 +599,7 @@ extension Unsafe${Mutable}RawBufferPointer {
595599
return UnsafeMutableBufferPointer<T>(start: nil, count: 0)
596600
}
597601

598-
let count = (_end! - base) / MemoryLayout<T>.stride
602+
let count = (_end._unsafelyUnwrappedUnchecked - base) / MemoryLayout<T>.stride
599603
let typed = base.initializeMemory(
600604
as: type, repeating: repeatedValue, count: count)
601605
return UnsafeMutableBufferPointer<T>(start: typed, count: count)
@@ -644,9 +648,10 @@ extension Unsafe${Mutable}RawBufferPointer {
644648
return (it, UnsafeMutableBufferPointer(start: nil, count: 0))
645649
}
646650

651+
_internalInvariant(_end != nil)
647652
for p in stride(from: base,
648653
// only advance to as far as the last element that will fit
649-
to: base + count - elementStride + 1,
654+
to: _end._unsafelyUnwrappedUnchecked - elementStride + 1,
650655
by: elementStride
651656
) {
652657
// underflow is permitted -- e.g. a sequence into

test/SILOptimizer/unsafebufferpointer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public func testSubscript(_ ubp: UnsafeBufferPointer<Float>) -> Float {
8080
// CHECK: [[CMP:%[0-9]+]] = icmp eq
8181
// CHECK: br i1 [[CMP]], label %[[RET:.*]], label %[[LOOP]]
8282
//
83-
// CHECK: [[RET]]: ; preds = %[[LOOP]], %entry
83+
// CHECK: [[RET]]: ; preds = %[[LOOP]], %{{.*}}
8484
// CHECK: ret i64
8585
public func testSubscript(_ ubp: UnsafeRawBufferPointer) -> Int64 {
8686
var sum: Int64 = 0

0 commit comments

Comments
 (0)