File: SimplifyRefCasts.swift

package info (click to toggle)
swiftlang 6.1.3-4
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 2,791,644 kB
  • sloc: cpp: 9,901,738; ansic: 2,201,433; asm: 1,091,827; python: 308,252; objc: 82,166; f90: 80,126; lisp: 38,358; pascal: 25,559; sh: 20,429; ml: 5,058; perl: 4,745; makefile: 4,484; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (131 lines) | stat: -rw-r--r-- 4,864 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//===--- SimplifyRefCasts.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

// Note: this simplifications are not SILCombineSimplifyable, because SILCombine has
// its own simplifications for those cast instructions which are not ported to Swift, yet.

extension CheckedCastBranchInst : OnoneSimplifyable {
  func simplify(_ context: SimplifyContext) {
    // Has only an effect if the source is an (existential) reference.
    simplifySourceOperandOfRefCast(context)
  }
}

extension UncheckedRefCastInst : OnoneSimplifyable {
  func simplify(_ context: SimplifyContext) {
    simplifySourceOperandOfRefCast(context)
  }
}

private extension UnaryInstruction {

  /// Look through `upcast` and `init_existential_ref` instructions and replace the
  /// operand of this cast instruction with the original value.
  /// For example:
  /// ```
  ///   %2 = upcast %1 : $Derived to $Base
  ///   %3 = init_existential_ref %2 : $Base : $Base, $AnyObject
  ///   checked_cast_br AnyObject in %3 : $AnyObject to Derived, bb1, bb2
  /// ```
  ///
  /// This makes it more likely that the cast can be constant folded because the source
  /// operand's type is more accurate. In the example above, the cast reduces to
  /// ```
  ///   checked_cast_br Derived in %1 : $Derived to Derived, bb1, bb2
  /// ```
  /// which can be trivially folded to always-succeeds.
  ///
  func simplifySourceOperandOfRefCast(_ context: SimplifyContext) {
    while true {
      switch operand.value {
      case let ier as InitExistentialRefInst:
        if !tryReplaceSource(withOperandOf: ier, context) {
          return
        }
      case let uc as UpcastInst:
        if !tryReplaceSource(withOperandOf: uc, context) {
          return
        }
      default:
        return
      }
    }

  }

  func tryReplaceSource(withOperandOf inst: SingleValueInstruction, _ context: SimplifyContext) -> Bool {
    let singleUse = context.preserveDebugInfo ? inst.uses.singleUse : inst.uses.ignoreDebugUses.singleUse
    let canEraseInst = singleUse?.instruction == self
    let replacement = inst.operands[0].value

    if parentFunction.hasOwnership {
      if !canEraseInst && replacement.ownership == .owned {
        // We cannot add more uses to `replacement` without inserting a copy.
        return false
      }

      operand.set(to: replacement, context)

      if let ccb = self as? CheckedCastBranchInst {
        // In OSSA, the source value is passed as block argument to the failure block.
        // We have to re-create the skipped source instruction in the failure block.
        insertCompensatingInstructions(for: inst, in: ccb.failureBlock, context)
      }
    } else {
      operand.set(to: replacement, context)
    }

    if canEraseInst {
      context.erase(instructionIncludingDebugUses: inst)
    }
    return true
  }
}

/// Compensate a removed source value instruction in the failure block.
/// For example:
/// ```
///   %inst = upcast %sourceValue : $Derived to $Base
///   checked_cast_br Base in %inst : $Base to Derived, success_block, failure_block
///   ...
/// failure_block(%oldArg : $Base):
/// ```
/// is converted to:
/// ```
///   checked_cast_br Derived in %sourceValue : $Derived to Derived, success_block, failure_block
///   ...
/// failure_block(%newArg : $Derived):
///   %3 = upcast %newArg : $Derived to $Base
/// ```
private func insertCompensatingInstructions(for inst: Instruction, in failureBlock: BasicBlock, _ context: SimplifyContext) {
  assert(failureBlock.arguments.count == 1)
  let sourceValue = inst.operands[0].value
  let newArg = failureBlock.addArgument(type: sourceValue.type, ownership: sourceValue.ownership, context)
  let builder = Builder(atBeginOf: failureBlock, context)
  let newInst: SingleValueInstruction
  switch inst {
  case let ier as InitExistentialRefInst:
    newInst = builder.createInitExistentialRef(instance: newArg,
                                               existentialType: ier.type,
                                               formalConcreteType: ier.formalConcreteType,
                                               conformances: ier.conformances)
  case let uc as UpcastInst:
    newInst = builder.createUpcast(from: newArg, to: uc.type)
  default:
    fatalError("unhandled instruction")
  }
  let oldArg = failureBlock.arguments[0]
  oldArg.uses.replaceAll(with: newInst, context)
  failureBlock.eraseArgument(at: 0, context)
}