Skip to content

Commit e96e174

Browse files
committed
Adopt typed throws in withUnsafeTemporaryAllocation
The interior wrapping in Result<T, U> is a little unfortunate, but is currently necessary because we end up with extra stack allocations along the error-handling path in a do..catch that aren't there when using untyped throws. We can simplify the implementation if we can eliminate the extraneous stack allocation. Fixes rdar://134973620.
1 parent 94f7c54 commit e96e174

File tree

2 files changed

+108
-67
lines changed

2 files changed

+108
-67
lines changed

stdlib/public/core/TemporaryAllocation.swift

Lines changed: 91 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,6 @@ internal func _isStackAllocationSafe(byteCount: Int, alignment: Int) -> Bool {
119119
///
120120
/// - Returns: Whatever is returned by `body`.
121121
///
122-
/// - Throws: Whatever is thrown by `body`.
123-
///
124122
/// This function encapsulates the various calls to builtins required by
125123
/// `withUnsafeTemporaryAllocation()`.
126124
@_alwaysEmitIntoClient @_transparent
@@ -130,13 +128,13 @@ internal func _withUnsafeTemporaryAllocation<
130128
of type: T.Type,
131129
capacity: Int,
132130
alignment: Int,
133-
_ body: (Builtin.RawPointer) throws -> R
134-
) rethrows -> R {
131+
_ body: (Builtin.RawPointer) -> R
132+
) -> R {
135133
// How many bytes do we need to allocate?
136134
let byteCount = _byteCountForTemporaryAllocation(of: type, capacity: capacity)
137135

138136
guard _isStackAllocationSafe(byteCount: byteCount, alignment: alignment) else {
139-
return try _fallBackToHeapAllocation(byteCount: byteCount, alignment: alignment, body)
137+
return _fallBackToHeapAllocation(byteCount: byteCount, alignment: alignment, body)
140138
}
141139

142140
// This declaration must come BEFORE Builtin.stackAlloc() or
@@ -154,15 +152,9 @@ internal func _withUnsafeTemporaryAllocation<
154152
// The multiple calls to Builtin.stackDealloc() are because defer { } produces
155153
// a child function at the SIL layer and that conflicts with the verifier's
156154
// idea of a stack allocation's lifetime.
157-
do {
158-
result = try body(stackAddress)
159-
Builtin.stackDealloc(stackAddress)
160-
return result
161-
162-
} catch {
163-
Builtin.stackDealloc(stackAddress)
164-
throw error
165-
}
155+
result = body(stackAddress)
156+
Builtin.stackDealloc(stackAddress)
157+
return result
166158
#else
167159
fatalError("unsupported compiler")
168160
#endif
@@ -175,13 +167,13 @@ internal func _withUnprotectedUnsafeTemporaryAllocation<
175167
of type: T.Type,
176168
capacity: Int,
177169
alignment: Int,
178-
_ body: (Builtin.RawPointer) throws -> R
179-
) rethrows -> R {
170+
_ body: (Builtin.RawPointer) -> R
171+
) -> R {
180172
// How many bytes do we need to allocate?
181173
let byteCount = _byteCountForTemporaryAllocation(of: type, capacity: capacity)
182174

183175
guard _isStackAllocationSafe(byteCount: byteCount, alignment: alignment) else {
184-
return try _fallBackToHeapAllocation(byteCount: byteCount, alignment: alignment, body)
176+
return _fallBackToHeapAllocation(byteCount: byteCount, alignment: alignment, body)
185177
}
186178

187179
// This declaration must come BEFORE Builtin.unprotectedStackAlloc() or
@@ -198,23 +190,17 @@ internal func _withUnprotectedUnsafeTemporaryAllocation<
198190
// The multiple calls to Builtin.stackDealloc() are because defer { } produces
199191
// a child function at the SIL layer and that conflicts with the verifier's
200192
// idea of a stack allocation's lifetime.
201-
do {
202-
result = try body(stackAddress)
203-
Builtin.stackDealloc(stackAddress)
204-
return result
205-
206-
} catch {
207-
Builtin.stackDealloc(stackAddress)
208-
throw error
209-
}
193+
result = body(stackAddress)
194+
Builtin.stackDealloc(stackAddress)
195+
return result
210196
}
211197

212198
@_alwaysEmitIntoClient @_transparent
213-
internal func _fallBackToHeapAllocation<R: ~Copyable>(
199+
internal func _fallBackToHeapAllocation<R: ~Copyable, E: Error>(
214200
byteCount: Int,
215201
alignment: Int,
216-
_ body: (Builtin.RawPointer) throws -> R
217-
) rethrows -> R {
202+
_ body: (Builtin.RawPointer) throws(E) -> R
203+
) throws(E) -> R {
218204
let buffer = UnsafeMutableRawPointer.allocate(
219205
byteCount: byteCount,
220206
alignment: alignment
@@ -259,21 +245,30 @@ internal func _fallBackToHeapAllocation<R: ~Copyable>(
259245
/// the buffer) must not escape. It will be deallocated when `body` returns and
260246
/// cannot be used afterward.
261247
@_alwaysEmitIntoClient @_transparent
262-
public func withUnsafeTemporaryAllocation<R: ~Copyable>(
248+
public func withUnsafeTemporaryAllocation<R: ~Copyable, E: Error>(
263249
byteCount: Int,
264250
alignment: Int,
265-
_ body: (UnsafeMutableRawBufferPointer) throws -> R
266-
) rethrows -> R {
267-
return try _withUnsafeTemporaryAllocation(
251+
_ body: (UnsafeMutableRawBufferPointer) throws(E) -> R
252+
) throws(E) -> R {
253+
let result: Result<R, E> = _withUnsafeTemporaryAllocation(
268254
of: Int8.self,
269255
capacity: byteCount,
270256
alignment: alignment
271257
) { pointer in
272-
let buffer = unsafe UnsafeMutableRawBufferPointer(
273-
start: .init(pointer),
274-
count: byteCount
275-
)
276-
return try unsafe body(buffer)
258+
do throws(E) {
259+
let buffer = unsafe UnsafeMutableRawBufferPointer(
260+
start: .init(pointer),
261+
count: byteCount
262+
)
263+
return .success(try unsafe body(buffer))
264+
} catch {
265+
return .failure(error)
266+
}
267+
}
268+
269+
switch consume result {
270+
case .success(let resultValue): return resultValue
271+
case .failure(let error): throw error
277272
}
278273
}
279274

@@ -283,21 +278,30 @@ public func withUnsafeTemporaryAllocation<R: ~Copyable>(
283278
/// This function is similar to `withUnsafeTemporaryAllocation`, except that it
284279
/// doesn't trigger stack protection for the stack allocated memory.
285280
@_alwaysEmitIntoClient @_transparent
286-
public func _withUnprotectedUnsafeTemporaryAllocation<R: ~Copyable>(
281+
public func _withUnprotectedUnsafeTemporaryAllocation<R: ~Copyable, E: Error>(
287282
byteCount: Int,
288283
alignment: Int,
289-
_ body: (UnsafeMutableRawBufferPointer) throws -> R
290-
) rethrows -> R {
291-
return try _withUnprotectedUnsafeTemporaryAllocation(
284+
_ body: (UnsafeMutableRawBufferPointer) throws(E) -> R
285+
) throws(E) -> R {
286+
let result: Result<R, E> = _withUnprotectedUnsafeTemporaryAllocation(
292287
of: Int8.self,
293288
capacity: byteCount,
294289
alignment: alignment
295290
) { pointer in
296-
let buffer = unsafe UnsafeMutableRawBufferPointer(
297-
start: .init(pointer),
298-
count: byteCount
299-
)
300-
return try unsafe body(buffer)
291+
do throws(E) {
292+
let buffer = unsafe UnsafeMutableRawBufferPointer(
293+
start: .init(pointer),
294+
count: byteCount
295+
)
296+
return try unsafe .success(body(buffer))
297+
} catch {
298+
return .failure(error)
299+
}
300+
}
301+
302+
switch consume result {
303+
case .success(let resultValue): return resultValue
304+
case .failure(let error): throw error
301305
}
302306
}
303307

@@ -334,23 +338,33 @@ public func _withUnprotectedUnsafeTemporaryAllocation<R: ~Copyable>(
334338
/// cannot be used afterward.
335339
@_alwaysEmitIntoClient @_transparent
336340
public func withUnsafeTemporaryAllocation<
337-
T: ~Copyable,R: ~Copyable
341+
T: ~Copyable,R: ~Copyable,
342+
E: Error
338343
>(
339344
of type: T.Type,
340345
capacity: Int,
341-
_ body: (UnsafeMutableBufferPointer<T>) throws -> R
342-
) rethrows -> R {
343-
return try _withUnsafeTemporaryAllocation(
346+
_ body: (UnsafeMutableBufferPointer<T>) throws(E) -> R
347+
) throws(E) -> R {
348+
let result: Result<R, E> = _withUnsafeTemporaryAllocation(
344349
of: type,
345350
capacity: capacity,
346351
alignment: MemoryLayout<T>.alignment
347352
) { pointer in
348-
Builtin.bindMemory(pointer, capacity._builtinWordValue, type)
349-
let buffer = unsafe UnsafeMutableBufferPointer<T>(
350-
start: .init(pointer),
351-
count: capacity
352-
)
353-
return try unsafe body(buffer)
353+
do throws(E) {
354+
Builtin.bindMemory(pointer, capacity._builtinWordValue, type)
355+
let buffer = unsafe UnsafeMutableBufferPointer<T>(
356+
start: .init(pointer),
357+
count: capacity
358+
)
359+
return try unsafe .success(body(buffer))
360+
} catch {
361+
return .failure(error)
362+
}
363+
}
364+
365+
switch consume result {
366+
case .success(let resultValue): return resultValue
367+
case .failure(let error): throw error
354368
}
355369
}
356370

@@ -361,22 +375,32 @@ public func withUnsafeTemporaryAllocation<
361375
/// doesn't trigger stack protection for the stack allocated memory.
362376
@_alwaysEmitIntoClient @_transparent
363377
public func _withUnprotectedUnsafeTemporaryAllocation<
364-
T: ~Copyable, R: ~Copyable
378+
T: ~Copyable, R: ~Copyable,
379+
E: Error
365380
>(
366381
of type: T.Type,
367382
capacity: Int,
368-
_ body: (UnsafeMutableBufferPointer<T>) throws -> R
369-
) rethrows -> R {
370-
return try _withUnprotectedUnsafeTemporaryAllocation(
383+
_ body: (UnsafeMutableBufferPointer<T>) throws(E) -> R
384+
) throws(E) -> R {
385+
let result: Result<R, E> = _withUnprotectedUnsafeTemporaryAllocation(
371386
of: type,
372387
capacity: capacity,
373388
alignment: MemoryLayout<T>.alignment
374389
) { pointer in
375-
Builtin.bindMemory(pointer, capacity._builtinWordValue, type)
376-
let buffer = unsafe UnsafeMutableBufferPointer<T>(
377-
start: .init(pointer),
378-
count: capacity
379-
)
380-
return try unsafe body(buffer)
390+
do throws(E) {
391+
Builtin.bindMemory(pointer, capacity._builtinWordValue, type)
392+
let buffer = unsafe UnsafeMutableBufferPointer<T>(
393+
start: .init(pointer),
394+
count: capacity
395+
)
396+
return try unsafe .success(body(buffer))
397+
} catch {
398+
return .failure(error)
399+
}
400+
}
401+
402+
switch consume result {
403+
case .success(let resultValue): return resultValue
404+
case .failure(let error): throw error
381405
}
382406
}

test/stdlib/TemporaryAllocation.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,21 @@ TemporaryAllocationTestSuite.test("typedAllocationIsAligned") {
155155
}
156156
}
157157

158+
// MARK: Typed throws
159+
enum HomeworkError: Error, Equatable {
160+
case dogAtIt
161+
case forgot
162+
}
163+
164+
TemporaryAllocationTestSuite.test("typedAllocationWithThrow") {
165+
do throws(HomeworkError) {
166+
try withUnsafeTemporaryAllocation(of: Int.self, capacity: 1) { (buffer) throws(HomeworkError) -> Void in
167+
throw HomeworkError.forgot
168+
}
169+
fatalError("did not throw!?!")
170+
} catch {
171+
expectEqual(error, .forgot)
172+
}
173+
}
174+
158175
runAllTests()

0 commit comments

Comments
 (0)