@@ -28,24 +28,24 @@ private func log(_ message: @autoclosure () -> String) {
28
28
/// Within safe swift code there shouldn't be any buffer overflows. But if the address
29
29
/// of a stack variable is converted to an unsafe pointer, it's not in the control of
30
30
/// the compiler anymore.
31
- /// This means, if there is any `address_to_pointer` instruction for an `alloc_stack `,
32
- /// such a function is marked for stack protection.
33
- /// Another case is `index_addr` for non-tail allocated memory. This pattern appears if
34
- /// pointer arithmetic is done with unsafe pointers in swift code.
31
+ /// This means, if an `alloc_stack` ends up at an `address_to_pointer [stack_protection] `,
32
+ /// the `alloc_stack`'s function is marked for stack protection.
33
+ /// Another case is `index_addr [stack_protection] ` for non-tail allocated memory. This
34
+ /// pattern appears if pointer arithmetic is done with unsafe pointers in swift code.
35
35
///
36
36
/// If the origin of an unsafe pointer can only be tracked to a function argument, the
37
37
/// pass tries to find the root stack allocation for such an argument by doing an
38
- /// inter-procedural analysis. If this is not possible, the fallback is to move the
39
- /// argument into a temporary `alloc_stack` and do the unsafe pointer operations on
40
- /// the temporary.
38
+ /// inter-procedural analysis. If this is not possible and the `enableMoveInoutStackProtection`
39
+ /// option is set, the fallback is to move the argument into a temporary `alloc_stack`
40
+ /// and do the unsafe pointer operations on the temporary.
41
41
let stackProtection = ModulePass ( name: " stack-protection " , {
42
42
( context: ModulePassContext ) in
43
43
44
44
if !context. options. enableStackProtection {
45
45
return
46
46
}
47
47
48
- var optimization = StackProtectionOptimization ( )
48
+ var optimization = StackProtectionOptimization ( enableMoveInout : context . options . enableMoveInoutStackProtection )
49
49
optimization. processModule ( context)
50
50
} )
51
51
@@ -60,13 +60,15 @@ let functionStackProtection = FunctionPass(name: "function-stack-protection", {
60
60
return
61
61
}
62
62
63
- var optimization = StackProtectionOptimization ( )
63
+ var optimization = StackProtectionOptimization ( enableMoveInout : context . options . enableMoveInoutStackProtection )
64
64
optimization. process ( function: function, context)
65
65
} )
66
66
67
67
/// The optimization algorithm.
68
68
private struct StackProtectionOptimization {
69
69
70
+ private let enableMoveInout : Bool
71
+
70
72
// The following members are nil/not used if this utility is used on function-level.
71
73
72
74
private var moduleContext : ModulePassContext ?
@@ -76,7 +78,11 @@ private struct StackProtectionOptimization {
76
78
// Functions (other than the currently processed one) which need stack protection,
77
79
// are added to this array in `findOriginsInCallers`.
78
80
private var needStackProtection : [ Function ] = [ ]
79
-
81
+
82
+ init ( enableMoveInout: Bool ) {
83
+ self . enableMoveInout = enableMoveInout
84
+ }
85
+
80
86
/// The main entry point if running on module-level.
81
87
mutating func processModule( _ moduleContext: ModulePassContext ) {
82
88
self . moduleContext = moduleContext
@@ -147,6 +153,8 @@ private struct StackProtectionOptimization {
147
153
case . yes:
148
154
// For example:
149
155
// %baseAddr = alloc_stack $T
156
+ log ( " local: \( function. name) -- \( instruction) " )
157
+
150
158
function. setNeedsStackProtection ( context)
151
159
152
160
case . decidedInCaller( let arg) :
@@ -157,7 +165,7 @@ private struct StackProtectionOptimization {
157
165
defer { worklist. deinitialize ( ) }
158
166
worklist. push ( arg)
159
167
160
- if ! findOriginsInCallers( & worklist) {
168
+ if findOriginsInCallers ( & worklist) == NeedInsertMoves . yes {
161
169
// We don't know the origin of the function argument. Therefore we need to do the
162
170
// conservative default which is to move the value to a temporary stack location.
163
171
if let beginAccess = scope {
@@ -179,22 +187,19 @@ private struct StackProtectionOptimization {
179
187
180
188
// If the object is passed as an argument to its function, add those arguments
181
189
// to the worklist.
182
- switch worklist. push ( rootsOf: obj) {
183
- case . failed:
184
- // If we cannot find the roots, the object is most likely not stack allocated.
185
- return
186
- case . succeeded( let foundStackAlloc) :
187
- if foundStackAlloc {
188
- // The object is created by an `alloc_ref [stack]`.
189
- function. setNeedsStackProtection ( context)
190
- }
190
+ let ( _, foundStackAlloc) = worklist. push ( rootsOf: obj)
191
+ if foundStackAlloc {
192
+ // The object is created by an `alloc_ref [stack]`.
193
+ log ( " objectIfStackPromoted: \( function. name) -- \( instruction) " )
194
+
195
+ function. setNeedsStackProtection ( context)
191
196
}
192
197
// In case the (potentially) stack allocated object is passed via an argument,
193
198
// process the worklist as we do for indirect arguments (see above).
194
199
// For example:
195
200
// bb0(%0: $Class):
196
201
// %baseAddr = ref_element_addr %0 : $Class, #Class.field
197
- if ! findOriginsInCallers( & worklist) ,
202
+ if findOriginsInCallers ( & worklist) == NeedInsertMoves . yes ,
198
203
let beginAccess = scope {
199
204
// We don't know the origin of the object. Therefore we need to do the
200
205
// conservative default which is to move the value to a temporary stack location.
@@ -206,15 +211,26 @@ private struct StackProtectionOptimization {
206
211
break
207
212
}
208
213
}
209
-
214
+
215
+ /// Return value of `findOriginsInCallers()`.
216
+ enum NeedInsertMoves {
217
+ // Not all call sites could be identified, and if moves are enabled (`enableMoveInout`)
218
+ // the original argument should be moved to a temporary.
219
+ case yes
220
+
221
+ // Either all call sites could be identified, which means that stack protection is done
222
+ // in the callers, or moves are not enabled (`enableMoveInout` is false).
223
+ case no
224
+ }
225
+
210
226
/// Find all origins of function arguments in `worklist`.
211
227
/// All functions, which allocate such an origin are added to `self.needStackProtection`.
212
228
/// Returns true if all origins could be found and false, if there are unknown origins.
213
- private mutating func findOriginsInCallers( _ worklist: inout ArgumentWorklist ) -> Bool {
229
+ private mutating func findOriginsInCallers( _ worklist: inout ArgumentWorklist ) -> NeedInsertMoves {
214
230
215
231
guard let moduleContext = moduleContext else {
216
232
// Don't do any inter-procedural analysis when used on function-level.
217
- return false
233
+ return enableMoveInout ? . yes : . no
218
234
}
219
235
220
236
// Put the resulting functions into a temporary array, because we only add them to
@@ -230,28 +246,33 @@ private struct StackProtectionOptimization {
230
246
while let arg = worklist. pop ( ) {
231
247
let f = arg. function
232
248
let uses = functionUses. getUses ( of: f)
233
- if uses. hasUnknownUses {
234
- return false
249
+ if uses. hasUnknownUses && enableMoveInout {
250
+ return NeedInsertMoves . yes
235
251
}
236
252
237
253
for useInst in uses {
238
254
guard let fri = useInst as? FunctionRefInst else {
239
- return false
255
+ if enableMoveInout {
256
+ return NeedInsertMoves . yes
257
+ }
258
+ continue
240
259
}
241
260
242
261
for functionRefUse in fri. uses {
243
- guard let apply = functionRefUse. instruction as? ApplySite else {
244
- return false
245
- }
246
- guard let callerArgIdx = apply. callerArgIndex ( calleeArgIndex: arg. index) else {
247
- return false
262
+ guard let apply = functionRefUse. instruction as? ApplySite ,
263
+ let callerArgIdx = apply. callerArgIndex ( calleeArgIndex: arg. index) else {
264
+ if enableMoveInout {
265
+ return NeedInsertMoves . yes
266
+ }
267
+ continue
248
268
}
249
269
let callerArg = apply. arguments [ callerArgIdx]
250
270
if callerArg. type. isAddress {
251
271
// It's an indirect argument.
252
272
switch callerArg. accessBase. isStackAllocated {
253
273
case . yes:
254
274
if !callerArg. function. needsStackProtection {
275
+ log ( " alloc_stack in caller: \( callerArg. function. name) -- \( callerArg) " )
255
276
newFunctions. push ( callerArg. function)
256
277
}
257
278
case . no:
@@ -266,36 +287,38 @@ private struct StackProtectionOptimization {
266
287
case . objectIfStackPromoted( let obj) :
267
288
// If the object is passed as an argument to its function,
268
289
// add those arguments to the worklist.
269
- switch worklist. push ( rootsOf: obj) {
270
- case . failed :
271
- return false
272
- case . succeeded ( let foundStackAlloc ) :
273
- if foundStackAlloc && !obj. function. needsStackProtection {
274
- // The object is created by an `alloc_ref [stack]`.
275
- newFunctions . push ( obj. function)
276
- }
290
+ let ( foundUnknownRoots , foundStackAlloc ) = worklist. push ( rootsOf: obj)
291
+ if foundUnknownRoots && enableMoveInout {
292
+ return NeedInsertMoves . yes
293
+ }
294
+ if foundStackAlloc && !obj. function. needsStackProtection {
295
+ // The object is created by an `alloc_ref [stack]`.
296
+ log ( " object in caller: \ ( obj. function. name ) -- \( obj ) " )
297
+ newFunctions . push ( obj . function )
277
298
}
278
299
case . unknown:
279
- return false
300
+ if enableMoveInout {
301
+ return NeedInsertMoves . yes
302
+ }
280
303
}
281
304
} else {
282
305
// The argument is an object. If the object is itself passed as an argument
283
306
// to its function, add those arguments to the worklist.
284
- switch worklist. push ( rootsOf: callerArg) {
285
- case . failed :
286
- return false
287
- case . succeeded ( let foundStackAlloc ) :
288
- if foundStackAlloc && !callerArg. function. needsStackProtection {
289
- // The object is created by an `alloc_ref [stack]`.
290
- newFunctions . push ( callerArg. function)
291
- }
307
+ let ( foundUnknownRoots , foundStackAlloc ) = worklist. push ( rootsOf: callerArg)
308
+ if foundUnknownRoots && enableMoveInout {
309
+ return NeedInsertMoves . yes
310
+ }
311
+ if foundStackAlloc && !callerArg. function. needsStackProtection {
312
+ // The object is created by an `alloc_ref [stack]`.
313
+ log ( " object arg in caller: \ ( callerArg. function. name ) -- \( callerArg ) " )
314
+ newFunctions . push ( callerArg . function )
292
315
}
293
316
}
294
317
}
295
318
}
296
319
}
297
320
needStackProtection. append ( contentsOf: newFunctions)
298
- return true
321
+ return NeedInsertMoves . no
299
322
}
300
323
301
324
/// Moves the value of an indirect argument to a temporary stack location, if possible.
@@ -366,9 +389,16 @@ private struct StackProtectionOptimization {
366
389
/// Worklist for inter-procedural analysis of function arguments.
367
390
private struct ArgumentWorklist : ValueUseDefWalker {
368
391
var walkUpCache = WalkerCache < SmallProjectionPath > ( )
392
+
393
+ // Used in `push(rootsOf:)`
369
394
private var foundStackAlloc = false
395
+ private var foundUnknownRoots = false
370
396
397
+ // Contains arguments which are already handled and don't need to be put into the worklist again.
398
+ // Note that this cannot be a `ValueSet`, because argument can be from different functions.
371
399
private var handled = Set < FunctionArgument > ( )
400
+
401
+ // The actual worklist.
372
402
private var list : Stack < FunctionArgument >
373
403
374
404
init ( _ context: PassContext ) {
@@ -385,21 +415,15 @@ private struct ArgumentWorklist : ValueUseDefWalker {
385
415
}
386
416
}
387
417
388
- enum PushResult {
389
- case failed
390
- case succeeded( foundStackAlloc: Bool )
391
- }
392
-
393
418
/// Pushes all roots of `object`, which are function arguments, to the worklist.
394
- /// Returns `.succeeded(true)` if some of the roots are `alloc_ref [stack]` instructions.
395
- mutating func push( rootsOf object: Value ) -> PushResult {
419
+ /// If the returned `foundUnknownRoots` is true, it means that not all roots of `object` could
420
+ /// be tracked to a function argument.
421
+ /// If the returned `foundStackAlloc` than at least one found root is an `alloc_ref [stack]`.
422
+ mutating func push( rootsOf object: Value ) -> ( foundUnknownRoots: Bool , foundStackAlloc: Bool ) {
396
423
foundStackAlloc = false
397
- switch walkUp ( value: object, path: SmallProjectionPath ( . anything) ) {
398
- case . continueWalk:
399
- return . succeeded( foundStackAlloc: foundStackAlloc)
400
- case . abortWalk:
401
- return . failed
402
- }
424
+ foundUnknownRoots = false
425
+ _ = walkUp ( value: object, path: SmallProjectionPath ( . anything) )
426
+ return ( foundUnknownRoots, foundStackAlloc)
403
427
}
404
428
405
429
mutating func pop( ) -> FunctionArgument ? {
@@ -413,15 +437,12 @@ private struct ArgumentWorklist : ValueUseDefWalker {
413
437
if ar. canAllocOnStack {
414
438
foundStackAlloc = true
415
439
}
416
- return . continueWalk
417
440
case let arg as FunctionArgument :
418
- if handled. insert ( arg) . 0 {
419
- list. push ( arg)
420
- }
421
- return . continueWalk
422
- default :
423
- return . abortWalk
441
+ push ( arg)
442
+ default :
443
+ foundUnknownRoots = true
424
444
}
445
+ return . continueWalk
425
446
}
426
447
}
427
448
@@ -491,7 +512,6 @@ private extension Instruction {
491
512
private extension Function {
492
513
func setNeedsStackProtection( _ context: PassContext ) {
493
514
if !needsStackProtection {
494
- log ( " needs protection: \( name) " )
495
515
set ( needStackProtection: true , context)
496
516
}
497
517
}
0 commit comments