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