2
2
//
3
3
// This source file is part of the Swift.org open source project
4
4
//
5
- // Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
5
+ // Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6
6
// Licensed under Apache License v2.0 with Runtime Library Exception
7
7
//
8
8
// See https://swift.org/LICENSE.txt for license information
@@ -235,7 +235,7 @@ private func insertResultDependencies(for apply: LifetimeDependentApply, _ conte
235
235
guard var sources = apply. getResultDependenceSources ( ) else {
236
236
return
237
237
}
238
- log ( " Creating dependencies for \( apply. applySite) " )
238
+ log ( " Creating result dependencies for \( apply. applySite) " )
239
239
240
240
// Find the dependence base for each source.
241
241
sources. initializeBases ( context)
@@ -246,13 +246,7 @@ private func insertResultDependencies(for apply: LifetimeDependentApply, _ conte
246
246
}
247
247
for resultOper in apply. applySite. indirectResultOperands {
248
248
let accessBase = resultOper. value. accessBase
249
- guard let ( initialAddress, initializingStore) = accessBase. findSingleInitializer ( context) else {
250
- continue
251
- }
252
- // TODO: This might bail-out on SIL that should be diagnosed. We should handle/cleanup projections and casts that
253
- // occur before the initializingStore. Or check in the SIL verifier that all stores without an access scope follow
254
- // this form. Then convert this bail-out to an assert.
255
- guard initialAddress. usesOccurOnOrAfter ( instruction: initializingStore, context) else {
249
+ guard case let . store( initializingStore, initialAddress) = accessBase. findSingleInitializer ( context) else {
256
250
continue
257
251
}
258
252
assert ( initializingStore == resultOper. instruction, " an indirect result is a store " )
@@ -268,7 +262,7 @@ private func insertParameterDependencies(apply: LifetimeDependentApply, target:
268
262
guard var sources = apply. getParameterDependenceSources ( target: target) else {
269
263
return
270
264
}
271
- log ( " Creating dependencies for \( apply. applySite) " )
265
+ log ( " Creating parameter dependencies for \( apply. applySite) " )
272
266
273
267
sources. initializeBases ( context)
274
268
@@ -285,8 +279,13 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
285
279
let markDep = builder. createMarkDependence (
286
280
value: currentValue, base: base, kind: . Unresolved)
287
281
288
- // Address dependencies cannot be represented as SSA values, so it doesn not make sense to replace any uses of the
289
- // dependent address. TODO: consider a separate mark_dependence_addr instruction since the semantics are different.
282
+ // Address dependencies cannot be represented as SSA values, so it does not make sense to replace any uses of the
283
+ // dependent address.
284
+ //
285
+ // TODO: either (1) insert a separate mark_dependence_addr instruction with no return value, or (2) perform data
286
+ // flow to replace all reachable address uses, and if any aren't dominated by base, then insert an extra
287
+ // escaping mark_dependence at this apply site that directly uses the mark_dependence [nonescaping] to force
288
+ // diagnostics to fail.
290
289
if !value. type. isAddress {
291
290
let uses = currentValue. uses. lazy. filter {
292
291
if $0. isScopeEndingUse {
@@ -300,3 +299,247 @@ private func insertMarkDependencies(value: Value, initializer: Instruction?,
300
299
currentValue = markDep
301
300
}
302
301
}
302
+
303
+ /// Walk up the value dependence chain to find the best-effort variable declaration. Typically called while diagnosing
304
+ /// an error.
305
+ ///
306
+ /// Returns an array with at least one introducer value.
307
+ ///
308
+ /// The walk stops at:
309
+ /// - a variable declaration (begin_borrow [var_decl], move_value [var_decl])
310
+ /// - a begin_access for a mutable variable access
311
+ /// - the value or address "root" of the dependence chain
312
+ func gatherVariableIntroducers( for value: Value , _ context: Context )
313
+ -> SingleInlineArray < Value >
314
+ {
315
+ var introducers = SingleInlineArray < Value > ( )
316
+ var useDefVisitor = VariableIntroducerUseDefWalker ( context, scopedValue: value) {
317
+ introducers. push ( $0)
318
+ return . continueWalk
319
+ }
320
+ defer { useDefVisitor. deinitialize ( ) }
321
+ _ = useDefVisitor. walkUp ( valueOrAddress: value)
322
+ assert ( !introducers. isEmpty, " missing variable introducer " )
323
+ return introducers
324
+ }
325
+
326
+ // =============================================================================
327
+ // VariableIntroducerUseDefWalker - upward walk
328
+ // =============================================================================
329
+
330
+ /// Walk up lifetime dependencies to the first value associated with a variable declaration.
331
+ ///
332
+ /// To start walking:
333
+ /// walkUp(valueOrAddress: Value) -> WalkResult
334
+ ///
335
+ /// This utility finds the value or address associated with the lvalue (variable declaration) that is passed as the
336
+ /// source of a lifetime dependent argument. If no lvalue is found, then it finds the "root" of the chain of temporary
337
+ /// rvalues.
338
+ ///
339
+ /// This "looks through" projections: a property that is either visible as a stored property or access via
340
+ /// unsafe[Mutable]Address.
341
+ ///
342
+ /// dependsOn(lvalue.field) // finds 'lvalue' when 'field' is a stored property
343
+ ///
344
+ /// dependsOn(lvalue.computed) // finds the temporary value directly returned by a getter.
345
+ ///
346
+ /// SILGen emits temporary copies that violate lifetime dependence semantcs. This utility looks through such temporary
347
+ /// copies, stopping at a value that introduces an immutable variable: move_value [var_decl] or begin_borrow [var_decl],
348
+ /// or at an access of a mutable variable: begin_access [read] or begin_access [modify].
349
+ ///
350
+ /// In this example, the dependence "root" is copied, borrowed, and forwarded before being used as the base operand of
351
+ /// `mark_dependence`. The dependence "root" is the parent of the outer-most dependence scope.
352
+ ///
353
+ /// %root = apply // lifetime dependence root
354
+ /// %copy = copy_value %root
355
+ /// %parent = begin_borrow %copy // lifetime dependence parent value
356
+ /// %base = struct_extract %parent // lifetime dependence base value
357
+ /// %dependent = mark_dependence [nonescaping] %value on %base
358
+ ///
359
+ /// VariableIntroducerUseDefWalker extends the ForwardingUseDefWalker to follow copies, moves, and
360
+ /// borrows. ForwardingUseDefWalker treats these as forward-extended lifetime introducers. But they inherit a lifetime
361
+ /// dependency from their operand because non-escapable values can be copied, moved, and borrowed. Nonetheless, all of
362
+ /// their uses must remain within original dependence scope.
363
+ ///
364
+ /// # owned lifetime dependence
365
+ /// %parent = apply // begin dependence scope -+
366
+ /// ... |
367
+ /// %1 = mark_dependence [nonescaping] %value on %parent |
368
+ /// ... |
369
+ /// %2 = copy_value %1 -+ |
370
+ /// # forwarding instruction | |
371
+ /// %3 = struct $S (%2) | forward-extended lifetime |
372
+ /// | | OSSA Lifetime
373
+ /// %4 = move_value %3 -+ |
374
+ /// ... | forward-extended lifetime |
375
+ /// %5 = begin_borrow %4 | -+ |
376
+ /// # dependent use of %1 | | forward-extended lifetime|
377
+ /// end_borrow %5 | -+ |
378
+ /// destroy_value %4 -+ |
379
+ /// ... |
380
+ /// destroy_value %parent // end dependence scope -+
381
+ ///
382
+ /// All of the dependent uses including `end_borrow %5` and `destroy_value %4` must be before the end of the dependence
383
+ /// scope: `destroy_value %parent`. In this case, the dependence parent is an owned value, so the scope is simply the
384
+ /// value's OSSA lifetime.
385
+ struct VariableIntroducerUseDefWalker : ForwardingUseDefWalker {
386
+ // The ForwardingUseDefWalker's context is the most recent lifetime owner.
387
+ typealias PathContext = Value ?
388
+
389
+ let context : Context
390
+
391
+ // If the scoped value is trivial, then only the variable's lexical scope is relevant, and access scopes can be
392
+ // ignored.
393
+ let isTrivialScope : Bool
394
+
395
+ // This visited set is only really needed for instructions with
396
+ // multiple results, including phis.
397
+ private var visitedValues : ValueSet
398
+
399
+ // Call \p visit rather than calling this directly.
400
+ private let visitorClosure : ( Value ) -> WalkResult
401
+
402
+ init ( _ context: Context , scopedValue: Value , _ visitor: @escaping ( Value ) -> WalkResult ) {
403
+ self . context = context
404
+ self . isTrivialScope = scopedValue. type. isAddress
405
+ ? scopedValue. type. objectType. isTrivial ( in: scopedValue. parentFunction)
406
+ : scopedValue. isTrivial ( context)
407
+ self . visitedValues = ValueSet ( context)
408
+ self . visitorClosure = visitor
409
+ }
410
+
411
+ mutating func deinitialize( ) {
412
+ visitedValues. deinitialize ( )
413
+ }
414
+
415
+ mutating func needWalk( for value: Value , _ owner: Value ? ) -> Bool {
416
+ visitedValues. insert ( value)
417
+ }
418
+
419
+ mutating func introducer( _ value: Value , _ owner: Value ? ) -> WalkResult {
420
+ return visitorClosure ( value)
421
+ }
422
+
423
+ mutating func walkUp( valueOrAddress: Value ) -> WalkResult {
424
+ if valueOrAddress. type. isAddress {
425
+ return walkUp ( address: valueOrAddress)
426
+ }
427
+ return walkUp ( newLifetime: valueOrAddress)
428
+ }
429
+ }
430
+
431
+ // Helpers
432
+ extension VariableIntroducerUseDefWalker {
433
+ mutating func walkUp( newLifetime: Value ) -> WalkResult {
434
+ let newOwner = newLifetime. ownership == . owned ? newLifetime : nil
435
+ return walkUp ( value: newLifetime, newOwner)
436
+ }
437
+
438
+ mutating func walkUp( value: Value , _ owner: Value ? ) -> WalkResult {
439
+ // Check for variable introducers: move_value, begin_value, before following OwnershipTransitionInstruction.
440
+ if let inst = value. definingInstruction, VariableScopeInstruction ( inst) != nil {
441
+ return visitorClosure ( value)
442
+ }
443
+ switch value. definingInstruction {
444
+ case let transition as OwnershipTransitionInstruction :
445
+ return walkUp ( newLifetime: transition. operand. value)
446
+ case let load as LoadInstruction :
447
+ return walkUp ( address: load. address)
448
+ default :
449
+ break
450
+ }
451
+ // If the dependence chain has a phi, consider it a root. Dependence roots dominate all dependent values.
452
+ if Phi ( value) != nil {
453
+ return introducer ( value, owner)
454
+ }
455
+ // ForwardingUseDefWalker will callback to introducer() when it finds no forwarding instruction.
456
+ return walkUpDefault ( forwarded: value, owner)
457
+ }
458
+
459
+ // Handle temporary allocations and access scopes.
460
+ mutating func walkUp( address: Value ) -> WalkResult {
461
+ let accessBaseAndScopes = address. accessBaseWithScopes
462
+ // Continue walking for some kinds of access base.
463
+ switch accessBaseAndScopes. base {
464
+ case . box, . global, . class, . tail, . pointer, . index, . unidentified:
465
+ break
466
+ case let . stack( allocStack) :
467
+ if allocStack. varDecl == nil {
468
+ // Ignore temporary stack locations. Their access scopes do not affect lifetime dependence.
469
+ return walkUp ( stackInitializer: allocStack, at: address)
470
+ }
471
+ case let . argument( arg) :
472
+ // Ignore access scopes for @in or @in_guaranteed arguments when all scopes are reads. Do not ignore a [read]
473
+ // access of an inout argument or outer [modify]. Mutation later with the outer scope could invalidate the
474
+ // borrowed state in this narrow scope. Do not ignore any mark_depedence on the address.
475
+ if arg. convention. isIndirectIn && accessBaseAndScopes. isOnlyReadAccess {
476
+ return introducer ( arg, nil )
477
+ }
478
+ // @inout arguments may be singly initialized (when no modification exists in this function), but this is not
479
+ // relevant here because they require nested access scopes which can never be ignored.
480
+ case let . yield( yieldedAddress) :
481
+ // Ignore access scopes for @in or @in_guaranteed yields when all scopes are reads.
482
+ let apply = yieldedAddress. definingInstruction as! FullApplySite
483
+ if apply. convention ( of: yieldedAddress) . isIndirectIn && accessBaseAndScopes. isOnlyReadAccess {
484
+ return introducer ( yieldedAddress, nil )
485
+ }
486
+ case . storeBorrow( let sb) :
487
+ // Walk up through a store into a temporary.
488
+ if accessBaseAndScopes. scopes. isEmpty,
489
+ case . stack = sb. destinationOperand. value. accessBase {
490
+ return walkUp ( newLifetime: sb. source)
491
+ }
492
+ }
493
+ // Skip the access scope for unsafe[Mutable]Address. Treat it like a projection of 'self' rather than a separate
494
+ // variable access.
495
+ if case let . access( innerAccess) = accessBaseAndScopes. scopes. first,
496
+ let addressorSelf = innerAccess. unsafeAddressorSelf {
497
+ return walkUp ( valueOrAddress: addressorSelf)
498
+ }
499
+ // Ignore the acces scope for trivial values regardless of whether it is singly-initialized. Trivial values do not
500
+ // need to be kept alive in memory and can be safely be overwritten in the same scope. Lifetime dependence only
501
+ // cares that the loaded value is within the lexical scope of the trivial value's variable declaration. Rather than
502
+ // skipping all access scopes, call 'walkUp' on each nested access in case one of them needs to redirect the walk,
503
+ // as required for 'access.unsafeAddressorSelf'.
504
+ if isTrivialScope {
505
+ switch accessBaseAndScopes. scopes. first {
506
+ case . none, . base:
507
+ break
508
+ case let . access( beginAccess) :
509
+ return walkUp ( address: beginAccess. address)
510
+ case let . dependence( markDep) :
511
+ return walkUp ( address: markDep. value)
512
+ }
513
+ }
514
+ return introducer ( accessBaseAndScopes. enclosingAccess. address ?? address, nil )
515
+ }
516
+
517
+ // Handle singly-initialized temporary stack locations.
518
+ mutating func walkUp( stackInitializer allocStack: AllocStackInst , at address: Value ) -> WalkResult {
519
+ guard let initializer = allocStack. accessBase. findSingleInitializer ( context) else {
520
+ return introducer ( address, nil )
521
+ }
522
+ if case let . store( store, _) = initializer {
523
+ switch store {
524
+ case let store as StoringInstruction :
525
+ return walkUp ( newLifetime: store. source)
526
+ case let srcDestInst as SourceDestAddrInstruction :
527
+ return walkUp ( address: srcDestInst. destination)
528
+ case let apply as FullApplySite :
529
+ if let f = apply. referencedFunction, f. isConvertPointerToPointerArgument {
530
+ return walkUp ( address: apply. parameterOperands [ 0 ] . value)
531
+ }
532
+ default :
533
+ break
534
+ }
535
+ }
536
+ return introducer ( address, nil )
537
+ }
538
+ }
539
+
540
+ let variableIntroducerTest = FunctionTest ( " variable_introducer " ) {
541
+ function, arguments, context in
542
+ let value = arguments. takeValue ( )
543
+ print ( " Variable introducers of: \( value) " )
544
+ print ( gatherVariableIntroducers ( for: value, context) )
545
+ }
0 commit comments