diff --git a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift index 6ffdc4015e355..b87cfbf69d7d1 100644 --- a/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift +++ b/SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift @@ -14,148 +14,138 @@ import SIL extension DestructureTupleInst : OnoneSimplifiable, SILCombineSimplifiable { func simplify(_ context: SimplifyContext) { + foldWithAggregateConstruction(context) + } +} - // If the tuple is trivial, replace - // ``` - // (%1, %2) = destructure_tuple %t - // ``` - // -> - // ``` - // %1 = tuple_extract %t, 0 - // %2 = tuple_extract %t, 1 - // ``` - // This canonicalization helps other optimizations to e.g. CSE tuple_extracts. - // - if replaceWithTupleExtract(context) { - return - } +extension DestructureStructInst : OnoneSimplifiable, SILCombineSimplifiable { + func simplify(_ context: SimplifyContext) { + foldWithAggregateConstruction(context) + } +} - // Eliminate the redundant instruction pair - // ``` - // %t = tuple (%0, %1, %2) - // (%3, %4, %5) = destructure_tuple %t - // ``` - // and replace the results %3, %4, %5 with %0, %1, %2, respectively - // - if let tuple = self.tuple as? TupleInst { - tryReplaceConstructDestructPair(construct: tuple, destruct: self, context) - } +private protocol DestructureInstruction : MultipleValueInstruction { + func createExtract(of aggregate: Value, elementIndex: Int, using builder: Builder) -> Value +} + +extension DestructureTupleInst: DestructureInstruction { + func createExtract(of aggregate: Value, elementIndex: Int, using builder: Builder) -> Value { + return builder.createTupleExtract(tuple: aggregate, elementIndex: elementIndex) } +} - private func replaceWithTupleExtract(_ context: SimplifyContext) -> Bool { - guard self.tuple.type.isTrivial(in: parentFunction) else { - return false - } - let builder = Builder(before: self, context) - for (elementIdx, result) in results.enumerated() { - let elementValue = builder.createTupleExtract(tuple: self.tuple, elementIndex: elementIdx) - result.uses.replaceAll(with: elementValue, context) - } - context.erase(instruction: self) - return true +extension DestructureStructInst: DestructureInstruction { + func createExtract(of aggregate: Value, elementIndex: Int, using builder: Builder) -> Value { + return builder.createStructExtract(struct: aggregate, fieldIndex: elementIndex) } } -extension DestructureStructInst : OnoneSimplifiable, SILCombineSimplifiable { - func simplify(_ context: SimplifyContext) { +private protocol ConstructureInstruction : SingleValueInstruction {} + +extension TupleInst: ConstructureInstruction {} +extension StructInst: ConstructureInstruction {} + +private extension DestructureInstruction { + var aggregate: Value { operands[0].value } + + func foldWithAggregateConstruction(_ context: SimplifyContext) { - // If the struct is trivial, replace - // ``` - // (%1, %2) = destructure_struct %s - // ``` - // -> - // ``` - // %1 = struct_extract %s, #S.field0 - // %2 = struct_extract %s, #S.field1 - // ``` - // This canonicalization helps other optimizations to e.g. CSE tuple_extracts. - // - if replaceWithStructExtract(context) { + if aggregate.type.isTrivial(in: parentFunction) { + // ``` + // (%1, %2) = destructure_tuple %t + // ``` + // -> + // ``` + // %1 = tuple_extract %t, 0 + // %2 = tuple_extract %t, 1 + // ``` + replaceWithAggregateExtract(context) return } - switch self.struct { - case let str as StructInst: + switch aggregate { + case let constructInst as ConstructureInstruction: // Eliminate the redundant instruction pair // ``` - // %s = struct (%0, %1, %2) - // (%3, %4, %5) = destructure_struct %s + // %t = tuple (%0, %1, %2) + // (%3, %4, %5) = destructure_tuple %t // ``` // and replace the results %3, %4, %5 with %0, %1, %2, respectively // - tryReplaceConstructDestructPair(construct: str, destruct: self, context) + tryFoldWithConstructure(constructure: constructInst, context) case let copy as CopyValueInst: // Similar to the pattern above, but with a copy_value: // Replace // ``` - // %s = struct (%0, %1, %2) - // %c = copy_value %s // can also be a chain of multiple copies - // (%3, %4, %5) = destructure_struct %c + // %t = tuple (%0, %1, %2) + // %c = copy_value %t // can also be a chain of multiple copies + // (%3, %4, %5) = destructure_tuple %c // ``` // with // ``` // %c0 = copy_value %0 // %c1 = copy_value %1 // %c2 = copy_value %2 - // %s = struct (%0, %1, %2) // keep the original struct + // %s = tuple (%0, %1, %2) // keep the original tuple/struct instruction // ``` // and replace the results %3, %4, %5 with %c0, %c1, %c2, respectively. // - // This transformation has the advantage that we can do it even if the `struct` instruction + // This transformation has the advantage that we can do it even if the `tuple`/`struct` // has other uses than the `copy_value`. // - if copy.uses.singleUse?.instruction == self, - let structInst = copy.fromValue.lookThroughCopy as? StructInst, - structInst.parentBlock == self.parentBlock - { - for (result, operand) in zip(self.results, structInst.operands) { - if operand.value.type.isTrivial(in: parentFunction) { - result.uses.replaceAll(with: operand.value, context) - } else { - let builder = Builder(before: structInst, context) - let copiedOperand = builder.createCopyValue(operand: operand.value) - result.uses.replaceAll(with: copiedOperand, context) - } - } - context.erase(instruction: self) - context.erase(instruction: copy) - } + tryFoldWithCopyOfConstructure(copy: copy, context) + default: break } } - private func replaceWithStructExtract(_ context: SimplifyContext) -> Bool { - guard self.struct.type.isTrivial(in: parentFunction) else { - return false - } + private func replaceWithAggregateExtract(_ context: SimplifyContext) { let builder = Builder(before: self, context) - for (fieldIdx, result) in results.enumerated() { - let fieldValue = builder.createStructExtract(struct: self.struct, fieldIndex: fieldIdx) - result.uses.replaceAll(with: fieldValue, context) + for (elementIdx, result) in results.enumerated() { + let elementValue = createExtract(of: aggregate, elementIndex: elementIdx, using: builder) + result.uses.replaceAll(with: elementValue, context) } context.erase(instruction: self) - return true } -} -private func tryReplaceConstructDestructPair(construct: SingleValueInstruction, - destruct: MultipleValueInstruction, - _ context: SimplifyContext) { - let singleUse = context.preserveDebugInfo ? construct.uses.singleUse : construct.uses.ignoreDebugUses.singleUse - let canEraseFirst = singleUse?.instruction == destruct + private func tryFoldWithConstructure(constructure: ConstructureInstruction, _ context: SimplifyContext) { + let singleConstructureUse = context.preserveDebugInfo ? constructure.uses.singleUse : constructure.uses.ignoreDebugUses.singleUse + let canEraseConstructure = singleConstructureUse?.instruction == self - if !canEraseFirst && construct.parentFunction.hasOwnership && construct.ownership == .owned { - // We cannot add more uses to this tuple without inserting a copy. - return - } + if !canEraseConstructure && constructure.ownership == .owned { + // We cannot add more uses to this tuple/struct without inserting a copy. + return + } + + for (result, operand) in zip(self.results, constructure.operands) { + result.uses.replaceAll(with: operand.value, context) + } - for (result, operand) in zip(destruct.results, construct.operands) { - result.uses.replaceAll(with: operand.value, context) + context.erase(instruction: self) + if canEraseConstructure { + context.erase(instructionIncludingDebugUses: constructure) + } } - if canEraseFirst { - context.erase(instructionIncludingDebugUses: destruct) + private func tryFoldWithCopyOfConstructure(copy: CopyValueInst, _ context: SimplifyContext) { + guard copy.uses.singleUse?.instruction == self, + let constructure = copy.fromValue.lookThroughCopy as? ConstructureInstruction, + constructure.parentBlock == self.parentBlock + else { + return + } + for (result, operand) in zip(self.results, constructure.operands) { + if operand.value.type.isTrivial(in: parentFunction) { + result.uses.replaceAll(with: operand.value, context) + } else { + let builder = Builder(before: constructure, context) + let copiedOperand = builder.createCopyValue(operand: operand.value) + result.uses.replaceAll(with: copiedOperand, context) + } + } + context.erase(instruction: self) + context.erase(instruction: copy) } } diff --git a/test/SILOptimizer/simplify_destructure_tuple.sil b/test/SILOptimizer/simplify_destructure_tuple.sil index bd94defc505aa..3dfadcdbe8b75 100644 --- a/test/SILOptimizer/simplify_destructure_tuple.sil +++ b/test/SILOptimizer/simplify_destructure_tuple.sil @@ -70,6 +70,77 @@ bb0(%0 : @owned $String, %1 : $Int): return %4 : $String } +// CHECK-LABEL: sil [ossa] @forward_with_copy : +// CHECK: %2 = copy_value %0 +// CHECK: %3 = tuple (%0 : $String, %1 : $Int) +// CHECK: fix_lifetime %1 +// CHECK: destroy_value %3 +// CHECK: return %2 +// CHECK: } // end sil function 'forward_with_copy' +sil [ossa] @forward_with_copy : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = tuple (%0, %1) + %3 = copy_value %2 + (%4, %5) = destructure_tuple %3 + fix_lifetime %5 + destroy_value %2 + return %4 +} + +// CHECK-LABEL: sil [ossa] @forward_with_two_copies : +// CHECK: %2 = copy_value %0 +// CHECK: %3 = tuple (%0 : $String, %1 : $Int) +// CHECK: %4 = copy_value %3 +// CHECK: fix_lifetime %1 +// CHECK: destroy_value %3 +// CHECK: destroy_value %4 +// CHECK: return %2 +// CHECK: } // end sil function 'forward_with_two_copies' +sil [ossa] @forward_with_two_copies : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = tuple (%0, %1) + %3 = copy_value %2 + %4 = copy_value %3 + (%5, %6) = destructure_tuple %4 + fix_lifetime %6 + destroy_value %2 + destroy_value %3 + return %5 : $String +} + +// CHECK-LABEL: sil [ossa] @copy_has_other_uses : +// CHECK: destructure_tuple +// CHECK: } // end sil function 'copy_has_other_uses' +sil [ossa] @copy_has_other_uses : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = tuple (%0, %1) + %3 = copy_value %2 + fix_lifetime %3 + (%5, %6) = destructure_tuple %3 + fix_lifetime %6 + destroy_value %2 + return %5 +} + +// CHECK-LABEL: sil [ossa] @different_basic_block : +// CHECK: bb2: +// CHECK: destructure_tuple +// CHECK: } // end sil function 'different_basic_block' +sil [ossa] @different_basic_block : $@convention(thin) (@owned String, Int) -> @owned String { +bb0(%0 : @owned $String, %1 : $Int): + %2 = tuple (%0, %1) + %3 = copy_value %2 + cond_br undef, bb1, bb2 +bb1: + destroy_value %3 + unreachable +bb2: + (%4, %5) = destructure_tuple %3 + fix_lifetime %5 + destroy_value %2 + return %4 +} + // CHECK-LABEL: sil [ossa] @to_tuple_extract : // CHECK: %1 = tuple_extract %0 : $(Int, Int), 0 // CHECK: %2 = tuple_extract %0 : $(Int, Int), 1