|
12 | 12 |
|
13 | 13 | import SIL
|
14 | 14 |
|
| 15 | +private let verbose = true |
| 16 | + |
| 17 | +private func log(_ message: @autoclosure () -> String) { |
| 18 | + if verbose { |
| 19 | + print("### \(message())") |
| 20 | + } |
| 21 | +} |
| 22 | + |
15 | 23 | /// Classify address uses. This can be used by def-use walkers to
|
16 | 24 | /// ensure complete handling of all legal SIL patterns.
|
17 | 25 | ///
|
@@ -340,3 +348,207 @@ extension AddressInitializationWalker {
|
340 | 348 | return .abortWalk
|
341 | 349 | }
|
342 | 350 | }
|
| 351 | + |
| 352 | +/// A live range representing the ownership of addressible memory. |
| 353 | +/// |
| 354 | +/// This live range represents the minimal guaranteed lifetime of the object being addressed. Uses of derived addresses |
| 355 | +/// may be extended up to the ends of this scope without violating ownership. |
| 356 | +/// |
| 357 | +/// .liveOut objects (@in_guaranteed, @out) and .global variables have no instruction range. |
| 358 | +/// |
| 359 | +/// .local objects (alloc_stack, yield, @in, @inout) report the single live range of the full assignment that reaches |
| 360 | +/// this address. |
| 361 | +/// |
| 362 | +/// .owned values (boxes and references) simply report OSSA liveness. |
| 363 | +/// |
| 364 | +/// .borrow values report each borrow scope's range. The effective live range is their intersection. A valid use must |
| 365 | +/// lie within all ranges. |
| 366 | +/// |
| 367 | +/// FIXME: .borrow values should be represented with a single multiply-defined instruction range. Otherwise we will run |
| 368 | +/// out of blockset bits as soon as we have multiple ranges (each range uses three sets). It is ok to take the |
| 369 | +/// union of the borrow ranges since all address uses that may be extended will be already be dominated by the current |
| 370 | +/// address. Alternatively, we can have a utility that folds two InstructionRanges together as an intersection, and |
| 371 | +/// repeatedly fold the range of each borrow introducer. |
| 372 | +/// |
| 373 | +/// Example: |
| 374 | +/// |
| 375 | +/// %x = alloc_box |
| 376 | +/// %b = begin_borrow %x -+ begin ownership range |
| 377 | +/// %p = project_box %b | <--- accessBase |
| 378 | +/// %a = begin_access %p | |
| 379 | +/// end_access %a | <--- address use |
| 380 | +/// end_borrow %b -+ end ownership range |
| 381 | +/// destroy_value %x |
| 382 | +/// |
| 383 | +/// This may return multiple ranges if a borrowed reference has multiple introducers: |
| 384 | +/// |
| 385 | +/// %b1 = begin_borrow -+ range1 |
| 386 | +/// %b2 = begin_borrow | -+ -+ range2 |
| 387 | +/// %s = struct (%b1, %2) | | | |
| 388 | +/// %e = struct_extract %s, #s.0 | | | intersection |
| 389 | +/// %d = ref_element_addr %e | | | where ownership |
| 390 | +/// %a = begin_access %d | | | is valid |
| 391 | +/// end_access %a | | | |
| 392 | +/// end_borrow %b1 -+ | -+ |
| 393 | +/// ... | |
| 394 | +/// end_borrow %b2 -+ |
| 395 | +/// |
| 396 | +/// Note: The resulting live range must be deinitialized in stack order. |
| 397 | +enum AddressOwnershipLiveRange : CustomStringConvertible { |
| 398 | + case liveOut(FunctionArgument) |
| 399 | + case global(GlobalVariable) |
| 400 | + case local(Value, InstructionRange) // Value represents the local allocation |
| 401 | + case owned(Value, InstructionRange) |
| 402 | + case borrow(SingleInlineArray<(BeginBorrowValue, InstructionRange)>) |
| 403 | + |
| 404 | + mutating func deinitialize() { |
| 405 | + switch self { |
| 406 | + case .liveOut, .global: |
| 407 | + break |
| 408 | + case var .local(_, range): |
| 409 | + range.deinitialize() |
| 410 | + case var .owned(_, range): |
| 411 | + range.deinitialize() |
| 412 | + case var .borrow(ranges): |
| 413 | + for idx in ranges.indices { |
| 414 | + ranges[idx].1.deinitialize() |
| 415 | + } |
| 416 | + } |
| 417 | + } |
| 418 | + |
| 419 | + /// Return nil if the live range is unknown. |
| 420 | + static func compute(for address: Value, at begin: Instruction, |
| 421 | + _ localReachabilityCache: LocalVariableReachabilityCache, |
| 422 | + _ context: FunctionPassContext) -> AddressOwnershipLiveRange? { |
| 423 | + let accessBase = address.accessBase |
| 424 | + switch accessBase { |
| 425 | + case .box, .class, .tail: |
| 426 | + return computeValueLiveRange(of: accessBase.reference!, context) |
| 427 | + case let .stack(allocStack): |
| 428 | + return computeLocalLiveRange(allocation: allocStack, begin: begin, localReachabilityCache, context) |
| 429 | + case let .global(global): |
| 430 | + return .global(global) |
| 431 | + case let .argument(arg): |
| 432 | + switch arg.convention { |
| 433 | + case .indirectInGuaranteed, .indirectOut: |
| 434 | + return .liveOut(arg) |
| 435 | + case .indirectIn, .indirectInout, .indirectInoutAliasable: |
| 436 | + return computeLocalLiveRange(allocation: arg, begin: begin, localReachabilityCache, context) |
| 437 | + default: |
| 438 | + return nil |
| 439 | + } |
| 440 | + case let .yield(result): |
| 441 | + let apply = result.parentInstruction as! BeginApplyInst |
| 442 | + switch apply.convention(of: result) { |
| 443 | + case .indirectInGuaranteed: |
| 444 | + var range = InstructionRange(for: address, context) |
| 445 | + _ = BorrowingInstruction(apply)!.visitScopeEndingOperands(context) { |
| 446 | + range.insert($0.instruction) |
| 447 | + return .continueWalk |
| 448 | + } |
| 449 | + return .local(result, range) |
| 450 | + case .indirectIn, .indirectInout, .indirectInoutAliasable: |
| 451 | + return computeLocalLiveRange(allocation: result, begin: begin, localReachabilityCache, context) |
| 452 | + default: |
| 453 | + return nil |
| 454 | + } |
| 455 | + case .pointer, .unidentified: |
| 456 | + return nil |
| 457 | + } |
| 458 | + } |
| 459 | + |
| 460 | + /// Does this inclusive range include `inst`, assuming that `inst` occurs after a definition of the live range. |
| 461 | + func coversUse(_ inst: Instruction) -> Bool { |
| 462 | + switch self { |
| 463 | + case .liveOut, .global: |
| 464 | + return true |
| 465 | + case let .local(_, range), let .owned(_, range): |
| 466 | + return range.inclusiveRangeContains(inst) |
| 467 | + case let .borrow(borrowRanges): |
| 468 | + for (_, range) in borrowRanges { |
| 469 | + if !range.inclusiveRangeContains(inst) { |
| 470 | + return false |
| 471 | + } |
| 472 | + } |
| 473 | + return true |
| 474 | + } |
| 475 | + } |
| 476 | + |
| 477 | + var description: String { |
| 478 | + switch self { |
| 479 | + case let .liveOut(arg): |
| 480 | + return "liveOut: \(arg)" |
| 481 | + case let .global(global): |
| 482 | + return "global: \(global)" |
| 483 | + case let .local(allocation, range): |
| 484 | + return "local: \(allocation)\n\(range)" |
| 485 | + case let .owned(value, range): |
| 486 | + return "owned: \(value)\n\(range)" |
| 487 | + case let .borrow(borrowRanges): |
| 488 | + var str = "" |
| 489 | + for (borrow, range) in borrowRanges { |
| 490 | + str += "borrow: \(borrow)\n\(range)" |
| 491 | + } |
| 492 | + return str |
| 493 | + } |
| 494 | + } |
| 495 | +} |
| 496 | + |
| 497 | +extension AddressOwnershipLiveRange { |
| 498 | + /// Compute the ownership live range of any non-address value. |
| 499 | + /// |
| 500 | + /// For an owned value, simply compute its liveness. For a guaranteed value, return a separate range for each borrow |
| 501 | + /// introducer. |
| 502 | + /// |
| 503 | + /// For address values, use AccessBase.computeOwnershipRange. |
| 504 | + /// |
| 505 | + /// FIXME: This should use computeLinearLiveness rather than computeInteriorLiveness as soon as lifetime completion |
| 506 | + /// runs immediately after SILGen. |
| 507 | + private static func computeValueLiveRange(of value: Value, _ context: FunctionPassContext) |
| 508 | + -> AddressOwnershipLiveRange? { |
| 509 | + switch value.ownership { |
| 510 | + case .none, .unowned: |
| 511 | + // This is unexpected for a value with derived addresses. |
| 512 | + return nil |
| 513 | + case .owned: |
| 514 | + return .owned(value, computeInteriorLiveness(for: value, context)) |
| 515 | + case .guaranteed: |
| 516 | + return .borrow(computeBorrowLiveRange(for: value, context)) |
| 517 | + } |
| 518 | + } |
| 519 | + |
| 520 | + private static func computeLocalLiveRange(allocation: Value, begin: Instruction, |
| 521 | + _ localReachabilityCache: LocalVariableReachabilityCache, |
| 522 | + _ context: some Context) -> AddressOwnershipLiveRange? { |
| 523 | + guard let localReachability = localReachabilityCache.reachability(for: allocation, context) else { |
| 524 | + return nil |
| 525 | + } |
| 526 | + var reachingAssignments = Stack<LocalVariableAccess>(context) |
| 527 | + defer { reachingAssignments.deinitialize() } |
| 528 | + |
| 529 | + if !localReachability.gatherReachingAssignments(for: begin, in: &reachingAssignments) { |
| 530 | + return nil |
| 531 | + } |
| 532 | + // Any one of the reaching assignment is sufficient to compute the minimal live range. The caller presumably only |
| 533 | + // cares about the live range that is dominated by 'begin'. Since all assignments gathered above reach |
| 534 | + // 'begin', their live ranges must all be identical on all paths through 'addressInst'. |
| 535 | + let assignment = reachingAssignments.first! |
| 536 | + |
| 537 | + var reachableUses = Stack<LocalVariableAccess>(context) |
| 538 | + defer { reachableUses.deinitialize() } |
| 539 | + |
| 540 | + localReachability.gatherKnownReachableUses(from: assignment, in: &reachableUses) |
| 541 | + |
| 542 | + let assignmentInst = assignment.instruction ?? allocation.parentFunction.entryBlock.instructions.first! |
| 543 | + var range = InstructionRange(begin: assignmentInst, context) |
| 544 | + for localAccess in reachableUses { |
| 545 | + if localAccess.kind == .escape { |
| 546 | + log("Local variable: \(allocation)\n escapes at \(localAccess.instruction!)") |
| 547 | + } |
| 548 | + for end in localAccess.instruction!.endInstructions { |
| 549 | + range.insert(end) |
| 550 | + } |
| 551 | + } |
| 552 | + return .local(allocation, range) |
| 553 | + } |
| 554 | +} |
0 commit comments