mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
Merge pull request #85814 from eeckstein/no-destroy-hoisting-in-dce
Optimizer: don't hoist destroys in DeadCodeElimination and the InstructionDeleter
This commit is contained in:
@@ -14,8 +14,147 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user