|
12 | 12 |
|
13 | 13 | import SIL
|
14 | 14 |
|
15 |
| -extension PointerToAddressInst : OnoneSimplifyable { |
| 15 | +extension PointerToAddressInst : OnoneSimplifyable, SILCombineSimplifyable { |
16 | 16 |
|
17 |
| - /// For a redundant pair of pointer-address conversions, e.g. |
18 |
| - /// |
19 |
| - /// %2 = address_to_pointer %1 |
20 |
| - /// %3 = pointer_to_address %2 [strict] |
21 |
| - /// |
22 |
| - /// replace all uses of %3 with %1. |
23 |
| - /// |
24 | 17 | func simplify(_ context: SimplifyContext) {
|
25 |
| - if let atp = self.pointer as? AddressToPointerInst, |
26 |
| - atp.address.type == self.type, |
27 |
| - self.isStrict, |
28 |
| - |
29 |
| - // If the pointer is within an ownership scope, the transformation can break ownership rules, e.g. |
30 |
| - // %2 = begin_borrow %1 |
31 |
| - // %3 = ref_tail_addr %2 |
32 |
| - // %4 = address_to_pointer %3 |
33 |
| - // end_borrow %2 |
34 |
| - // %5 = pointer_to_address %4 <- cannot replace %5 with %3! |
35 |
| - // |
36 |
| - !atp.address.accessBase.hasLocalOwnershipLifetime |
37 |
| - { |
38 |
| - self.uses.replaceAll(with: atp.address, context) |
| 18 | + if removeAddressToPointerToAddressPair(of: self, context) { |
| 19 | + return |
| 20 | + } |
| 21 | + if simplifyIndexRawPointer(of: self, context) { |
| 22 | + return |
| 23 | + } |
| 24 | + _ = optimizeAlignment(of: self, context) |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +/// Remove a redundant pair of pointer-address conversions: |
| 29 | +/// ``` |
| 30 | +/// %2 = address_to_pointer %1 |
| 31 | +/// %3 = pointer_to_address %2 [strict] |
| 32 | +/// ``` |
| 33 | +/// -> replace all uses of %3 with %1. |
| 34 | +/// |
| 35 | +private func removeAddressToPointerToAddressPair( |
| 36 | + of ptr2Addr: PointerToAddressInst, |
| 37 | + _ context: SimplifyContext |
| 38 | +) -> Bool { |
| 39 | + guard let addr2Ptr = ptr2Addr.pointer as? AddressToPointerInst, |
| 40 | + ptr2Addr.isStrict, |
| 41 | + !ptr2Addr.hasIllegalUsesAfterLifetime(of: addr2Ptr, context) |
| 42 | + else { |
| 43 | + return false |
| 44 | + } |
| 45 | + |
| 46 | + if ptr2Addr.type == addr2Ptr.address.type { |
| 47 | + ptr2Addr.replace(with: addr2Ptr.address, context) |
| 48 | + } else { |
| 49 | + let cast = Builder(before: ptr2Addr, context).createUncheckedAddrCast(from: addr2Ptr.address, to: ptr2Addr.type) |
| 50 | + ptr2Addr.replace(with: cast, context) |
| 51 | + } |
| 52 | + return true |
| 53 | +} |
| 54 | + |
| 55 | +/// Replace an `index_raw_pointer` with a manually computed stride with `index_addr`: |
| 56 | +/// ``` |
| 57 | +/// %1 = metatype $T.Type |
| 58 | +/// %2 = builtin "strideof"<T>(%1) : |
| 59 | +/// %3 = builtin "smul_with_overflow_Int64"(%idx, %2) |
| 60 | +/// %4 = tuple_extract %3, 0 |
| 61 | +/// %5 = index_raw_pointer %ptr, %4 |
| 62 | +/// %6 = pointer_to_address %5 to [strict] $*T |
| 63 | +/// ``` |
| 64 | +/// -> |
| 65 | +/// ``` |
| 66 | +/// %2 = pointer_to_address %ptr to [strict] $*T |
| 67 | +/// %3 = index_addr %2, %idx |
| 68 | +/// ``` |
| 69 | +/// |
| 70 | +private func simplifyIndexRawPointer(of ptr2Addr: PointerToAddressInst, _ context: SimplifyContext) -> Bool { |
| 71 | + guard let indexRawPtr = ptr2Addr.pointer as? IndexRawPointerInst, |
| 72 | + let tupleExtract = indexRawPtr.index.lookThroughTruncOrBitCast as? TupleExtractInst, |
| 73 | + let strideMul = tupleExtract.tuple as? BuiltinInst, strideMul.id == .SMulOver, |
| 74 | + let (index, strideType) = strideMul.indexAndStrideOfMultiplication, |
| 75 | + strideType == ptr2Addr.type.objectType |
| 76 | + else { |
| 77 | + return false |
| 78 | + } |
| 79 | + |
| 80 | + let builder = Builder(before: ptr2Addr, context) |
| 81 | + let newPtr2Addr = builder.createPointerToAddress(pointer: indexRawPtr.base, addressType: ptr2Addr.type, |
| 82 | + isStrict: ptr2Addr.isStrict, isInvariant: ptr2Addr.isInvariant) |
| 83 | + let newIndex = builder.createCastIfNeeded(of: index, toIndexTypeOf: indexRawPtr) |
| 84 | + let indexAddr = builder.createIndexAddr(base: newPtr2Addr, index: newIndex, needStackProtection: false) |
| 85 | + ptr2Addr.replace(with: indexAddr, context) |
| 86 | + return true |
| 87 | +} |
| 88 | + |
| 89 | +/// Optimize the alignment of a `pointer_to_address` based on `Builtin.assumeAlignment` |
| 90 | +/// ``` |
| 91 | +/// %1 = builtin "assumeAlignment"(%ptr, %align) |
| 92 | +/// %2 = pointer_to_address %1 to [align=1] $*T |
| 93 | +/// ``` |
| 94 | +/// -> |
| 95 | +/// ``` |
| 96 | +/// %2 = pointer_to_address %ptr to [align=8] $*T |
| 97 | +/// ``` |
| 98 | +/// or |
| 99 | +/// ``` |
| 100 | +/// %2 = pointer_to_address %ptr to $*T |
| 101 | +/// ``` |
| 102 | +/// |
| 103 | +/// The goal is to increase the alignment or to remove the attribute completely, which means that |
| 104 | +/// the resulting address is naturaly aligned to its type. |
| 105 | +/// |
| 106 | +private func optimizeAlignment(of ptr2Addr: PointerToAddressInst, _ context: SimplifyContext) -> Bool { |
| 107 | + guard let assumeAlign = ptr2Addr.pointer as? BuiltinInst, assumeAlign.id == .AssumeAlignment else { |
| 108 | + return false |
| 109 | + } |
| 110 | + |
| 111 | + if optimizeConstantAlignment(of: ptr2Addr, assumed: assumeAlign, context) { |
| 112 | + return true |
| 113 | + } |
| 114 | + return optimizeTypeAlignment(of: ptr2Addr, assumed: assumeAlign, context) |
| 115 | +} |
| 116 | + |
| 117 | +/// Optimize the alignment based on an integer literal |
| 118 | +/// ``` |
| 119 | +/// %align = integer_literal $Builtin.Int64, 16 |
| 120 | +/// %1 = builtin "assumeAlignment"(%ptr, %align) |
| 121 | +/// %2 = pointer_to_address %1 to [align=1] $*T |
| 122 | +/// ``` |
| 123 | +/// -> |
| 124 | +/// ``` |
| 125 | +/// %2 = pointer_to_address %ptr to [align=16] $*T |
| 126 | +/// ``` |
| 127 | +private func optimizeConstantAlignment( |
| 128 | + of ptr2Addr: PointerToAddressInst, |
| 129 | + assumed assumeAlign: BuiltinInst, |
| 130 | + _ context: SimplifyContext |
| 131 | +) -> Bool { |
| 132 | + guard let alignLiteral = assumeAlign.arguments[1] as? IntegerLiteralInst, |
| 133 | + let assumedAlignment = alignLiteral.value |
| 134 | + else { |
| 135 | + return false |
| 136 | + } |
| 137 | + |
| 138 | + ptr2Addr.operand.set(to: assumeAlign.arguments[0], context) |
| 139 | + |
| 140 | + if assumedAlignment == 0 { |
| 141 | + // A zero alignment means that the pointer is aligned to the natural alignment of the address type. |
| 142 | + ptr2Addr.set(alignment: nil, context) |
| 143 | + } else { |
| 144 | + if let oldAlignment = ptr2Addr.alignment, assumedAlignment <= oldAlignment { |
| 145 | + // Avoid decreasing the alignment, which would be a pessimisation. |
| 146 | + return true |
| 147 | + } |
| 148 | + ptr2Addr.set(alignment: assumedAlignment, context) |
| 149 | + } |
| 150 | + return true |
| 151 | +} |
| 152 | + |
| 153 | +/// Remove the alignment attribute if the alignment is assumed to be the natural alignment of the address type. |
| 154 | +/// ``` |
| 155 | +// %align = builtin "alignof"<T>(%0 : $@thin T.Type) |
| 156 | +/// %1 = builtin "assumeAlignment"(%ptr, %align) |
| 157 | +/// %2 = pointer_to_address %1 to [align=1] $*T |
| 158 | +/// ``` |
| 159 | +/// -> |
| 160 | +/// ``` |
| 161 | +/// %2 = pointer_to_address %ptr to $*T |
| 162 | +/// ``` |
| 163 | +private func optimizeTypeAlignment( |
| 164 | + of ptr2Addr: PointerToAddressInst, |
| 165 | + assumed assumeAlign: BuiltinInst, |
| 166 | + _ context: SimplifyContext |
| 167 | +) -> Bool { |
| 168 | + guard let alignOf = assumeAlign.arguments[1].lookThroughIntCasts as? BuiltinInst, alignOf.id == .Alignof, |
| 169 | + alignOf.alignOrStrideType == ptr2Addr.type.objectType |
| 170 | + else { |
| 171 | + return false |
| 172 | + } |
| 173 | + let pointer = assumeAlign.arguments[0] |
| 174 | + ptr2Addr.set(alignment: nil, context) |
| 175 | + ptr2Addr.operand.set(to: pointer, context) |
| 176 | + return true |
| 177 | +} |
| 178 | + |
| 179 | +private extension PointerToAddressInst { |
| 180 | + |
| 181 | + /// Checks if the `pointer_to_address` has uses outside the scope of the `baseAddress`. |
| 182 | + /// In such a case removing the `address_to_pointer`-`pointer_to_address` pair would result in |
| 183 | + /// invalid SIL. For example: |
| 184 | + /// ``` |
| 185 | + /// %1 = alloc_stack $T |
| 186 | + /// %2 = address_to_pointer %1 |
| 187 | + /// dealloc_stack %1 |
| 188 | + /// %3 = pointer_to_address %2 |
| 189 | + /// %4 = load %3 |
| 190 | + /// ``` |
| 191 | + /// or |
| 192 | + /// ``` |
| 193 | + /// %1 = begin_borrow %0 |
| 194 | + /// %2 = ref_element_addr %1, #C.x |
| 195 | + /// %3 = address_to_pointer %2 |
| 196 | + /// end_borrow %1 |
| 197 | + /// %4 = pointer_to_address %3 |
| 198 | + /// %5 = load %4 |
| 199 | + /// ``` |
| 200 | + func hasIllegalUsesAfterLifetime(of baseAddress: AddressToPointerInst, _ context: SimplifyContext) -> Bool { |
| 201 | + |
| 202 | + var lifetimeFrontier = InstructionSet(context) |
| 203 | + defer { lifetimeFrontier.deinitialize() } |
| 204 | + |
| 205 | + switch baseAddress.address.accessBase.addEndLifetimeUses(to: &lifetimeFrontier, context) { |
| 206 | + case .unknownLifetime: |
| 207 | + return true |
| 208 | + case .unlimitedLifetime: |
| 209 | + return false |
| 210 | + case .limitedLifetime: |
| 211 | + var addressUses = AddressUses(of: self, context) |
| 212 | + defer { addressUses.deinitialize() } |
| 213 | + return addressUses.hasUsesOutside(of: lifetimeFrontier, beginInstruction: baseAddress) |
| 214 | + } |
| 215 | + } |
| 216 | +} |
| 217 | + |
| 218 | +private extension AccessBase { |
| 219 | + func addEndLifetimeUses(to frontier: inout InstructionSet, _ context: SimplifyContext) -> Result { |
| 220 | + switch self { |
| 221 | + case .stack(let allocStack): |
| 222 | + frontier.insert(contentsOf: allocStack.deallocations) |
| 223 | + return .limitedLifetime |
| 224 | + case .global, .argument, .pointer: |
| 225 | + return .unlimitedLifetime |
| 226 | + case .storeBorrow(let storeBorrow): |
| 227 | + frontier.insert(contentsOf: storeBorrow.endBorrows) |
| 228 | + return .limitedLifetime |
| 229 | + default: |
| 230 | + guard let ref = reference else { |
| 231 | + return .unknownLifetime |
| 232 | + } |
| 233 | + switch ref.ownership { |
| 234 | + case .owned: |
| 235 | + frontier.insert(contentsOf: ref.uses.endingLifetime.users) |
| 236 | + return .limitedLifetime |
| 237 | + case .guaranteed: |
| 238 | + for borrowIntroducer in ref.getBorrowIntroducers(context) { |
| 239 | + frontier.insert(contentsOf: borrowIntroducer.scopeEndingOperands.users) |
| 240 | + } |
| 241 | + return .limitedLifetime |
| 242 | + case .none: |
| 243 | + // Not in an OSSA function. |
| 244 | + return .unlimitedLifetime |
| 245 | + case .unowned: |
| 246 | + return .unknownLifetime |
| 247 | + } |
| 248 | + } |
| 249 | + } |
| 250 | + |
| 251 | + enum Result { |
| 252 | + case unknownLifetime, unlimitedLifetime, limitedLifetime |
| 253 | + } |
| 254 | +} |
| 255 | + |
| 256 | +private struct AddressUses : AddressDefUseWalker { |
| 257 | + var users: InstructionWorklist |
| 258 | + |
| 259 | + init(of address: Value, _ context: SimplifyContext) { |
| 260 | + users = InstructionWorklist(context) |
| 261 | + _ = walkDownUses(ofAddress: address, path: UnusedWalkingPath()) |
| 262 | + } |
| 263 | + |
| 264 | + mutating func deinitialize() { |
| 265 | + users.deinitialize() |
| 266 | + } |
| 267 | + |
| 268 | + mutating func leafUse(address: Operand, path: UnusedWalkingPath) -> WalkResult { |
| 269 | + users.pushIfNotVisited(address.instruction) |
| 270 | + return .continueWalk |
| 271 | + } |
| 272 | + |
| 273 | + mutating func hasUsesOutside(of lifetimeFrontier: InstructionSet, beginInstruction: Instruction) -> Bool { |
| 274 | + while let inst = users.pop() { |
| 275 | + if lifetimeFrontier.contains(inst) { |
| 276 | + return true |
| 277 | + } |
| 278 | + users.pushPredecessors(of: inst, ignoring: beginInstruction) |
| 279 | + } |
| 280 | + return false |
| 281 | + } |
| 282 | +} |
| 283 | + |
| 284 | +private extension Value { |
| 285 | + var lookThroughIntCasts: Value { |
| 286 | + guard let builtin = self as? BuiltinInst else { |
| 287 | + return self |
| 288 | + } |
| 289 | + switch builtin.id { |
| 290 | + case .ZExtOrBitCast, .SExtOrBitCast, .TruncOrBitCast: |
| 291 | + return builtin.arguments[0].lookThroughIntCasts |
| 292 | + default: |
| 293 | + return self |
| 294 | + } |
| 295 | + } |
| 296 | + |
| 297 | + var lookThroughTruncOrBitCast: Value { |
| 298 | + if let truncOrBitCast = self as? BuiltinInst, truncOrBitCast.id == .TruncOrBitCast { |
| 299 | + return truncOrBitCast.arguments[0] |
| 300 | + } |
| 301 | + return self |
| 302 | + } |
| 303 | +} |
| 304 | + |
| 305 | +private extension BuiltinInst { |
| 306 | + var indexAndStrideOfMultiplication : (index: Value, strideType: Type)? { |
| 307 | + assert(id == .SMulOver) |
| 308 | + if let strideOf = arguments[0].lookThroughIntCasts as? BuiltinInst, strideOf.id == .Strideof { |
| 309 | + return (index: arguments[1], strideType: strideOf.alignOrStrideType) |
| 310 | + } |
| 311 | + if let strideOf = arguments[1].lookThroughIntCasts as? BuiltinInst, strideOf.id == .Strideof { |
| 312 | + return (index: arguments[0], strideType: strideOf.alignOrStrideType) |
| 313 | + } |
| 314 | + return nil |
| 315 | + } |
| 316 | + |
| 317 | + var alignOrStrideType: Type { |
| 318 | + substitutionMap.replacementTypes[0].loweredType(in: parentFunction) |
| 319 | + } |
| 320 | +} |
| 321 | + |
| 322 | +private extension Builder { |
| 323 | + func createCastIfNeeded(of index: Value, toIndexTypeOf indexRawPtr: IndexRawPointerInst) -> Value { |
| 324 | + if let truncOrBitCast = indexRawPtr.index as? BuiltinInst { |
| 325 | + assert(truncOrBitCast.id == .TruncOrBitCast) |
| 326 | + return createBuiltin(name: truncOrBitCast.name, type: truncOrBitCast.type, arguments: [index]) |
39 | 327 | }
|
| 328 | + return index |
40 | 329 | }
|
41 | 330 | }
|
0 commit comments