//===--- SimplifyDestroyValue.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 extension DestroyValueInst : OnoneSimplifiable, SILCombineSimplifiable { func simplify(_ context: SimplifyContext) { // If the value has `.none` ownership, the destroy is a no-op. Note that a value can have `.none` // ownership even if it's type is not trivial, e.g. // // ``` // %1 = enum $NonTrivialEnum, #NonTrivialEnum.trivialCase!enumelt // ownership: none // %2 = destroy_value %1 // ``` // if destroyedValue.ownership == .none { context.erase(instruction: self) return } tryRemoveForwardingOperandInstruction(context) } /// Attempt to optimize by forwarding the destroy to operands of forwarding instructions. /// /// ``` /// %3 = struct $S (%1, %2) /// destroy_value %3 // the only use of %3 /// ``` /// -> /// ``` /// destroy_value %1 /// destroy_value %2 /// ``` /// /// The benefit of this transformation is that the forwarding instruction can be removed. /// private func tryRemoveForwardingOperandInstruction(_ context: SimplifyContext) { guard context.preserveDebugInfo ? destroyedValue.uses.isSingleUse : destroyedValue.uses.ignoreDebugUses.isSingleUse else { return } let destroyedInst: Instruction switch destroyedValue { case is StructInst, is EnumInst: if destroyedValue.type.nominal!.valueTypeDestructor != nil { // Moving the destroy to a non-copyable struct/enum's operands would drop the deinit call! return } destroyedInst = destroyedValue as! SingleValueInstruction // Handle various "forwarding" instructions that simply pass through values // without performing operations that would affect destruction semantics. // // We are intentionally _not_ handling `unchecked_enum_data`, because that would not necessarily be // a simplification, because destroying the whole enum is more effort than to destroy an enum payload. // We are also not handling `destructure_struct` and `destructure_tuple`. That would end up in // an infinite simplification loop in MandatoryPerformanceOptimizations because there we "split" such // destroys again when de-virtualizing deinits of non-copyable types. // case is TupleInst, is RefToBridgeObjectInst, is ConvertFunctionInst, is ThinToThickFunctionInst, is UpcastInst, is UncheckedRefCastInst, is UnconditionalCheckedCastInst, is BridgeObjectToRefInst, is InitExistentialRefInst, is OpenExistentialRefInst: destroyedInst = destroyedValue as! SingleValueInstruction case let arg as Argument: tryRemovePhiArgument(arg, context) return default: return } let builder = Builder(before: self, context) for op in destroyedInst.definedOperands where op.value.ownership == .owned { builder.createDestroyValue(operand: op.value) } // Users include `debug_value` instructions and this `destroy_value` context.erase(instructionIncludingAllUsers: destroyedInst) } /// Handles the optimization of `destroy_value` instructions for phi arguments. /// This is a more complex case where the destroyed value comes from different predecessors /// via a phi argument. The optimization moves the `destroy_value` to each predecessor block. /// /// ``` /// bb1: /// br bb3(%0) /// bb2: /// br bb3(%1) /// bb3(%3 : @owned T): /// ... // no deinit-barriers /// destroy_value %3 // the only use of %3 /// ``` /// -> /// ``` /// bb1: /// destroy_value %0 /// br bb3 /// bb2: /// destroy_value %1 /// br bb3 /// bb3: /// ... /// ``` /// private func tryRemovePhiArgument(_ arg: Argument, _ context: SimplifyContext) { guard let phi = Phi(arg), arg.parentBlock == parentBlock, !isDeinitBarrierInBlock(before: self, context) else { return } for incomingOp in phi.incomingOperands { let oldBranch = incomingOp.instruction as! BranchInst let builder = Builder(before: oldBranch, context) builder.createDestroyValue(operand: incomingOp.value) builder.createBranch(to: parentBlock, arguments: oldBranch.arguments(excluding: incomingOp)) context.erase(instruction: oldBranch) } // Users of `arg` include `debug_value` instructions and this `destroy_value` context.erase(instructions: arg.uses.users) arg.parentBlock.eraseArgument(at: arg.index, context) } } private func isDeinitBarrierInBlock(before instruction: Instruction, _ context: SimplifyContext) -> Bool { return ReverseInstructionList(first: instruction.previous).contains(where: { $0.isDeinitBarrier(context.calleeAnalysis) }) } private extension BranchInst { func arguments(excluding excludeOp: Operand) -> [Value] { return Array(operands.filter{ $0 != excludeOp }.values) } }