Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
71 changes: 71 additions & 0 deletions test/SILOptimizer/simplify_destructure_tuple.sil
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down