mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
This is needed after running the SSAUpdater, because the updater can insert unnecessary phis in the middle of the original liverange of a value. Fixes an ownership error. rdar://153229472
236 lines
8.8 KiB
Swift
236 lines
8.8 KiB
Swift
//===--- LetPropertyLowering.swift -----------------------------------------==//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import SIL
|
|
|
|
/// Lowers let property accesses of classes.
|
|
///
|
|
/// Lowering consists of two tasks:
|
|
///
|
|
/// * In class initializers, insert `end_init_let_ref` instructions at places where all let-fields are initialized.
|
|
/// This strictly separates the life-range of the class into a region where let fields are still written during
|
|
/// initialization and a region where let fields are truly immutable.
|
|
///
|
|
/// * Add the `[immutable]` flag to all `ref_element_addr` instructions (for let-fields) which are in the "immutable"
|
|
/// region. This includes the region after an inserted `end_init_let_ref` in an class initializer, but also all
|
|
/// let-field accesses in other functions than the initializer and the destructor.
|
|
///
|
|
/// This pass should run after DefiniteInitialization but before RawSILInstLowering (because it relies on
|
|
/// `mark_uninitialized` still present in the class initializer).
|
|
///
|
|
/// Note that it's not mandatory to run this pass. If it doesn't run, SIL is still correct.
|
|
///
|
|
/// Simplified example (after lowering):
|
|
///
|
|
/// bb0(%0 : @owned C): // = self of the class initializer
|
|
/// %1 = mark_uninitialized %0
|
|
/// %2 = ref_element_addr %1, #C.l // a let-field
|
|
/// store %init_value to %2
|
|
/// %3 = end_init_let_ref %1 // inserted by lowering
|
|
/// %4 = ref_element_addr [immutable] %3, #C.l // set to immutable by lowering
|
|
/// %5 = load %4
|
|
///
|
|
let letPropertyLowering = FunctionPass(name: "let-property-lowering") {
|
|
(function: Function, context: FunctionPassContext) in
|
|
|
|
assert(context.silStage == .raw, "let-property-lowering must run before RawSILInstLowering")
|
|
|
|
if context.hadError {
|
|
// If DefiniteInitialization (or other passes) already reported an error, we cannot assume valid SIL anymore.
|
|
return
|
|
}
|
|
|
|
if function.isDestructor {
|
|
// Let-fields are not immutable in the class destructor.
|
|
return
|
|
}
|
|
|
|
for inst in function.instructions {
|
|
switch inst {
|
|
|
|
// First task of lowering: insert `end_init_let_ref` instructions in class initializers.
|
|
case let markUninitialized as MarkUninitializedInst
|
|
where markUninitialized.type.isClass &&
|
|
// TODO: support move-only classes
|
|
!markUninitialized.type.isMoveOnly &&
|
|
// We only have to do that for root classes because derived classes call the super-initializer
|
|
// _after_ all fields in the derived class are already initialized.
|
|
markUninitialized.kind == .rootSelf:
|
|
|
|
insertEndInitInstructions(for: markUninitialized, context)
|
|
|
|
// Second task of lowering: set the `immutable` flags.
|
|
case let rea as RefElementAddrInst
|
|
where rea.fieldIsLet && !rea.isInUninitializedRegion &&
|
|
// TODO: support move-only classes
|
|
!rea.instance.type.isMoveOnly:
|
|
rea.set(isImmutable: true, context)
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
private func insertEndInitInstructions(for markUninitialized: MarkUninitializedInst, _ context: FunctionPassContext) {
|
|
assert(!markUninitialized.type.isAddress, "self of class should not be an address")
|
|
|
|
// The region which contains all let-field initializations, including any partial
|
|
// let-field de-initializations (in case of a fail-able or throwing initializer).
|
|
var initRegion = InstructionRange(begin: markUninitialized, context)
|
|
defer { initRegion.deinitialize() }
|
|
|
|
constructLetInitRegion(of: markUninitialized, result: &initRegion, context)
|
|
|
|
insertEndInitInstructions(for: markUninitialized, atEndOf: initRegion, context)
|
|
}
|
|
|
|
private func insertEndInitInstructions(
|
|
for markUninitialized: MarkUninitializedInst,
|
|
atEndOf initRegion: InstructionRange,
|
|
_ context: FunctionPassContext
|
|
) {
|
|
var ssaUpdater = SSAUpdater(function: markUninitialized.parentFunction,
|
|
type: markUninitialized.type, ownership: .owned, context)
|
|
ssaUpdater.addAvailableValue(markUninitialized, in: markUninitialized.parentBlock)
|
|
|
|
for endInst in initRegion.ends {
|
|
let builder = Builder(after: endInst, context)
|
|
let newValue = builder.createEndInitLetRef(operand: markUninitialized)
|
|
ssaUpdater.addAvailableValue(newValue, in: endInst.parentBlock)
|
|
}
|
|
|
|
for exitInst in initRegion.exits {
|
|
let builder = Builder(before: exitInst, context)
|
|
let newValue = builder.createEndInitLetRef(operand: markUninitialized)
|
|
ssaUpdater.addAvailableValue(newValue, in: exitInst.parentBlock)
|
|
}
|
|
|
|
for use in markUninitialized.uses {
|
|
if !initRegion.inclusiveRangeContains(use.instruction) &&
|
|
!(use.instruction is EndInitLetRefInst)
|
|
{
|
|
use.set(to: ssaUpdater.getValue(atEndOf: use.instruction.parentBlock), context)
|
|
}
|
|
}
|
|
// This peephole optimization is required to avoid ownership errors.
|
|
replacePhisWithIncomingValues(phis: ssaUpdater.insertedPhis, context)
|
|
}
|
|
|
|
private func constructLetInitRegion(
|
|
of markUninitialized: MarkUninitializedInst,
|
|
result initRegion: inout InstructionRange,
|
|
_ context: FunctionPassContext
|
|
) {
|
|
// Adding the initial `mark_uninitialized` ensures that a single `end_init_let_ref` is inserted (after the
|
|
// `mark_uninitialized`) in case there are no let-field accesses at all.
|
|
// Note that we have to insert an `end_init_let_ref` even if there are no let-field initializations, because
|
|
// derived classes could have let-field initializations in their initializers (which eventually call the
|
|
// root-class initializer).
|
|
initRegion.insert(markUninitialized)
|
|
|
|
var borrows = Stack<BeginBorrowInstruction>(context)
|
|
defer { borrows.deinitialize() }
|
|
|
|
for inst in markUninitialized.parentFunction.instructions {
|
|
switch inst {
|
|
case let assign as AssignInst
|
|
where assign.destination.isLetFieldAddress(of: markUninitialized):
|
|
assert(assign.assignOwnership == .initialize)
|
|
initRegion.insert(inst)
|
|
|
|
case let store as StoreInst
|
|
where store.destination.isLetFieldAddress(of: markUninitialized):
|
|
assert(store.storeOwnership != .assign)
|
|
initRegion.insert(inst)
|
|
|
|
case let copy as CopyAddrInst
|
|
where copy.destination.isLetFieldAddress(of: markUninitialized):
|
|
assert(copy.isInitializationOfDestination)
|
|
initRegion.insert(inst)
|
|
|
|
case let beginAccess as BeginAccessInst
|
|
where beginAccess.accessKind == .deinit &&
|
|
beginAccess.address.isLetFieldAddress(of: markUninitialized):
|
|
// Include let-field partial de-initializations in the region.
|
|
initRegion.insert(inst)
|
|
|
|
case let beginBorrow as BeginBorrowInst
|
|
where beginBorrow.borrowedValue.isReferenceDerived(from: markUninitialized):
|
|
borrows.append(beginBorrow)
|
|
|
|
case let storeBorrow as StoreBorrowInst
|
|
where storeBorrow.source.isReferenceDerived(from: markUninitialized):
|
|
borrows.append(storeBorrow)
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
// Extend the region to whole borrow scopes to avoid that we insert an `end_init_let_ref` in the
|
|
// middle of a borrow scope.
|
|
for borrow in borrows where initRegion.contains(borrow) {
|
|
initRegion.insert(borrowScopeOf: borrow, context)
|
|
}
|
|
}
|
|
|
|
private extension RefElementAddrInst {
|
|
var isInUninitializedRegion: Bool {
|
|
var root = self.instance
|
|
while true {
|
|
switch root {
|
|
case let beginBorrow as BeginBorrowInst:
|
|
root = beginBorrow.borrowedValue
|
|
case let loadBorrow as LoadBorrowInst:
|
|
// Initializers of derived classes store `self` into a stack location from where
|
|
// it's loaded via a `load_borrow`.
|
|
root = loadBorrow.address
|
|
case is MarkUninitializedInst:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension Value {
|
|
func isReferenceDerived(from root: Value) -> Bool {
|
|
var parent: Value = self
|
|
while true {
|
|
if parent == root {
|
|
return true
|
|
}
|
|
if let operand = parent.forwardingInstruction?.singleForwardedOperand {
|
|
parent = operand.value
|
|
continue
|
|
}
|
|
if let transition = parent.definingInstruction as? OwnershipTransitionInstruction {
|
|
parent = transition.operand.value
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isLetFieldAddress(of markUninitialized: MarkUninitializedInst) -> Bool {
|
|
if case .class(let rea) = self.accessBase,
|
|
rea.fieldIsLet,
|
|
rea.instance.isReferenceDerived(from: markUninitialized)
|
|
{
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|