Skip to content

Commit 87d7e72

Browse files
committed
stdlib: add an underscored _withUnprotectedUnsafeTemporaryAllocation
This function is similar to `withUnsafeTemporaryAllocation`, except that it doesn't trigger stack protection for the stack allocated memory.
1 parent 748264c commit 87d7e72

File tree

3 files changed

+152
-10
lines changed

3 files changed

+152
-10
lines changed

stdlib/public/core/TemporaryAllocation.swift

Lines changed: 115 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,7 @@ internal func _withUnsafeTemporaryAllocation<T, R>(
129129
let byteCount = _byteCountForTemporaryAllocation(of: type, capacity: capacity)
130130

131131
guard _isStackAllocationSafe(byteCount: byteCount, alignment: alignment) else {
132-
// Fall back to the heap. This may still be optimizable if escape analysis
133-
// shows that the allocated pointer does not escape.
134-
let buffer = UnsafeMutableRawPointer.allocate(
135-
byteCount: byteCount,
136-
alignment: alignment
137-
)
138-
defer {
139-
buffer.deallocate()
140-
}
141-
return try body(buffer._rawValue)
132+
return try _fallBackToHeapAllocation(byteCount: byteCount, alignment: alignment, body)
142133
}
143134

144135
// This declaration must come BEFORE Builtin.stackAlloc() or
@@ -170,6 +161,63 @@ internal func _withUnsafeTemporaryAllocation<T, R>(
170161
#endif
171162
}
172163

164+
#if $BuiltinUnprotectedStackAlloc
165+
@_alwaysEmitIntoClient @_transparent
166+
internal func _withUnprotectedUnsafeTemporaryAllocation<T, R>(
167+
of type: T.Type,
168+
capacity: Int,
169+
alignment: Int,
170+
_ body: (Builtin.RawPointer) throws -> R
171+
) rethrows -> R {
172+
// How many bytes do we need to allocate?
173+
let byteCount = _byteCountForTemporaryAllocation(of: type, capacity: capacity)
174+
175+
guard _isStackAllocationSafe(byteCount: byteCount, alignment: alignment) else {
176+
return try _fallBackToHeapAllocation(byteCount: byteCount, alignment: alignment, body)
177+
}
178+
179+
// This declaration must come BEFORE Builtin.unprotectedStackAlloc() or
180+
// Builtin.stackDealloc() will end up blowing it away (and the verifier will
181+
// notice and complain.)
182+
let result: R
183+
184+
let stackAddress = Builtin.unprotectedStackAlloc(
185+
capacity._builtinWordValue,
186+
MemoryLayout<T>.stride._builtinWordValue,
187+
alignment._builtinWordValue
188+
)
189+
190+
// The multiple calls to Builtin.stackDealloc() are because defer { } produces
191+
// a child function at the SIL layer and that conflicts with the verifier's
192+
// idea of a stack allocation's lifetime.
193+
do {
194+
result = try body(stackAddress)
195+
Builtin.stackDealloc(stackAddress)
196+
return result
197+
198+
} catch {
199+
Builtin.stackDealloc(stackAddress)
200+
throw error
201+
}
202+
}
203+
#endif
204+
205+
@_alwaysEmitIntoClient @_transparent
206+
internal func _fallBackToHeapAllocation<R>(
207+
byteCount: Int,
208+
alignment: Int,
209+
_ body: (Builtin.RawPointer) throws -> R
210+
) rethrows -> R {
211+
let buffer = UnsafeMutableRawPointer.allocate(
212+
byteCount: byteCount,
213+
alignment: alignment
214+
)
215+
defer {
216+
buffer.deallocate()
217+
}
218+
return try body(buffer._rawValue)
219+
}
220+
173221
// MARK: - Public interface
174222

175223
/// Provides scoped access to a raw buffer pointer with the specified byte count
@@ -222,6 +270,34 @@ public func withUnsafeTemporaryAllocation<R>(
222270
}
223271
}
224272

273+
/// Provides scoped access to a raw buffer pointer with the specified byte count
274+
/// and alignment.
275+
///
276+
/// This function is similar to `withUnsafeTemporaryAllocation`, except that it
277+
/// doesn't trigger stack protection for the stack allocated memory.
278+
@_alwaysEmitIntoClient @_transparent
279+
public func _withUnprotectedUnsafeTemporaryAllocation<R>(
280+
byteCount: Int,
281+
alignment: Int,
282+
_ body: (UnsafeMutableRawBufferPointer) throws -> R
283+
) rethrows -> R {
284+
return try _withUnsafeTemporaryAllocation(
285+
of: Int8.self,
286+
capacity: byteCount,
287+
alignment: alignment
288+
) { pointer in
289+
#if $BuiltinUnprotectedStackAlloc
290+
let buffer = UnsafeMutableRawBufferPointer(
291+
start: .init(pointer),
292+
count: byteCount
293+
)
294+
return try body(buffer)
295+
#else
296+
withUnsafeTemporaryAllocation(byteCount: byteCount, alignment: alignment, body)
297+
#endif
298+
}
299+
}
300+
225301
/// Provides scoped access to a buffer pointer to memory of the specified type
226302
/// and with the specified capacity.
227303
///
@@ -272,3 +348,32 @@ public func withUnsafeTemporaryAllocation<T, R>(
272348
return try body(buffer)
273349
}
274350
}
351+
352+
/// Provides scoped access to a buffer pointer to memory of the specified type
353+
/// and with the specified capacity.
354+
///
355+
/// This function is similar to `withUnsafeTemporaryAllocation`, except that it
356+
/// doesn't trigger stack protection for the stack allocated memory.
357+
@_alwaysEmitIntoClient @_transparent
358+
public func _withUnprotectedUnsafeTemporaryAllocation<T, R>(
359+
of type: T.Type,
360+
capacity: Int,
361+
_ body: (UnsafeMutableBufferPointer<T>) throws -> R
362+
) rethrows -> R {
363+
return try _withUnprotectedUnsafeTemporaryAllocation(
364+
of: type,
365+
capacity: capacity,
366+
alignment: MemoryLayout<T>.alignment
367+
) { pointer in
368+
#if $BuiltinUnprotectedStackAlloc
369+
Builtin.bindMemory(pointer, capacity._builtinWordValue, type)
370+
let buffer = UnsafeMutableBufferPointer<T>(
371+
start: .init(pointer),
372+
count: capacity
373+
)
374+
return try body(buffer)
375+
#else
376+
return try withUnsafeTemporaryAllocation(of: type, capacity: capacity, body)
377+
#endif
378+
}
379+
}

test/IRGen/temporary_allocation/codegen.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ withUnsafeTemporaryAllocation(of: Int32.self, capacity: 4) { buffer in
5959
// CHECK: [[INT_PTR:%[0-9]+]] = ptrtoint [16 x i8]* [[INT_PTR_RAW]] to [[WORD]]
6060
// CHECK: call swiftcc void @blackHole([[WORD]] [[INT_PTR]])
6161

62+
_withUnprotectedUnsafeTemporaryAllocation(of: Int32.self, capacity: 4) { buffer in
63+
blackHole(buffer.baseAddress)
64+
}
65+
// CHECK: [[INT_PTR_RAW:%temp_alloc[0-9]*]] = alloca [16 x i8], align 4
66+
// CHECK: [[INT_PTR:%[0-9]+]] = ptrtoint [16 x i8]* [[INT_PTR_RAW]] to [[WORD]]
67+
// CHECK: call swiftcc void @blackHole([[WORD]] [[INT_PTR]])
68+
6269
withUnsafeTemporaryAllocation(of: Void.self, capacity: 2) { buffer in
6370
blackHole(buffer.baseAddress)
6471
}

test/stdlib/TemporaryAllocation.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ TemporaryAllocationTestSuite.test("untypedAllocationOnHeap") {
4949
}
5050
}
5151

52+
TemporaryAllocationTestSuite.test("unprotectedUntypedAllocationOnStack") {
53+
_withUnprotectedUnsafeTemporaryAllocation(byteCount: 8, alignment: 1) { buffer in
54+
expectStackAllocated(buffer.baseAddress!)
55+
}
56+
}
57+
58+
TemporaryAllocationTestSuite.test("unprotectedUntypedAllocationOnHeap") {
59+
// EXPECTATION: a very large allocated buffer is heap-allocated. (Note if
60+
// swift_stdlib_isStackAllocationSafe() gets fleshed out, this test may need
61+
// to be changed.)
62+
_withUnprotectedUnsafeTemporaryAllocation(byteCount: 100_000, alignment: 1) { buffer in
63+
expectNotStackAllocated(buffer.baseAddress!)
64+
}
65+
}
66+
5267
TemporaryAllocationTestSuite.test("untypedEmptyAllocationIsStackAllocated") {
5368
withUnsafeTemporaryAllocation(byteCount: 0, alignment: 1) { buffer in
5469
expectStackAllocated(buffer.baseAddress!)
@@ -94,6 +109,21 @@ TemporaryAllocationTestSuite.test("typedAllocationOnHeap") {
94109
}
95110
}
96111

112+
TemporaryAllocationTestSuite.test("unprotectedTypedAllocationOnStack") {
113+
_withUnprotectedUnsafeTemporaryAllocation(of: Int.self, capacity: 1) { buffer in
114+
expectStackAllocated(buffer.baseAddress!)
115+
}
116+
}
117+
118+
TemporaryAllocationTestSuite.test("unprotectedTypedAllocationOnHeap") {
119+
// EXPECTATION: a very large allocated buffer is heap-allocated. (Note if
120+
// swift_stdlib_isStackAllocationSafe() gets fleshed out, this test may need
121+
// to be changed.)
122+
_withUnprotectedUnsafeTemporaryAllocation(of: Int.self, capacity: 100_000) { buffer in
123+
expectNotStackAllocated(buffer.baseAddress!)
124+
}
125+
}
126+
97127
TemporaryAllocationTestSuite.test("typedEmptyAllocationIsStackAllocated") {
98128
withUnsafeTemporaryAllocation(of: Int.self, capacity: 0) { buffer in
99129
expectStackAllocated(buffer.baseAddress!)

0 commit comments

Comments
 (0)