Skip to content

Commit 5f38f8e

Browse files
authored
Merge pull request #63676 from eeckstein/improve-stack-protection
SILOptimizer: avoid stack protection in two cases where it's not needed
2 parents 791d780 + abf9900 commit 5f38f8e

File tree

17 files changed

+385
-30
lines changed

17 files changed

+385
-30
lines changed

SwiftCompilerSources/Sources/Optimizer/ModulePasses/StackProtection.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,13 +486,23 @@ private extension Instruction {
486486
if !atp.needsStackProtection {
487487
return nil
488488
}
489+
var hasNoStores = NoStores()
490+
if hasNoStores.walkDownUses(ofValue: atp, path: SmallProjectionPath()) == .continueWalk {
491+
return nil
492+
}
493+
489494
// The result of an `address_to_pointer` may be used in any unsafe way, e.g.
490495
// passed to a C function.
491496
baseAddr = atp.operand
492497
case let ia as IndexAddrInst:
493498
if !ia.needsStackProtection {
494499
return nil
495500
}
501+
var hasNoStores = NoStores()
502+
if hasNoStores.walkDownUses(ofAddress: ia, path: SmallProjectionPath()) == .continueWalk {
503+
return nil
504+
}
505+
496506
// `index_addr` is unsafe if not used for tail-allocated elements (e.g. in Array).
497507
baseAddr = ia.base
498508
default:
@@ -509,6 +519,29 @@ private extension Instruction {
509519
}
510520
}
511521

522+
/// Checks if there are no stores to an address or raw pointer.
523+
private struct NoStores : ValueDefUseWalker, AddressDefUseWalker {
524+
var walkDownCache = WalkerCache<SmallProjectionPath>()
525+
526+
mutating func leafUse(value: Operand, path: SmallProjectionPath) -> WalkResult {
527+
if let ptai = value.instruction as? PointerToAddressInst {
528+
return walkDownUses(ofAddress: ptai, path: path)
529+
}
530+
return .abortWalk
531+
}
532+
533+
mutating func leafUse(address: Operand, path: SmallProjectionPath) -> WalkResult {
534+
switch address.instruction {
535+
case is LoadInst:
536+
return .continueWalk
537+
case let cai as CopyAddrInst:
538+
return address == cai.sourceOperand ? .continueWalk : .abortWalk
539+
default:
540+
return .abortWalk
541+
}
542+
}
543+
}
544+
512545
private extension Function {
513546
func setNeedsStackProtection(_ context: FunctionPassContext) {
514547
if !needsStackProtection {

include/swift/AST/Builtins.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,10 @@ BUILTIN_MISC_OPERATION(DeallocRaw, "deallocRaw", "", Special)
629629
/// is not known at compile time, MaximumAlignment is assumed.
630630
BUILTIN_MISC_OPERATION(StackAlloc, "stackAlloc", "", Special)
631631

632+
/// Like `stackAlloc`, but doesn't set the `[stack_protection]` flag on its
633+
/// containing function.
634+
BUILTIN_MISC_OPERATION(UnprotectedStackAlloc, "unprotectedStackAlloc", "", Special)
635+
632636
/// StackDealloc has type (Builtin.RawPointer) -> ()
633637
///
634638
/// Parameters: address.

include/swift/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ LANGUAGE_FEATURE(BuiltinBuildMainExecutor, 0, "MainActor executor building built
8282
LANGUAGE_FEATURE(BuiltinCreateAsyncTaskInGroup, 0, "MainActor executor building builtin", true)
8383
LANGUAGE_FEATURE(BuiltinCopy, 0, "Builtin.copy()", true)
8484
LANGUAGE_FEATURE(BuiltinStackAlloc, 0, "Builtin.stackAlloc", true)
85+
LANGUAGE_FEATURE(BuiltinUnprotectedStackAlloc, 0, "Builtin.unprotectedStackAlloc", true)
8586
LANGUAGE_FEATURE(BuiltinTaskRunInline, 0, "Builtin.taskRunInline", true)
8687
LANGUAGE_FEATURE(BuiltinUnprotectedAddressOf, 0, "Builtin.unprotectedAddressOf", true)
8788
SUPPRESSIBLE_LANGUAGE_FEATURE(SpecializeAttributeWithAvailability, 0, "@_specialize attribute with availability", true)

lib/AST/ASTPrinter.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3020,6 +3020,10 @@ static bool usesFeatureBuiltinStackAlloc(Decl *decl) {
30203020
return false;
30213021
}
30223022

3023+
static bool usesFeatureBuiltinUnprotectedStackAlloc(Decl *decl) {
3024+
return false;
3025+
}
3026+
30233027
static bool usesFeatureBuiltinAssumeAlignment(Decl *decl) {
30243028
return false;
30253029
}

lib/AST/Builtins.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2707,6 +2707,7 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
27072707
return getDeallocOperation(Context, Id);
27082708

27092709
case BuiltinValueKind::StackAlloc:
2710+
case BuiltinValueKind::UnprotectedStackAlloc:
27102711
return getStackAllocOperation(Context, Id);
27112712
case BuiltinValueKind::StackDealloc:
27122713
return getStackDeallocOperation(Context, Id);

lib/IRGen/IRGenSIL.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3237,7 +3237,8 @@ static Alignment getStackAllocationAlignment(IRGenSILFunction &IGF,
32373237
/// some other builtin.)
32383238
static bool emitStackAllocBuiltinCall(IRGenSILFunction &IGF,
32393239
swift::BuiltinInst *i) {
3240-
if (i->getBuiltinKind() == BuiltinValueKind::StackAlloc) {
3240+
if (i->getBuiltinKind() == BuiltinValueKind::StackAlloc ||
3241+
i->getBuiltinKind() == BuiltinValueKind::UnprotectedStackAlloc) {
32413242
// Stack-allocate a buffer with the specified size/alignment.
32423243
auto loc = i->getLoc().getSourceLoc();
32433244
auto size = getStackAllocationSize(

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SRem)
783783
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, GenericSRem)
784784
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SSubOver)
785785
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, StackAlloc)
786+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, UnprotectedStackAlloc)
786787
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, StackDealloc)
787788
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SToSCheckedTrunc)
788789
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, SToUCheckedTrunc)

lib/SIL/IR/SILInstruction.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1243,7 +1243,8 @@ bool SILInstruction::isAllocatingStack() const {
12431243
return PA->isOnStack();
12441244

12451245
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
1246-
if (BI->getBuiltinKind() == BuiltinValueKind::StackAlloc) {
1246+
if (BI->getBuiltinKind() == BuiltinValueKind::StackAlloc ||
1247+
BI->getBuiltinKind() == BuiltinValueKind::UnprotectedStackAlloc) {
12471248
return true;
12481249
}
12491250
}

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ CONSTANT_OWNERSHIP_BUILTIN(None, AllocRaw)
498498
CONSTANT_OWNERSHIP_BUILTIN(None, AssertConf)
499499
CONSTANT_OWNERSHIP_BUILTIN(None, UToSCheckedTrunc)
500500
CONSTANT_OWNERSHIP_BUILTIN(None, StackAlloc)
501+
CONSTANT_OWNERSHIP_BUILTIN(None, UnprotectedStackAlloc)
501502
CONSTANT_OWNERSHIP_BUILTIN(None, StackDealloc)
502503
CONSTANT_OWNERSHIP_BUILTIN(None, SToSCheckedTrunc)
503504
CONSTANT_OWNERSHIP_BUILTIN(None, SToUCheckedTrunc)

lib/SIL/Utils/MemAccessUtils.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2519,6 +2519,7 @@ static void visitBuiltinAddress(BuiltinInst *builtin,
25192519
case BuiltinValueKind::AllocRaw:
25202520
case BuiltinValueKind::DeallocRaw:
25212521
case BuiltinValueKind::StackAlloc:
2522+
case BuiltinValueKind::UnprotectedStackAlloc:
25222523
case BuiltinValueKind::StackDealloc:
25232524
case BuiltinValueKind::Fence:
25242525
case BuiltinValueKind::StaticReport:

lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ static bool isBarrier(SILInstruction *inst) {
153153
case BuiltinValueKind::CreateTaskGroupWithFlags:
154154
case BuiltinValueKind::DestroyTaskGroup:
155155
case BuiltinValueKind::StackAlloc:
156+
case BuiltinValueKind::UnprotectedStackAlloc:
156157
case BuiltinValueKind::StackDealloc:
157158
case BuiltinValueKind::AssumeAlignment:
158159
return false;

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+
return try 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+
}

stdlib/public/core/UnsafeRawPointer.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,7 @@ public struct UnsafeRawPointer: _Pointer {
463463
as type: T.Type
464464
) -> T {
465465
_debugPrecondition(_isPOD(T.self))
466-
return withUnsafeTemporaryAllocation(of: T.self, capacity: 1) {
466+
return _withUnprotectedUnsafeTemporaryAllocation(of: T.self, capacity: 1) {
467467
let temporary = $0.baseAddress._unsafelyUnwrappedUnchecked
468468
Builtin.int_memcpy_RawPointer_RawPointer_Int64(
469469
temporary._rawValue,
@@ -1245,7 +1245,7 @@ public struct UnsafeMutableRawPointer: _Pointer {
12451245
as type: T.Type
12461246
) -> T {
12471247
_debugPrecondition(_isPOD(T.self))
1248-
return withUnsafeTemporaryAllocation(of: T.self, capacity: 1) {
1248+
return _withUnprotectedUnsafeTemporaryAllocation(of: T.self, capacity: 1) {
12491249
let temporary = $0.baseAddress._unsafelyUnwrappedUnchecked
12501250
Builtin.int_memcpy_RawPointer_RawPointer_Int64(
12511251
temporary._rawValue,

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
}

0 commit comments

Comments
 (0)