diff --git a/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift b/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift index f7695822f9051..5b49689aae750 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift @@ -36,7 +36,7 @@ struct AliasAnalysis { static func register() { BridgedAliasAnalysis.registerAnalysis( // getMemEffectsFn - { (bridgedCtxt: BridgedPassContext, bridgedVal: BridgedValue, bridgedInst: BridgedInstruction) -> swift.MemoryBehavior in + { (bridgedCtxt: BridgedPassContext, bridgedVal: BridgedValue, bridgedInst: BridgedInstruction, complexityBudget: Int) -> swift.MemoryBehavior in let context = FunctionPassContext(_bridged: bridgedCtxt) let inst = bridgedInst.instruction let val = bridgedVal.value @@ -47,7 +47,8 @@ struct AliasAnalysis { case let builtin as BuiltinInst: return getMemoryEffect(ofBuiltin: builtin, for: val, path: path, context).bridged default: - if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), context) { + if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), + complexityBudget: complexityBudget, context) { return .MayReadWrite } return .None diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt index cc124655bf1c2..9c9f7415f626d 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt @@ -17,6 +17,7 @@ swift_compiler_sources(Optimizer ObjCBridgingOptimization.swift MergeCondFails.swift NamedReturnValueOptimization.swift + RedundantLoadElimination.swift ReleaseDevirtualizer.swift SimplificationPasses.swift StackPromotion.swift diff --git a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeadStoreElimination.swift b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeadStoreElimination.swift index ba41165e9f995..e5bb1aadb804f 100644 --- a/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeadStoreElimination.swift +++ b/SwiftCompilerSources/Sources/Optimizer/FunctionPasses/DeadStoreElimination.swift @@ -88,7 +88,7 @@ private func tryEliminate(store: StoreInst, _ context: FunctionPassContext) { case .dead: // The new individual stores are inserted right after the current store and // will be optimized in the following loop iterations. - store.split(context) + store.trySplit(context) } } } @@ -158,27 +158,6 @@ private extension StoreInst { } } - func split(_ context: FunctionPassContext) { - let builder = Builder(after: self, context) - let type = source.type - if type.isStruct { - for idx in 0.. +/// store %1 to %addr +/// ... // no writes to %addr +/// // replace uses of %2 with the available value %1 +/// +/// or a preceding load from the same address: +/// +/// %1 = load %addr +/// ... // no writes to %addr +/// %2 = load %addr +/// -> +/// %1 = load %addr +/// ... // no writes to %addr +/// // replace uses of %2 with the available value %1 +/// +/// In case of a partial redundant load, the load is split so that some of the new +/// individual loads can be eliminated in the next round of the optimization: +/// +/// %fa1 = struct_element_addr %addr, #field1 +/// store %1 to %fa1 +/// ... // no writes to %fa1 +/// %2 = load %addr // partially redundant +/// -> +/// %fa1 = struct_extract %addr, #field1 +/// store %1 to %fa1 +/// ... // no writes to %fa1 +/// %fa1 = struct_element_addr %addr, #field1 +/// %f1 = load %fa1 // this load is redundant now +/// %fa2 = struct_element_addr %addr, #field2 +/// %f2 = load %fa2 +/// %2 = struct (%f1, %f2) +/// +/// The algorithm is a data flow analysis which starts at the original load and searches +/// for preceding stores or loads by following the control flow in backward direction. +/// The preceding stores and loads provide the "available values" with which the original +/// load can be replaced. +/// +/// If the function is in OSSA, redundant loads are replaced in a way that no additional +/// copies of the loaded value are introduced. If this is not possible, the redundant load +/// is not replaced. +/// +let redundantLoadElimination = FunctionPass(name: "redundant-load-elimination") { + (function: Function, context: FunctionPassContext) in + eliminateRedundantLoads(in: function, ignoreArrays: false, context) +} + +// Early RLE does not touch loads from Arrays. This is important because later array optimizations, +// like ABCOpt, get confused if an array load in a loop is converted to a pattern with a phi argument. +let earlyRedundantLoadElimination = FunctionPass(name: "early-redundant-load-elimination") { + (function: Function, context: FunctionPassContext) in + eliminateRedundantLoads(in: function, ignoreArrays: true, context) +} + +private func eliminateRedundantLoads(in function: Function, ignoreArrays: Bool, _ context: FunctionPassContext) { + for block in function.blocks.reversed() { + + // We cannot use for-in iteration here because if the load is split, the new + // individual loads are inserted right before and they would be ignored by a for-in iteration. + var inst = block.instructions.reversed().first + while let i = inst { + defer { inst = i.previous } + + if let load = inst as? LoadInst { + if !context.continueWithNextSubpassRun(for: load) { + return + } + if ignoreArrays && load.type.isNominal && load.type.nominal == context.swiftArrayDecl { + continue + } + tryEliminate(load: load, context) + } + } + } +} + +private func tryEliminate(load: LoadInst, _ context: FunctionPassContext) { + switch load.isRedundant(context) { + case .notRedundant: + break + case .redundant(let availableValues): + replace(load: load, with: availableValues, context) + case .maybePartiallyRedundant(let subPath): + // Check if the a partial load would really be redundant to avoid unnecessary splitting. + switch load.isRedundant(at: subPath, context) { + case .notRedundant, .maybePartiallyRedundant: + break + case .redundant: + // The new individual loads are inserted right before the current load and + // will be optimized in the following loop iterations. + load.trySplit(context) + } + } +} + +private extension LoadInst { + + enum DataflowResult { + case notRedundant + case redundant([AvailableValue]) + case maybePartiallyRedundant(AccessPath) + + init(notRedundantWith subPath: AccessPath?) { + if let subPath = subPath { + self = .maybePartiallyRedundant(subPath) + } else { + self = .notRedundant + } + } + } + + func isRedundant(_ context: FunctionPassContext) -> DataflowResult { + return isRedundant(at: address.accessPath, context) + } + + func isRedundant(at accessPath: AccessPath, _ context: FunctionPassContext) -> DataflowResult { + var scanner = InstructionScanner(load: self, accessPath: accessPath, context.aliasAnalysis) + + switch scanner.scan(instructions: ReverseInstructionList(first: self.previous), in: parentBlock) { + case .overwritten: + return DataflowResult(notRedundantWith: scanner.potentiallyRedundantSubpath) + case .available: + return .redundant(scanner.availableValues) + case .transparent: + return self.isRedundantInPredecessorBlocks(scanner: &scanner, context) + } + } + + private func isRedundantInPredecessorBlocks( + scanner: inout InstructionScanner, + _ context: FunctionPassContext + ) -> DataflowResult { + + var liferange = Liferange(endBlock: self.parentBlock, context) + defer { liferange.deinitialize() } + liferange.pushPredecessors(of: self.parentBlock) + + while let block = liferange.pop() { + switch scanner.scan(instructions: block.instructions.reversed(), in: block) { + case .overwritten: + return DataflowResult(notRedundantWith: scanner.potentiallyRedundantSubpath) + case .available: + liferange.add(beginBlock: block) + case .transparent: + liferange.pushPredecessors(of: block) + } + } + if !self.canReplaceWithoutInsertingCopies(liferange: liferange, context) { + return DataflowResult(notRedundantWith: scanner.potentiallyRedundantSubpath) + } + return .redundant(scanner.availableValues) + } + + func canReplaceWithoutInsertingCopies(liferange: Liferange,_ context: FunctionPassContext) -> Bool { + switch self.loadOwnership { + case .trivial, .unqualified: + return true + + case .copy, .take: + let deadEndBlocks = context.deadEndBlocks + + // The liferange of the value has an "exit", i.e. a path which doesn't lead to the load, + // it means that we would have to insert a destroy on that exit to satisfy ownership rules. + // But an inserted destroy also means that we would need to insert copies of the value which + // were not there originally. For example: + // + // store %1 to [init] %addr + // cond_br bb1, bb2 + // bb1: + // %2 = load [take] %addr + // bb2: // liferange exit + // + // TODO: we could extend OSSA to transfer ownership to support liferange exits without copying. E.g.: + // + // %b = store_and_borrow %1 to [init] %addr // %b is borrowed from %addr + // cond_br bb1, bb2 + // bb1: + // %o = borrowed_to_owned %b take_ownership_from %addr + // // replace %2 with %o + // bb2: + // end_borrow %b + // + if liferange.hasExits(deadEndBlocks) { + return false + } + + // Handle a corner case: if the load is in an infinite loop, the liferange doesn't have an exit, + // but we still would need to insert a copy. For example: + // + // store %1 to [init] %addr + // br bb1 + // bb1: + // %2 = load [copy] %addr // would need to insert a copy here + // br bb1 // no exit from the liferange + // + // For simplicity, we don't handle this in OSSA. + if deadEndBlocks.isDeadEnd(parentBlock) { + return false + } + return true + } + } +} + +private func replace(load: LoadInst, with availableValues: [AvailableValue], _ context: FunctionPassContext) { + var ssaUpdater = SSAUpdater(type: load.type, ownership: load.ownership, context) + + for availableValue in availableValues { + let block = availableValue.instruction.parentBlock + let availableValue = provideValue(for: load, from: availableValue, context) + ssaUpdater.addAvailableValue(availableValue, in: block) + } + + let newValue: Value + if availableValues.count == 1 { + // A single available value means that this available value is located _before_ the load. E.g.: + // + // store %1 to %addr // a single available value + // ... + // %2 = load %addr // The load + // + newValue = ssaUpdater.getValue(atEndOf: load.parentBlock) + } else { + // In case of multiple available values, if an available value is defined in the same basic block + // as the load, this available is located _after_ the load. E.g.: + // + // store %1 to %addr // an available value + // br bb1 + // bb1: + // %2 = load %addr // The load + // store %3 to %addr // another available value + // cond_br bb1, bb2 + // + newValue = ssaUpdater.getValue(inMiddleOf: load.parentBlock) + } + load.uses.replaceAll(with: newValue, context) + context.erase(instruction: load) +} + +private func provideValue( + for load: LoadInst, + from availableValue: AvailableValue, + _ context: FunctionPassContext +) -> Value { + let projectionPath = availableValue.address.accessPath.getMaterializableProjection(to: load.address.accessPath)! + + switch load.loadOwnership { + case .unqualified: + return availableValue.value.createProjection(path: projectionPath, + builder: availableValue.getBuilderForProjections(context)) + case .copy, .trivial: + // Note: even if the load is trivial, the available value may be projected out of a non-trivial value. + return availableValue.value.createProjectionAndCopy(path: projectionPath, + builder: availableValue.getBuilderForProjections(context)) + case .take: + if projectionPath.isEmpty { + return shrinkMemoryLifetime(from: load, to: availableValue, context) + } else { + return shrinkMemoryLifetimeAndSplit(from: load, to: availableValue, projectionPath: projectionPath, context) + } + } +} + +/// In case of a `load [take]` shrink lifetime of the value in memory back to the `availableValue` +/// and return the (possibly projected) available value. For example: +/// +/// store %1 to [assign] %addr +/// ... +/// %2 = load [take] %addr +/// -> +/// destroy_addr %addr +/// ... +/// // replace %2 with %1 +/// +private func shrinkMemoryLifetime(from load: LoadInst, to availableValue: AvailableValue, _ context: FunctionPassContext) -> Value { + switch availableValue { + case .viaLoad(let availableLoad): + assert(availableLoad.loadOwnership == .copy) + let builder = Builder(after: availableLoad, context) + availableLoad.set(ownership: .take, context) + return builder.createCopyValue(operand: availableLoad) + case .viaStore(let availableStore): + let builder = Builder(after: availableStore, context) + let valueToAdd = availableStore.source + switch availableStore.storeOwnership { + case .assign: + builder.createDestroyAddr(address: availableStore.destination) + context.erase(instruction: availableStore) + case .initialize: + context.erase(instruction: availableStore) + case .trivial, .unqualified: + fatalError("store ownership doesn't fit a later load [take]") + } + return valueToAdd + } +} + +/// Like `shrinkMemoryLifetime`, but the available value must be projected. +/// In this case we cannot just shrink the lifetime and reuse the available value. +/// Therefore, we split the available load or store and load the projected available value. +/// The inserted load can be optimized with the split value in the next iteration. +/// +/// store %1 to [assign] %addr +/// ... +/// %2 = struct_element_addr %addr, #field1 +/// %3 = load [take] %2 +/// -> +/// %f1 = struct_extract %1, #field1 +/// %fa1 = struct_element_addr %addr, #field1 +/// store %f1 to [assign] %fa1 +/// %f2 = struct_extract %1, #field2 +/// %fa2 = struct_element_addr %addr, #field2 +/// store %f2 to [assign] %fa2 +/// %1 = load [take] %fa1 // will be combined with `store %f1 to [assign] %fa1` in the next iteration +/// ... +/// // replace %3 with %1 +/// +private func shrinkMemoryLifetimeAndSplit(from load: LoadInst, to availableValue: AvailableValue, projectionPath: SmallProjectionPath, _ context: FunctionPassContext) -> Value { + switch availableValue { + case .viaLoad(let availableLoad): + assert(availableLoad.loadOwnership == .copy) + let builder = Builder(after: availableLoad, context) + let addr = availableLoad.address.createAddressProjection(path: projectionPath, builder: builder) + let valueToAdd = builder.createLoad(fromAddress: addr, ownership: .take) + availableLoad.trySplit(context) + return valueToAdd + case .viaStore(let availableStore): + let builder = Builder(after: availableStore, context) + let addr = availableStore.destination.createAddressProjection(path: projectionPath, builder: builder) + let valueToAdd = builder.createLoad(fromAddress: addr, ownership: .take) + availableStore.trySplit(context) + return valueToAdd + } +} + +/// Either a `load` or `store` which is preceding the original load and provides the loaded value. +private enum AvailableValue { + case viaLoad(LoadInst) + case viaStore(StoreInst) + + var value: Value { + switch self { + case .viaLoad(let load): return load + case .viaStore(let store): return store.source + } + } + + var address: Value { + switch self { + case .viaLoad(let load): return load.address + case .viaStore(let store): return store.destination + } + } + + var instruction: Instruction { + switch self { + case .viaLoad(let load): return load + case .viaStore(let store): return store + } + } + + func getBuilderForProjections(_ context: FunctionPassContext) -> Builder { + switch self { + case .viaLoad(let load): return Builder(after: load, context) + case .viaStore(let store): return Builder(before: store, context) + } + } +} + +private struct InstructionScanner { + private let load: LoadInst + private let accessPath: AccessPath + private let storageDefBlock: BasicBlock? + private let aliasAnalysis: AliasAnalysis + + private(set) var potentiallyRedundantSubpath: AccessPath? = nil + private(set) var availableValues = Array() + + // Avoid quadratic complexity by limiting the number of visited instructions for each store. + // The limit of 1000 instructions is not reached by far in "real-world" functions. + private var budget = 1000 + + init(load: LoadInst, accessPath: AccessPath, _ aliasAnalysis: AliasAnalysis) { + self.load = load + self.accessPath = accessPath + self.storageDefBlock = accessPath.base.reference?.referenceRoot.parentBlock + self.aliasAnalysis = aliasAnalysis + } + + enum ScanResult { + case overwritten + case available + case transparent + } + + mutating func scan(instructions: ReverseInstructionList, in block: BasicBlock) -> ScanResult { + for inst in instructions { + switch visit(instruction: inst) { + case .available: return .available + case .overwritten: return .overwritten + case .transparent: break + } + } + + // Abort if we find the storage definition of the access in case of a loop, e.g. + // + // bb1: + // %storage_root = apply + // %2 = ref_element_addr %storage_root + // %3 = load %2 + // cond_br %c, bb1, bb2 + // + // The storage root is different in each loop iteration. Therefore the load in a + // successive loop iteration does not load from the same address as in the previous iteration. + if let storageDefBlock = storageDefBlock, + block == storageDefBlock { + return .overwritten + } + if block.predecessors.isEmpty { + // We reached the function entry without finding an available value. + return .overwritten + } + return .transparent + } + + private mutating func visit(instruction: Instruction) -> ScanResult { + switch instruction { + case is FixLifetimeInst, is EndAccessInst, is BeginBorrowInst, is EndBorrowInst: + return .transparent + + case let precedingLoad as LoadInst: + if precedingLoad == load { + // We need to stop the data flow analysis when we visit the original load again. + // This happens if the load is in a loop. + return .available + } + let precedingLoadPath = precedingLoad.address.accessPath + if precedingLoadPath.getMaterializableProjection(to: accessPath) != nil { + availableValues.append(.viaLoad(precedingLoad)) + return .available + } + if accessPath.getMaterializableProjection(to: precedingLoadPath) != nil, + potentiallyRedundantSubpath == nil { + potentiallyRedundantSubpath = precedingLoadPath + } + if load.loadOwnership != .take { + return .transparent + } + + case let precedingStore as StoreInst: + if precedingStore.source is Undef { + return .overwritten + } + let precedingStorePath = precedingStore.destination.accessPath + if precedingStorePath.getMaterializableProjection(to: accessPath) != nil { + availableValues.append(.viaStore(precedingStore)) + return .available + } + if accessPath.getMaterializableProjection(to: precedingStorePath) != nil, + potentiallyRedundantSubpath == nil { + potentiallyRedundantSubpath = precedingStorePath + } + + default: + break + } + budget -= 1 + if budget == 0 { + return .overwritten + } + if load.loadOwnership == .take { + // In case of `take`, don't allow reading instructions in the liferange. + // Otherwise we cannot shrink the memory liferange afterwards. + if instruction.mayReadOrWrite(address: load.address, aliasAnalysis) { + return .overwritten + } + } else { + if instruction.mayWrite(toAddress: load.address, aliasAnalysis) { + return .overwritten + } + } + return .transparent + } +} + +/// Represents the liferange (in terms of basic blocks) of the loaded value. +/// +/// In contrast to a BlockRange, this liferange has multiple begin blocks (containing the +/// available values) and a single end block (containing the original load). For example: +/// +/// bb1: +/// store %1 to %addr // begin block +/// br bb3 +/// bb2: +/// store %2 to %addr // begin block +/// br bb3 +/// bb3: +/// %3 = load %addr // end block +/// +private struct Liferange { + private var worklist: BasicBlockWorklist + private var containingBlocks: Stack // doesn't include the end-block + private var beginBlocks: BasicBlockSet + private let endBlock: BasicBlock + + init(endBlock: BasicBlock, _ context: FunctionPassContext) { + self.worklist = BasicBlockWorklist(context) + self.containingBlocks = Stack(context) + self.beginBlocks = BasicBlockSet(context) + self.endBlock = endBlock + pushPredecessors(of: endBlock) + } + + mutating func deinitialize() { + worklist.deinitialize() + containingBlocks.deinitialize() + beginBlocks.deinitialize() + } + + mutating func pushPredecessors(of block: BasicBlock) { + worklist.pushIfNotVisited(contentsOf: block.predecessors) + containingBlocks.append(contentsOf: block.predecessors) + } + + mutating func pop() -> BasicBlock? { worklist.pop() } + + mutating func add(beginBlock: BasicBlock) { + beginBlocks.insert(beginBlock) + } + + /// Returns true if there is some path from a begin block to a function exit which doesn't + /// go through the end-block. For example: + /// + /// store %1 to %addr // begin + /// cond_br bb1, bb2 + /// bb1: + /// %2 = load %addr // end + /// bb2: + /// ... // exit + /// + func hasExits(_ deadEndBlocks: DeadEndBlocksAnalysis) -> Bool { + for block in containingBlocks { + for succ in block.successors { + if succ != endBlock, + (!worklist.hasBeenPushed(succ) || beginBlocks.contains(succ)), + !deadEndBlocks.isDeadEnd(succ) { + return true + } + } + } + return false + } +} + +private extension Value { + func createProjection(path: SmallProjectionPath, builder: Builder) -> Value { + let (kind, index, subPath) = path.pop() + switch kind { + case .root: + return self + case .structField: + let structExtract = builder.createStructExtract(struct: self, fieldIndex: index) + return structExtract.createProjection(path: subPath, builder: builder) + case .tupleField: + let tupleExtract = builder.createTupleExtract(tuple: self, elementIndex: index) + return tupleExtract.createProjection(path: subPath, builder: builder) + default: + fatalError("path is not materializable") + } + } + + func createAddressProjection(path: SmallProjectionPath, builder: Builder) -> Value { + let (kind, index, subPath) = path.pop() + switch kind { + case .root: + return self + case .structField: + let structExtract = builder.createStructElementAddr(structAddress: self, fieldIndex: index) + return structExtract.createAddressProjection(path: subPath, builder: builder) + case .tupleField: + let tupleExtract = builder.createTupleElementAddr(tupleAddress: self, elementIndex: index) + return tupleExtract.createAddressProjection(path: subPath, builder: builder) + default: + fatalError("path is not materializable") + } + } + + func createProjectionAndCopy(path: SmallProjectionPath, builder: Builder) -> Value { + if path.isEmpty { + return self.copyIfNotTrivial(builder) + } + if self.ownership == .owned { + let borrow = builder.createBeginBorrow(of: self) + let projectedValue = borrow.createProjection(path: path, builder: builder) + let result = projectedValue.copyIfNotTrivial(builder) + builder.createEndBorrow(of: borrow) + return result + } + let projectedValue = self.createProjection(path: path, builder: builder) + return projectedValue.copyIfNotTrivial(builder) + } + + func copyIfNotTrivial(_ builder: Builder) -> Value { + if type.isTrivial(in: parentFunction) { + return self + } + return builder.createCopyValue(operand: self) + } +} diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift index 04df69e858790..ed0365e69bf48 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/Context.swift @@ -145,17 +145,15 @@ extension MutatingContext { SubstitutionMap(_bridged.getContextSubstitutionMap(type.bridged)) } - // Private utilities - - fileprivate func notifyInstructionsChanged() { + func notifyInstructionsChanged() { _bridged.asNotificationHandler().notifyChanges(.instructionsChanged) } - fileprivate func notifyCallsChanged() { + func notifyCallsChanged() { _bridged.asNotificationHandler().notifyChanges(.callsChanged) } - fileprivate func notifyBranchesChanged() { + func notifyBranchesChanged() { _bridged.asNotificationHandler().notifyChanges(.branchesChanged) } } @@ -195,6 +193,10 @@ struct FunctionPassContext : MutatingContext { return PostDominatorTree(bridged: bridgedPDT) } + var swiftArrayDecl: NominalTypeDecl { + NominalTypeDecl(_bridged: _bridged.getSwiftArrayDecl()) + } + func loadFunction(name: StaticString, loadCalleesRecursively: Bool) -> Function? { return name.withUTF8Buffer { (nameBuffer: UnsafeBufferPointer) in let nameStr = llvm.StringRef(nameBuffer.baseAddress, nameBuffer.count) @@ -446,6 +448,14 @@ extension GlobalValueInst { } } +extension LoadInst { + func set(ownership: LoadInst.LoadOwnership, _ context: some MutatingContext) { + context.notifyInstructionsChanged() + bridged.LoadInst_setOwnership(ownership.rawValue) + context.notifyInstructionChanged(self) + } +} + extension TermInst { func replaceBranchTarget(from fromBlock: BasicBlock, to toBlock: BasicBlock, _ context: some MutatingContext) { context.notifyBranchesChanged() diff --git a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift index 95a5a04bf8849..060adf01abf53 100644 --- a/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift +++ b/SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift @@ -83,6 +83,8 @@ private func registerSwiftPasses() { registerPass(namedReturnValueOptimization, { namedReturnValueOptimization.run($0) }) registerPass(stripObjectHeadersPass, { stripObjectHeadersPass.run($0) }) registerPass(deadStoreElimination, { deadStoreElimination.run($0) }) + registerPass(redundantLoadElimination, { redundantLoadElimination.run($0) }) + registerPass(earlyRedundantLoadElimination, { earlyRedundantLoadElimination.run($0) }) // Instruction passes registerForSILCombine(BeginCOWMutationInst.self, { run(BeginCOWMutationInst.self, $0) }) diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/AccessUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/AccessUtils.swift index 7079dfa5f25bd..d36b732fcaa15 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/AccessUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/AccessUtils.swift @@ -28,6 +28,7 @@ // %l = load %s the access // ``` //===----------------------------------------------------------------------===// + import SIL /// AccessBase describes the base address of a memory access (e.g. of a `load` or `store``). @@ -68,7 +69,7 @@ enum AccessBase : CustomStringConvertible, Hashable { case argument(FunctionArgument) /// An indirect result of a `begin_apply`. - case yield(BeginApplyInst) + case yield(MultipleValueInstructionResult) /// An address which is derived from a `Builtin.RawPointer`. case pointer(PointerToAddressInst) @@ -86,8 +87,8 @@ enum AccessBase : CustomStringConvertible, Hashable { case let arg as FunctionArgument : self = .argument(arg) case let ga as GlobalAddrInst : self = .global(ga.global) case let mvr as MultipleValueInstructionResult: - if let ba = mvr.parentInstruction as? BeginApplyInst, baseAddress.type.isAddress { - self = .yield(ba) + if mvr.parentInstruction is BeginApplyInst && baseAddress.type.isAddress { + self = .yield(mvr) } else { self = .unidentified } @@ -105,7 +106,7 @@ enum AccessBase : CustomStringConvertible, Hashable { case .class(let rea): return "class - \(rea)" case .tail(let rta): return "tail - \(rta.instance)" case .argument(let arg): return "argument - \(arg)" - case .yield(let ba): return "yield - \(ba)" + case .yield(let result): return "yield - \(result)" case .pointer(let p): return "pointer - \(p)" } } @@ -174,14 +175,17 @@ enum AccessBase : CustomStringConvertible, Hashable { case (.class(let rea1), .class(let rea2)): return rea1.fieldIndex == rea2.fieldIndex && rea1.instance.referenceRoot == rea2.instance.referenceRoot + case (.tail(let rta1), .tail(let rta2)): + return rta1.instance.referenceRoot == rta2.instance.referenceRoot && + rta1.type == rta2.type case (.stack(let as1), .stack(let as2)): return as1 == as2 case (.global(let gl1), .global(let gl2)): return gl1 == gl2 case (.argument(let arg1), .argument(let arg2)): return arg1 == arg2 - case (.yield(let ba1), .yield(let ba2)): - return ba1 == ba2 + case (.yield(let baResult1), .yield(let baResult2)): + return baResult1 == baResult2 case (.pointer(let p1), .pointer(let p2)): return p1 == p2 default: diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/CMakeLists.txt b/SwiftCompilerSources/Sources/Optimizer/Utilities/CMakeLists.txt index 7c67e156df008..f6b3cf8728642 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/CMakeLists.txt +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/CMakeLists.txt @@ -11,5 +11,6 @@ swift_compiler_sources(Optimizer OptUtils.swift WalkUtils.swift AccessUtils.swift + SSAUpdater.swift StaticInitCloner.swift ) diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift index cf82c5b636485..07ee24f960c38 100644 --- a/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift @@ -146,6 +146,99 @@ extension Instruction { } } +extension StoreInst { + func trySplit(_ context: FunctionPassContext) { + let builder = Builder(after: self, context) + let type = source.type + if type.isStruct { + if type.nominal.isStructWithUnreferenceableStorage { + return + } + if parentFunction.hasOwnership && source.ownership != .none { + let destructure = builder.createDestructureStruct(struct: source) + for (fieldIdx, fieldValue) in destructure.results.enumerated() { + let destFieldAddr = builder.createStructElementAddr(structAddress: destination, fieldIndex: fieldIdx) + builder.createStore(source: fieldValue, destination: destFieldAddr, ownership: splitOwnership(for: fieldValue)) + } + } else { + for idx in 0.. StoreOwnership { + switch self.storeOwnership { + case .trivial, .unqualified: + return self.storeOwnership + case .assign, .initialize: + return fieldValue.type.isTrivial(in: parentFunction) ? .trivial : self.storeOwnership + } + } +} + +extension LoadInst { + func trySplit(_ context: FunctionPassContext) { + var elements = [Value]() + let builder = Builder(before: self, context) + if type.isStruct { + if type.nominal.isStructWithUnreferenceableStorage { + return + } + for idx in 0.. LoadOwnership { + switch self.loadOwnership { + case .trivial, .unqualified: + return self.loadOwnership + case .copy, .take: + return fieldValue.type.isTrivial(in: parentFunction) ? .trivial : self.loadOwnership + } + } +} + + extension UseList { var singleNonDebugUse: Operand? { var singleUse: Operand? diff --git a/SwiftCompilerSources/Sources/Optimizer/Utilities/SSAUpdater.swift b/SwiftCompilerSources/Sources/Optimizer/Utilities/SSAUpdater.swift new file mode 100644 index 0000000000000..9d0bee1cd20c2 --- /dev/null +++ b/SwiftCompilerSources/Sources/Optimizer/Utilities/SSAUpdater.swift @@ -0,0 +1,51 @@ +//===--- SSAUpdater.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 +import OptimizerBridging + +/// Utility for updating SSA for a set of SIL instructions defined in multiple blocks. +struct SSAUpdater { + let context: Context + + init(type: Type, ownership: Ownership, _ context: Context) { + self.context = context + context._bridged.SSAUpdater_initialize(type.bridged, ownership._bridged) + } + + mutating func addAvailableValue(_ value: Value, in block: BasicBlock) { + context._bridged.SSAUpdater_addAvailableValue(block.bridged, value.bridged) + } + + /// Construct SSA for a value that is live at the *end* of a basic block. + mutating func getValue(atEndOf block: BasicBlock) -> Value { + context.notifyInstructionsChanged() + return context._bridged.SSAUpdater_getValueAtEndOfBlock(block.bridged).value + } + + /// Construct SSA for a value that is live in the *middle* of a block. + /// This handles the case where the use is before a definition of the value in the same block. + /// + /// bb1: + /// %1 = def + /// br bb2 + /// bb2: + /// = use(?) + /// %2 = def + /// cond_br bb2, bb3 + /// + /// In this case we need to insert a phi argument in bb2, merging %1 and %2. + mutating func getValue(inMiddleOf block: BasicBlock) -> Value { + context.notifyInstructionsChanged() + return context._bridged.SSAUpdater_getValueInMiddleOfBlock(block.bridged).value + } +} diff --git a/SwiftCompilerSources/Sources/SIL/Builder.swift b/SwiftCompilerSources/Sources/SIL/Builder.swift index 581bf52a9ec51..aa6f0893381ed 100644 --- a/SwiftCompilerSources/Sources/SIL/Builder.swift +++ b/SwiftCompilerSources/Sources/SIL/Builder.swift @@ -157,6 +157,15 @@ public struct Builder { return notifyNew(bridged.createCopyValue(operand.bridged).getAs(CopyValueInst.self)) } + public func createBeginBorrow(of value: Value) -> BeginBorrowInst { + return notifyNew(bridged.createBeginBorrow(value.bridged).getAs(BeginBorrowInst.self)) + } + + @discardableResult + public func createEndBorrow(of beginBorrow: Value) -> EndBorrowInst { + return notifyNew(bridged.createEndBorrow(beginBorrow.bridged).getAs(EndBorrowInst.self)) + } + @discardableResult public func createCopyAddr(from fromAddr: Value, to toAddr: Value, takeSource: Bool = false, initializeDest: Bool = false) -> CopyAddrInst { @@ -169,6 +178,11 @@ public struct Builder { return notifyNew(bridged.createDestroyValue(operand.bridged).getAs(DestroyValueInst.self)) } + @discardableResult + public func createDestroyAddr(address: Value) -> DestroyAddrInst { + return notifyNew(bridged.createDestroyAddr(address.bridged).getAs(DestroyAddrInst.self)) + } + @discardableResult public func createDebugStep() -> DebugStepInst { return notifyNew(bridged.createDebugStep().getAs(DebugStepInst.self)) @@ -258,6 +272,10 @@ public struct Builder { return notifyNew(bridged.createStructElementAddr(structAddress.bridged, fieldIndex).getAs(StructElementAddrInst.self)) } + public func createDestructureStruct(struct: Value) -> DestructureStructInst { + return notifyNew(bridged.createDestructureStruct(`struct`.bridged).getAs(DestructureStructInst.self)) + } + public func createTuple(type: Type, elements: [Value]) -> TupleInst { let tuple = elements.withBridgedValues { valuesRef in return bridged.createTuple(type.bridged, valuesRef) @@ -273,6 +291,10 @@ public struct Builder { return notifyNew(bridged.createTupleElementAddr(tupleAddress.bridged, elementIndex).getAs(TupleElementAddrInst.self)) } + public func createDestructureTuple(tuple: Value) -> DestructureTupleInst { + return notifyNew(bridged.createDestructureTuple(tuple.bridged).getAs(DestructureTupleInst.self)) + } + @discardableResult public func createStore(source: Value, destination: Value, ownership: StoreInst.StoreOwnership) -> StoreInst { let store = bridged.createStore(source.bridged, destination.bridged, ownership.rawValue) diff --git a/SwiftCompilerSources/Sources/SIL/Instruction.swift b/SwiftCompilerSources/Sources/SIL/Instruction.swift index 1f14dc59a076c..c1d4d7b5da585 100644 --- a/SwiftCompilerSources/Sources/SIL/Instruction.swift +++ b/SwiftCompilerSources/Sources/SIL/Instruction.swift @@ -165,7 +165,7 @@ public class SingleValueInstruction : Instruction, Value { } } -public final class MultipleValueInstructionResult : Value { +public final class MultipleValueInstructionResult : Value, Hashable { public var parentInstruction: MultipleValueInstruction { bridged.getParent().getAs(MultipleValueInstruction.self) } @@ -179,6 +179,14 @@ public final class MultipleValueInstructionResult : Value { var bridged: BridgedMultiValueResult { BridgedMultiValueResult(obj: SwiftObject(self)) } + + public static func ==(lhs: MultipleValueInstructionResult, rhs: MultipleValueInstructionResult) -> Bool { + lhs === rhs + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } } extension BridgedMultiValueResult { diff --git a/SwiftCompilerSources/Sources/SIL/Type.swift b/SwiftCompilerSources/Sources/SIL/Type.swift index b920b71a76b91..c7fdca56ee76d 100644 --- a/SwiftCompilerSources/Sources/SIL/Type.swift +++ b/SwiftCompilerSources/Sources/SIL/Type.swift @@ -62,7 +62,7 @@ public struct Type : CustomStringConvertible, NoReflectionChildren { /// Can only be used if the type is in fact a nominal type (`isNominal` is true). public var nominal: NominalTypeDecl { - NominalTypeDecl(bridged: BridgedNominalTypeDecl(decl: bridged.getNominalOrBoundGenericNominal())) + NominalTypeDecl(_bridged: BridgedNominalTypeDecl(decl: bridged.getNominalOrBoundGenericNominal())) } public var isOrContainsObjectiveCClass: Bool { bridged.isOrContainsObjectiveCClass() } @@ -193,11 +193,19 @@ extension swift.SILType { // TODO: use an AST type for this once we have it public struct NominalTypeDecl : Equatable { - let bridged: BridgedNominalTypeDecl + private let bridged: BridgedNominalTypeDecl + + public init(_bridged: BridgedNominalTypeDecl) { + self.bridged = _bridged + } public var name: StringRef { StringRef(bridged: bridged.getName()) } public static func ==(lhs: NominalTypeDecl, rhs: NominalTypeDecl) -> Bool { lhs.bridged.decl == rhs.bridged.decl } + + public var isStructWithUnreferenceableStorage: Bool { + bridged.isStructWithUnreferenceableStorage() + } } diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index 532413467b4b4..d94f2d0e21d45 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -804,6 +804,10 @@ struct BridgedInstruction { getAs()->setBare(true); } + void LoadInst_setOwnership(SwiftInt ownership) const { + getAs()->setOwnershipQualifier((swift::LoadOwnershipQualifier)ownership); + } + SWIFT_IMPORT_UNSAFE inline BridgedBasicBlock CheckedCastBranch_getSuccessBlock() const; @@ -1164,6 +1168,16 @@ struct BridgedBuilder{ return {builder().createCopyValue(regularLoc(), op.getSILValue())}; } + SWIFT_IMPORT_UNSAFE + BridgedInstruction createBeginBorrow(BridgedValue op) const { + return {builder().createBeginBorrow(regularLoc(), op.getSILValue())}; + } + + SWIFT_IMPORT_UNSAFE + BridgedInstruction createEndBorrow(BridgedValue op) const { + return {builder().createEndBorrow(regularLoc(), op.getSILValue())}; + } + SWIFT_IMPORT_UNSAFE BridgedInstruction createCopyAddr(BridgedValue from, BridgedValue to, bool takeSource, bool initializeDest) const { @@ -1178,6 +1192,11 @@ struct BridgedBuilder{ return {builder().createDestroyValue(regularLoc(), op.getSILValue())}; } + SWIFT_IMPORT_UNSAFE + BridgedInstruction createDestroyAddr(BridgedValue op) const { + return {builder().createDestroyAddr(regularLoc(), op.getSILValue())}; + } + SWIFT_IMPORT_UNSAFE BridgedInstruction createDebugStep() const { return {builder().createDebugStep(regularLoc())}; @@ -1282,6 +1301,11 @@ struct BridgedBuilder{ return {builder().createStructElementAddr(regularLoc(), v, v->getType().getFieldDecl(fieldIndex))}; } + SWIFT_IMPORT_UNSAFE + BridgedInstruction createDestructureStruct(BridgedValue str) const { + return {builder().createDestructureStruct(regularLoc(), str.getSILValue())}; + } + SWIFT_IMPORT_UNSAFE BridgedInstruction createTuple(swift::SILType type, BridgedValueArray elements) const { llvm::SmallVector elementValues; @@ -1300,6 +1324,11 @@ struct BridgedBuilder{ return {builder().createTupleElementAddr(regularLoc(), v, elementIndex)}; } + SWIFT_IMPORT_UNSAFE + BridgedInstruction createDestructureTuple(BridgedValue str) const { + return {builder().createDestructureTuple(regularLoc(), str.getSILValue())}; + } + SWIFT_IMPORT_UNSAFE BridgedInstruction createStore(BridgedValue src, BridgedValue dst, SwiftInt ownership) const { @@ -1328,6 +1357,8 @@ struct BridgedNominalTypeDecl { llvm::StringRef getName() const { return decl->getName().str(); } + + bool isStructWithUnreferenceableStorage() const; }; // Passmanager and Context diff --git a/include/swift/SILOptimizer/Analysis/AliasAnalysis.h b/include/swift/SILOptimizer/Analysis/AliasAnalysis.h index 6cf202f8761b3..746c5121851b0 100644 --- a/include/swift/SILOptimizer/Analysis/AliasAnalysis.h +++ b/include/swift/SILOptimizer/Analysis/AliasAnalysis.h @@ -219,7 +219,7 @@ class AliasAnalysis { /// Returns true if \p Ptr may be released by the builtin \p BI. bool canBuiltinDecrementRefCount(BuiltinInst *BI, SILValue Ptr); - int getEstimatedFunctionSize(SILValue valueInFunction); + int getComplexityBudget(SILValue valueInFunction); /// Returns true if the object(s of) `obj` can escape to `toInst`. /// diff --git a/include/swift/SILOptimizer/OptimizerBridging.h b/include/swift/SILOptimizer/OptimizerBridging.h index 9de73317351aa..12adef0908c93 100644 --- a/include/swift/SILOptimizer/OptimizerBridging.h +++ b/include/swift/SILOptimizer/OptimizerBridging.h @@ -33,7 +33,7 @@ struct BridgedAliasAnalysis { } typedef swift::MemoryBehavior (* _Nonnull GetMemEffectFn)( - BridgedPassContext context, BridgedValue, BridgedInstruction); + BridgedPassContext context, BridgedValue, BridgedInstruction, SwiftInt); typedef bool (* _Nonnull Escaping2InstFn)( BridgedPassContext context, BridgedValue, BridgedInstruction); typedef bool (* _Nonnull Escaping2ValFn)( @@ -207,6 +207,12 @@ struct BridgedPassContext { return {pda->get(invocation->getFunction())}; } + SWIFT_IMPORT_UNSAFE + BridgedNominalTypeDecl getSwiftArrayDecl() const { + swift::SILModule *mod = invocation->getPassManager()->getModule(); + return {mod->getASTContext().getArrayDecl()}; + } + // SIL modifications SWIFT_IMPORT_UNSAFE @@ -452,6 +458,26 @@ struct BridgedPassContext { invocation->getTransform()); } + // SSAUpdater + + void SSAUpdater_initialize(swift::SILType type, BridgedValue::Ownership ownership) const { + invocation->initializeSSAUpdater(type, castToOwnership(ownership)); + } + + void SSAUpdater_addAvailableValue(BridgedBasicBlock block, BridgedValue value) const { + invocation->getSSAUpdater()->addAvailableValue(block.getBlock(), value.getSILValue()); + } + + SWIFT_IMPORT_UNSAFE + BridgedValue SSAUpdater_getValueAtEndOfBlock(BridgedBasicBlock block) const { + return {invocation->getSSAUpdater()->getValueAtEndOfBlock(block.getBlock())}; + } + + SWIFT_IMPORT_UNSAFE + BridgedValue SSAUpdater_getValueInMiddleOfBlock(BridgedBasicBlock block) const { + return {invocation->getSSAUpdater()->getValueInMiddleOfBlock(block.getBlock())}; + } + // Options bool enableStackProtection() const { diff --git a/include/swift/SILOptimizer/PassManager/PassManager.h b/include/swift/SILOptimizer/PassManager/PassManager.h index 6de6c648994f7..46d82e674e670 100644 --- a/include/swift/SILOptimizer/PassManager/PassManager.h +++ b/include/swift/SILOptimizer/PassManager/PassManager.h @@ -17,6 +17,7 @@ #include "swift/SILOptimizer/Analysis/Analysis.h" #include "swift/SILOptimizer/PassManager/PassPipeline.h" #include "swift/SILOptimizer/PassManager/Passes.h" +#include "swift/SILOptimizer/Utils/SILSSAUpdater.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Support/Casting.h" @@ -68,6 +69,8 @@ class SwiftPassInvocation { /// All slabs, allocated by the pass. SILModule::SlabList allocatedSlabs; + SILSSAUpdater *ssaUpdater = nullptr; + static constexpr int BlockSetCapacity = 8; char blockSetStorage[sizeof(BasicBlockSet) * BlockSetCapacity]; bool aliveBlockSets[BlockSetCapacity]; @@ -82,7 +85,7 @@ class SwiftPassInvocation { bool needFixStackNesting = false; - void endPassRunChecks(); + void endPass(); public: SwiftPassInvocation(SILPassManager *passManager, SILFunction *function, @@ -143,6 +146,17 @@ class SwiftPassInvocation { void setNeedFixStackNesting(bool newValue) { needFixStackNesting = newValue; } bool getNeedFixStackNesting() const { return needFixStackNesting; } + + void initializeSSAUpdater(SILType type, ValueOwnershipKind ownership) { + if (!ssaUpdater) + ssaUpdater = new SILSSAUpdater; + ssaUpdater->initialize(type, ownership); + } + + SILSSAUpdater *getSSAUpdater() const { + assert(ssaUpdater && "SSAUpdater not initialized"); + return ssaUpdater; + } }; /// The SIL pass manager. diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index 149edb0e71424..3a1ea6a69aa42 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -239,9 +239,9 @@ PASS(ARCSequenceOpts, "arc-sequence-opts", "ARC Sequence Optimization") PASS(ARCLoopOpts, "arc-loop-opts", "ARC Loop Optimization") -PASS(EarlyRedundantLoadElimination, "early-redundant-load-elim", +SWIFT_FUNCTION_PASS(EarlyRedundantLoadElimination, "early-redundant-load-elimination", "Early Redundant Load Elimination") -PASS(RedundantLoadElimination, "redundant-load-elim", +SWIFT_FUNCTION_PASS(RedundantLoadElimination, "redundant-load-elimination", "Redundant Load Elimination") SWIFT_FUNCTION_PASS(DeadStoreElimination, "dead-store-elimination", "Dead Store Elimination") @@ -298,8 +298,6 @@ PASS(Mem2Reg, "mem2reg", "Memory to SSA Value Conversion to Remove Stack Allocation") SWIFT_FUNCTION_PASS(MemBehaviorDumper, "dump-mem-behavior", "Print SIL Instruction MemBehavior from Alias Analysis over all Pairs") -PASS(LSLocationPrinter, "lslocation-dump", - "Print Load-Store Location Results Covering all Accesses") SWIFT_FUNCTION_PASS(MergeCondFails, "merge-cond_fails", "Merge SIL cond_fail to Eliminate Redundant Overflow Checks") PASS(MoveCondFailToPreds, "move-cond-fail-to-preds", diff --git a/include/swift/SILOptimizer/Utils/LoadStoreOptUtils.h b/include/swift/SILOptimizer/Utils/LoadStoreOptUtils.h deleted file mode 100644 index 0b0848b837e48..0000000000000 --- a/include/swift/SILOptimizer/Utils/LoadStoreOptUtils.h +++ /dev/null @@ -1,464 +0,0 @@ -//===--- LoadStoreOptUtils.h ------------------------------------*- C++ -*-===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 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 -// -//===----------------------------------------------------------------------===// -/// -/// This file defines LSBase, a class containing a SILValue base -/// and a ProjectionPath. It is used as the base class for LSLocation and -/// LSValue. -/// -/// In the case of LSLocation, the base represents the base of the allocated -/// objects and the ProjectionPath tells which field in the object the -/// LSLocation represents. -/// -/// In the case of LSValue, the base represents the root of loaded or stored -/// value it represents. And the ProjectionPath represents the field in the -/// loaded/store value the LSValue represents. -/// -//===----------------------------------------------------------------------===// - -#ifndef SWIFT_SIL_LSBASE_H -#define SWIFT_SIL_LSBASE_H - -#include "swift/SIL/InstructionUtils.h" -#include "swift/SIL/Projection.h" -#include "swift/SILOptimizer/Analysis/AliasAnalysis.h" -#include "swift/SILOptimizer/Analysis/TypeExpansionAnalysis.h" -#include "swift/SILOptimizer/Analysis/ValueTracking.h" -#include "swift/SILOptimizer/Utils/InstOptUtils.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/Hashing.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/Support/Debug.h" -#include - -namespace swift { - -class LSBase; -class LSLocation; -class LSValue; - -//===----------------------------------------------------------------------===// -// Load Store Base -//===----------------------------------------------------------------------===// - -class LSBase { -public: - enum KeyKind : uint8_t { Empty = 0, Tombstone, Normal }; - -protected: - /// The base of the object. - SILValue Base; - /// Empty key, tombstone key or normal key. - KeyKind Kind; - /// The path to reach the accessed field of the object. - llvm::Optional Path; - -public: - /// Constructors. - LSBase() : Base(), Kind(Normal) {} - LSBase(KeyKind Kind) : Base(), Kind(Kind) {} - LSBase(SILValue B) : Base(B), Kind(Normal) {} - LSBase(SILValue B, const llvm::Optional &P, - KeyKind Kind = Normal) - : Base(B), Kind(Kind), Path(P) {} - - /// Virtual destructor. - virtual ~LSBase() {} - - /// Copy constructor. - LSBase(const LSBase &RHS) { - Base = RHS.Base; - Kind = RHS.Kind; - Path = RHS.Path; - } - - /// Assignment operator. - LSBase &operator=(const LSBase &RHS) { - Base = RHS.Base; - Kind = RHS.Kind; - Path = RHS.Path; - return *this; - } - - /// Getters for LSBase. - KeyKind getKind() const { return Kind; } - SILValue getBase() const { return Base; } - const llvm::Optional &getPath() const { return Path; } - - /// Reset the LSBase, i.e. clear base and path. - void reset() { - Base = SILValue(); - Kind = Normal; - Path.reset(); - } - - /// Returns whether the LSBase has been initialized properly. - virtual bool isValid() const { return Base && Path.has_value(); } - - /// Returns true if the LSBase has a non-empty projection path. - bool hasEmptyProjectionPath() const { return !Path.value().size(); } - - /// return true if that the two objects have the same base but access different - /// fields of the base object. - bool hasNonEmptySymmetricPathDifference(const LSBase &RHS) const { - const ProjectionPath &P = RHS.Path.value(); - return Path.value().hasNonEmptySymmetricDifference(P); - } - - /// Subtract the given path from the ProjectionPath. - void removePathPrefix(llvm::Optional &P) { - if (!P.has_value()) - return; - // Remove prefix does not modify the Path in-place. - Path = ProjectionPath::removePrefix(Path.value(), P.value()); - } - - /// Return true if the RHS have identical projection paths. - /// - /// If both LSBase have empty paths, they are treated as having - /// identical projection path. - bool hasIdenticalProjectionPath(const LSBase &RHS) const { - // If both Paths have no value, then the 2 LSBases are - // different. - if (!Path.has_value() && !RHS.Path.has_value()) - return false; - // If 1 Path has value while the other does not, then the 2 - // LSBases are different. - if (Path.has_value() != RHS.Path.has_value()) - return false; - // If both Paths are empty, then the 2 LSBases are the same. - if (Path.value().empty() && RHS.Path.value().empty()) - return true; - // If both Paths have different values, then the 2 LSBases are - // different. - if (Path.value() != RHS.Path.value()) - return false; - // Otherwise, the 2 LSBases are the same. - return true; - } - - /// Comparisons. - bool operator!=(const LSBase &RHS) const { - return !(*this == RHS); - } - bool operator==(const LSBase &RHS) const { - // If type is not the same, then LSBases different. - if (Kind != RHS.Kind) - return false; - // Return true if this is a Tombstone or Empty. - if (Kind == Empty || Kind == Tombstone) - return true; - // If Base is different, then LSBases different. - if (Base != RHS.Base) - return false; - // If the projection paths are different, then LSBases are - // different. - if (!hasIdenticalProjectionPath(RHS)) - return false; - // These LSBases represent the same memory location. - return true; - } - - /// Print the LSBase. - virtual void print(llvm::raw_ostream &os) { - os << Base; - SILFunction *F = Base->getFunction(); - if (F) { - Path.value().print(os, F->getModule(), TypeExpansionContext(*F)); - } - } - - virtual void dump() { - print(llvm::dbgs()); - } -}; - -static inline llvm::hash_code hash_value(const LSBase &S) { - const SILValue Base = S.getBase(); - const llvm::Optional &Path = S.getPath(); - llvm::hash_code HC = llvm::hash_combine(Base.getOpaqueValue()); - if (!Path.has_value()) - return HC; - return llvm::hash_combine(HC, hash_value(Path.value())); -} - -//===----------------------------------------------------------------------===// -// Load Store Value -//===----------------------------------------------------------------------===// -using LSLocationValueMap = llvm::DenseMap; -using LSValueList = llvm::SmallVector; -using LSValueIndexMap = llvm::SmallDenseMap; -using ValueTableMap = llvm::SmallMapVector; - -/// A LSValue is an abstraction of an object field value in program. It -/// consists of a base that is the tracked SILValue, and a projection path to -/// the represented field. -class LSValue : public LSBase { - /// If this is a covering value, we need to go to each predecessor to - /// materialize the value. - bool CoveringValue; -public: - /// Constructors. - LSValue() : LSBase(), CoveringValue(false) {} - LSValue(KeyKind Kind) : LSBase(Kind), CoveringValue(false) {} - LSValue(bool CSVal) : LSBase(Normal), CoveringValue(CSVal) {} - LSValue(SILValue B, const ProjectionPath &P) - : LSBase(B, P), CoveringValue(false) {} - - /// Copy constructor. - LSValue(const LSValue &RHS) : LSBase(RHS) { - CoveringValue = RHS.CoveringValue; - } - - /// Assignment operator. - LSValue &operator=(const LSValue &RHS) { - LSBase::operator=(RHS); - CoveringValue = RHS.CoveringValue; - return *this; - } - - /// Comparisons. - bool operator!=(const LSValue &RHS) const { return !(*this == RHS); } - bool operator==(const LSValue &RHS) const { - if (CoveringValue && RHS.isCoveringValue()) - return true; - if (CoveringValue != RHS.isCoveringValue()) - return false; - return LSBase::operator==(RHS); - } - - /// Returns whether the LSValue has been initialized properly. - bool isValid() const override { - if (CoveringValue) - return true; - return LSBase::isValid(); - } - - /// Take the last level projection off. Return the modified LSValue. - LSValue &stripLastLevelProjection() { - Path.value().pop_back(); - return *this; - } - - /// Returns true if this LSValue is a covering value. - bool isCoveringValue() const { return CoveringValue; } - - /// Materialize the SILValue that this LSValue represents. - /// - /// In the case where we have a single value this can be materialized by - /// applying Path to the Base. - SILValue materialize(SILInstruction *Inst) { - if (CoveringValue) - return SILValue(); - if (isa(Base)) - return Base; - auto Val = Base; - auto InsertPt = getInsertAfterPoint(Base).value(); - SILBuilderWithScope Builder(InsertPt); - if (Inst->getFunction()->hasOwnership() && !Path.value().empty()) { - // We have to create a @guaranteed scope with begin_borrow in order to - // create a struct_extract in OSSA - Val = Builder.emitBeginBorrowOperation(InsertPt->getLoc(), Base); - } - auto Res = Path.value().createExtract(Val, &*InsertPt, true); - if (Val != Base) { - Res = makeCopiedValueAvailable(Res, Inst->getParent()); - Builder.emitEndBorrowOperation(InsertPt->getLoc(), Val); - // Insert a destroy on the Base - SILBuilderWithScope builder(Inst); - builder.emitDestroyValueOperation( - RegularLocation::getAutoGeneratedLocation(), Base); - } - return Res; - } - - void print(llvm::raw_ostream &os) override { - if (CoveringValue) { - os << "Covering Value"; - return; - } - LSBase::print(os); - } - - /// Expand this SILValue to all individual fields it contains. - static void expand(SILValue Base, SILModule *Mod, - TypeExpansionContext context, LSValueList &Vals, - TypeExpansionAnalysis *TE); - - /// Given a memory location and a map between the expansions of the location - /// and their corresponding values, try to come up with a single SILValue this - /// location holds. This may involve extracting and aggregating available - /// values. - static void reduceInner(LSLocation &B, SILModule *M, LSLocationValueMap &Vals, - SILInstruction *InsertPt); - static SILValue reduce(LSLocation &B, SILModule *M, LSLocationValueMap &Vals, - SILInstruction *InsertPt); -}; - -static inline llvm::hash_code hash_value(const LSValue &V) { - llvm::hash_code HC = llvm::hash_combine(V.isCoveringValue()); - if (V.isCoveringValue()) - return HC; - return llvm::hash_combine(HC, hash_value((LSBase)V)); -} - -//===----------------------------------------------------------------------===// -// Load Store Location -//===----------------------------------------------------------------------===// -using LSLocationList = llvm::SmallVector; -using LSLocationIndexMap = llvm::SmallDenseMap; -using LSLocationBaseMap = llvm::DenseMap; - -/// This class represents a field in an allocated object. It consists of a -/// base that is the tracked SILValue, and a projection path to the -/// represented field. -class LSLocation : public LSBase { -public: - /// Constructors. - LSLocation() {} - LSLocation(SILValue B, const llvm::Optional &P, - KeyKind K = Normal) - : LSBase(B, P, K) {} - LSLocation(KeyKind Kind) : LSBase(Kind) {} - /// Use the concatenation of the 2 ProjectionPaths as the Path. - LSLocation(SILValue B, const ProjectionPath &BP, const ProjectionPath &AP) - : LSBase(B) { - ProjectionPath P((*Base).getType()); - P.append(BP); - P.append(AP); - Path = P; - } - - /// Initialize a location with a new set of base, projectionpath and kind. - void init(SILValue B, const llvm::Optional &P, - KeyKind K = Normal) { - Base = B; - Path = P; - Kind = K; - } - - /// Copy constructor. - LSLocation(const LSLocation &RHS) : LSBase(RHS) {} - - /// Assignment operator. - LSLocation &operator=(const LSLocation &RHS) { - LSBase::operator=(RHS); - return *this; - } - - /// Returns the type of the object the LSLocation represents. - SILType getType(SILModule *M, TypeExpansionContext context) { - return Path.value().getMostDerivedType(*M, context); - } - - /// Get the first level locations based on this location's first level - /// projection. - void getNextLevelLSLocations(LSLocationList &Locs, SILModule *Mod, - TypeExpansionContext context); - - /// Check whether the 2 LSLocations may alias each other or not. - bool isMayAliasLSLocation(const LSLocation &RHS, AliasAnalysis *AA); - - /// Check whether the 2 LSLocations must alias each other or not. - bool isMustAliasLSLocation(const LSLocation &RHS, AliasAnalysis *AA); - - /// Expand this location to all individual fields it contains. - /// - /// In SIL, we can have a store to an aggregate and loads from its individual - /// fields. Therefore, we expand all the operations on aggregates onto - /// individual fields and process them separately. - static void expand(LSLocation Base, SILModule *Mod, - TypeExpansionContext context, LSLocationList &Locs, - TypeExpansionAnalysis *TE); - - /// Given a set of locations derived from the same base, try to merge/reduce - /// them into smallest number of LSLocations possible. - static void reduce(LSLocation Base, SILModule *Mod, - TypeExpansionContext context, LSLocationList &Locs); - - /// Gets the base address for `v`. - /// If `stopAtImmutable` is true, the base address is only calculated up to - /// a `ref_element_addr [immutable]` or a `ref_tail_addr [immutable]`. - /// Return the base address and true if such an immutable class projection - /// is found. - static std::pair - getBaseAddressOrObject(SILValue v, bool stopAtImmutable); - - /// Enumerate the given Mem LSLocation. - /// If `stopAtImmutable` is true, the base address is only calculated up to - /// a `ref_element_addr [immutable]` or a `ref_tail_addr [immutable]`. - /// Returns true if it's an immutable location. - static bool enumerateLSLocation(TypeExpansionContext context, SILModule *M, - SILValue Mem, - std::vector &LSLocationVault, - LSLocationIndexMap &LocToBit, - LSLocationBaseMap &BaseToLoc, - TypeExpansionAnalysis *TE, - bool stopAtImmutable); - - /// Enumerate all the locations in the function. - /// If `stopAtImmutable` is true, the base addresses are only calculated up to - /// a `ref_element_addr [immutable]` or a `ref_tail_addr [immutable]`. - static void enumerateLSLocations(SILFunction &F, - std::vector &LSLocationVault, - LSLocationIndexMap &LocToBit, - LSLocationBaseMap &BaseToLoc, - TypeExpansionAnalysis *TE, - bool stopAtImmutable, - int &numLoads, int &numStores, - bool &immutableLoadsFound); -}; - -static inline llvm::hash_code hash_value(const LSLocation &L) { - return llvm::hash_combine(hash_value((LSBase)L)); -} - -} // end swift namespace - -/// LSLocation and LSValue are used in DenseMap. -namespace llvm { -using swift::LSBase; -using swift::LSLocation; -using swift::LSValue; - -template <> struct DenseMapInfo { - static inline LSValue getEmptyKey() { - return LSValue(LSBase::Empty); - } - static inline LSValue getTombstoneKey() { - return LSValue(LSBase::Tombstone); - } - static inline unsigned getHashValue(const LSValue &Val) { - return hash_value(Val); - } - static bool isEqual(const LSValue &LHS, const LSValue &RHS) { - return LHS == RHS; - } -}; - -template <> struct DenseMapInfo { - static inline LSLocation getEmptyKey() { - return LSLocation(LSBase::Empty); - } - static inline LSLocation getTombstoneKey() { - return LSLocation(LSBase::Tombstone); - } - static inline unsigned getHashValue(const LSLocation &Loc) { - return hash_value(Loc); - } - static bool isEqual(const LSLocation &LHS, const LSLocation &RHS) { - return LHS == RHS; - } -}; - -} // namespace llvm - -#endif // SWIFT_SIL_LSBASE_H diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 7b1e61fcac705..72de536f4ae8c 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -81,6 +81,12 @@ SILValue swift::getUnderlyingObject(SILValue v) { v2 = stripAddressProjections(v2); v2 = stripIndexingInsts(v2); v2 = lookThroughOwnershipInsts(v2); + if (auto *ecm = dyn_cast(v2)) { + v2 = ecm->getOperand(); + } else if (auto *mvr = dyn_cast(v2)) { + if (auto *bci = dyn_cast(mvr->getParent())) + v2 = bci->getOperand(); + } if (v2 == v) return v2; v = v2; diff --git a/lib/SIL/Utils/Projection.cpp b/lib/SIL/Utils/Projection.cpp index 346ff5afc62a6..2a8e3fc99dffd 100644 --- a/lib/SIL/Utils/Projection.cpp +++ b/lib/SIL/Utils/Projection.cpp @@ -380,6 +380,15 @@ llvm::Optional ProjectionPath::getProjectionPath(SILValue Start, auto Iter = End; while (Start != Iter) { + + if (auto *mvr = dyn_cast(Iter)) { + if (auto *bci = dyn_cast(mvr->getParent())) { + Iter = bci->getOperand(); + continue; + } + break; + } + // end_cow_mutation and begin_access are not projections, but we need to be // able to form valid ProjectionPaths across them, otherwise optimization // passes like RLE/DSE cannot recognize their locations. diff --git a/lib/SIL/Utils/SILBridging.cpp b/lib/SIL/Utils/SILBridging.cpp index 77cde79413cfa..0aa1baaa36e86 100644 --- a/lib/SIL/Utils/SILBridging.cpp +++ b/lib/SIL/Utils/SILBridging.cpp @@ -288,3 +288,14 @@ bool BridgedInstruction::maySynchronizeNotConsideringSideEffects() const { bool BridgedInstruction::mayBeDeinitBarrierNotConsideringSideEffects() const { return ::mayBeDeinitBarrierNotConsideringSideEffects(getInst()); } + +//===----------------------------------------------------------------------===// +// BridgedNominalTypeDecl +//===----------------------------------------------------------------------===// + +bool BridgedNominalTypeDecl::isStructWithUnreferenceableStorage() const { + if (auto *structDecl = dyn_cast(decl)) { + return structDecl->hasUnreferenceableStorage(); + } + return false; +} diff --git a/lib/SILOptimizer/Analysis/AliasAnalysis.cpp b/lib/SILOptimizer/Analysis/AliasAnalysis.cpp index 8092c4b47889a..cd9bafb4c24f3 100644 --- a/lib/SILOptimizer/Analysis/AliasAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/AliasAnalysis.cpp @@ -718,7 +718,8 @@ MemoryBehavior AliasAnalysis::getMemoryEffectOnEscapedAddress( SILValue addr, SILInstruction *toInst) { if (getMemEffectsFunction) { return (MemoryBehavior)getMemEffectsFunction({PM->getSwiftPassInvocation()}, {addr}, - {toInst->asSILNode()}); + {toInst->asSILNode()}, + getComplexityBudget(addr)); } return MemoryBehavior::MayHaveSideEffects; } @@ -732,16 +733,8 @@ bool AliasAnalysis::isObjectReleasedByInst(SILValue obj, SILInstruction *inst) { bool AliasAnalysis::isAddrVisibleFromObject(SILValue addr, SILValue obj) { if (isAddrVisibleFromObjFunction) { - // This function is called a lot from ARCSequenceOpt and ReleaseHoisting. - // To avoid quadratic complexity for large functions, we limit the amount - // of work what the EscapeUtils are allowed to to. - // This keeps the complexity linear. - // - // This arbitrary limit is good enough for almost all functions. It lets - // the EscapeUtils do several hundred up/down walks which is much more than - // needed in most cases. - SwiftInt complexityLimit = 1000000 / getEstimatedFunctionSize(addr); - return isAddrVisibleFromObjFunction({PM->getSwiftPassInvocation()}, {addr}, {obj}, complexityLimit) != 0; + return isAddrVisibleFromObjFunction({PM->getSwiftPassInvocation()}, {addr}, {obj}, + getComplexityBudget(addr)) != 0; } return true; } @@ -753,7 +746,14 @@ bool AliasAnalysis::canReferenceSameField(SILValue lhs, SILValue rhs) { return true; } -int AliasAnalysis::getEstimatedFunctionSize(SILValue valueInFunction) { +// To avoid quadratic complexity for large functions, we limit the amount +// of work what the EscapeUtils are allowed to to. +// This keeps the complexity linear. +// +// This arbitrary limit is good enough for almost all functions. It lets +// the EscapeUtils do several hundred up/down walks which is much more than +// needed in most cases. +int AliasAnalysis::getComplexityBudget(SILValue valueInFunction) { if (estimatedFunctionSize < 0) { int numInsts = 0; SILFunction *f = valueInFunction->getFunction(); @@ -762,5 +762,5 @@ int AliasAnalysis::getEstimatedFunctionSize(SILValue valueInFunction) { } estimatedFunctionSize = numInsts; } - return estimatedFunctionSize; + return 1000000 / estimatedFunctionSize; } diff --git a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp index d1c8377abd210..f037a42c5ecf5 100644 --- a/lib/SILOptimizer/Analysis/MemoryBehavior.cpp +++ b/lib/SILOptimizer/Analysis/MemoryBehavior.cpp @@ -257,9 +257,14 @@ MemBehavior MemoryBehaviorVisitor::visitStoreInst(StoreInst *SI) { // If the store dest cannot alias the pointer in question and we are not // releasing anything due to an assign, then the specified value cannot be // modified by the store. - if (!mayAlias(SI->getDest()) && - SI->getOwnershipQualifier() != StoreOwnershipQualifier::Assign) + if (!mayAlias(SI->getDest())) { + if (SI->getOwnershipQualifier() == StoreOwnershipQualifier::Assign) { + // Consider side effects of the destructor + return AA->getMemoryEffectOnEscapedAddress(V, SI); + } + return MemBehavior::None; + } // Otherwise, a store just writes. LLVM_DEBUG(llvm::dbgs() << " Could not prove store does not alias inst. " diff --git a/lib/SILOptimizer/PassManager/PassManager.cpp b/lib/SILOptimizer/PassManager/PassManager.cpp index 63e599a03dfa0..de33cdad8615d 100644 --- a/lib/SILOptimizer/PassManager/PassManager.cpp +++ b/lib/SILOptimizer/PassManager/PassManager.cpp @@ -1360,7 +1360,7 @@ void SwiftPassInvocation::startInstructionPassRun(SILInstruction *inst) { } void SwiftPassInvocation::finishedModulePassRun() { - endPassRunChecks(); + endPass(); assert(!function && transform && "not running a pass"); assert(changeNotifications == SILAnalysis::InvalidationKind::Nothing && "unhandled change notifications at end of module pass"); @@ -1368,22 +1368,26 @@ void SwiftPassInvocation::finishedModulePassRun() { } void SwiftPassInvocation::finishedFunctionPassRun() { - endPassRunChecks(); + endPass(); endTransformFunction(); assert(allocatedSlabs.empty() && "StackList is leaking slabs"); transform = nullptr; } void SwiftPassInvocation::finishedInstructionPassRun() { - endPassRunChecks(); + endPass(); } -void SwiftPassInvocation::endPassRunChecks() { +void SwiftPassInvocation::endPass() { assert(allocatedSlabs.empty() && "StackList is leaking slabs"); assert(numBlockSetsAllocated == 0 && "Not all BasicBlockSets deallocated"); assert(numNodeSetsAllocated == 0 && "Not all NodeSets deallocated"); assert(numClonersAllocated == 0 && "Not all cloners deallocated"); assert(!needFixStackNesting && "Stack nesting not fixed"); + if (ssaUpdater) { + delete ssaUpdater; + ssaUpdater = nullptr; + } } void SwiftPassInvocation::beginTransformFunction(SILFunction *function) { diff --git a/lib/SILOptimizer/Transforms/CMakeLists.txt b/lib/SILOptimizer/Transforms/CMakeLists.txt index 8c60439433892..36b8b2c09fd25 100644 --- a/lib/SILOptimizer/Transforms/CMakeLists.txt +++ b/lib/SILOptimizer/Transforms/CMakeLists.txt @@ -26,7 +26,6 @@ target_sources(swiftSILOptimizer PRIVATE PerformanceInliner.cpp PhiArgumentOptimizations.cpp PruneVTables.cpp - RedundantLoadElimination.cpp RedundantOverflowCheckRemoval.cpp SILCodeMotion.cpp SILLowerAggregateInstrs.cpp diff --git a/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp b/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp deleted file mode 100644 index 96cd50881410e..0000000000000 --- a/lib/SILOptimizer/Transforms/RedundantLoadElimination.cpp +++ /dev/null @@ -1,1746 +0,0 @@ -//===--- RedundantLoadElimination.cpp - SIL Load Forwarding ---------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 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 -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// -/// This pass eliminates redundant loads. -/// -/// A load can be eliminated if its value has already been held somewhere, -/// i.e. generated by a previous load, LSLocation stored by a known value. -/// -/// In this case, one can replace the load instruction with the previous -/// results. -/// -/// Redundant Load Elimination (RLE) eliminates such loads by: -/// -/// 1. Introducing a notion of a LSLocation that is used to model object -/// fields. (See below for more details). -/// -/// 2. Introducing a notion of a LSValue that is used to model the value -/// that currently resides in the associated LSLocation on the particular -/// program path. (See below for more details). -/// -/// 3. Performing a RPO walk over the control flow graph, tracking any -/// LSLocations that are read from or stored into in each basic block. The -/// read or stored value, kept in a map between LSLocation and LSValue, -/// becomes the available value for the LSLocation. -/// -/// 4. An optimistic iterative intersection-based dataflow is performed on the -/// gensets until convergence. -/// -/// At the core of RLE, there is the LSLocation class. A LSLocation is an -/// abstraction of an object field in program. It consists of a base and a -/// projection path to the field accessed. -/// -/// In SIL, one can access an aggregate as a whole, i.e. store to a struct with -/// 2 Int fields. A store like this will generate 2 *indivisible* LSLocations, -/// 1 for each field and in addition to keeping a list of LSLocation, RLE also -/// keeps their available LSValues. We call it *indivisible* because it -/// cannot be broken down to more LSLocations. -/// -/// LSValue consists of a base - a SILValue from the load or store inst, -/// as well as a projection path to which the field it represents. So, a -/// store to an 2-field struct as mentioned above will generate 2 LSLocations -/// and 2 LSValues. -/// -/// Every basic block keeps a map between LSLocation and LSValue. By -/// keeping the LSLocation and LSValue in their indivisible form, one -/// can easily find which part of the load is redundant and how to compute its -/// forwarding value. -/// -/// Given the case which the 2 fields of the struct both have available values, -/// RLE can find their LSValues (maybe by struct_extract from a larger -/// value) and then aggregate them. -/// -/// However, this may introduce a lot of extraction and aggregation which may -/// not be necessary. i.e. a store the struct followed by a load from the -/// struct. To solve this problem, when RLE detects that a load instruction -/// can be replaced by forwarded value, it will try to find minimum # of -/// extractions necessary to form the forwarded value. It will group the -/// available value's by the LSValue base, i.e. the LSValues come from the -/// same instruction, and then use extraction to obtain the needed components -/// of the base. -/// -//===----------------------------------------------------------------------===// - -#define DEBUG_TYPE "sil-redundant-load-elim" -#include "swift/SIL/Projection.h" -#include "swift/SIL/SILArgument.h" -#include "swift/SIL/SILBuilder.h" -#include "swift/SIL/BasicBlockDatastructures.h" -#include "swift/SILOptimizer/Analysis/ARCAnalysis.h" -#include "swift/SILOptimizer/Analysis/AliasAnalysis.h" -#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" -#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" -#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" -#include "swift/SILOptimizer/Analysis/ValueTracking.h" -#include "swift/SILOptimizer/PassManager/Passes.h" -#include "swift/SILOptimizer/PassManager/Transforms.h" -#include "swift/SILOptimizer/Utils/CFGOptUtils.h" -#include "swift/SILOptimizer/Utils/InstOptUtils.h" -#include "swift/SILOptimizer/Utils/LoadStoreOptUtils.h" -#include "swift/SILOptimizer/Utils/SILSSAUpdater.h" -#include "swift/SIL/BasicBlockData.h" -#include "llvm/ADT/BitVector.h" -#include "llvm/ADT/MapVector.h" -#include "llvm/ADT/None.h" -#include "llvm/ADT/Statistic.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Debug.h" - -using namespace swift; - -STATISTIC(NumForwardedLoads, "Number of loads forwarded"); - -static AllocStackInst *findAllocStackInst(DeallocStackInst *I) { - // It's allowed to be undef in unreachable code. - return dyn_cast(I->getOperand()); -} - -/// ComputeAvailSetMax - If we ignore all unknown writes, what is the max -/// available set that can reach the a certain point in a basic block. This -/// helps generating the genset and killset. i.e. if there is no downward visible -/// value that can reach the end of a basic block, then we know that the genset -/// and killset for the location need not be set. -/// -/// ComputeAvailGenKillSet - Build the genset and killset of the basic block. -/// -/// ComputeAvailValue - Compute the available value at the end of the basic -/// block. -/// -/// PerformRLE - Perform the actual redundant load elimination. -enum class RLEKind : unsigned { - ComputeAvailSetMax = 0, - ComputeAvailGenKillSet = 1, - ComputeAvailValue = 2, - PerformRLE = 3, -}; - -//===----------------------------------------------------------------------===// -// Utility Functions -//===----------------------------------------------------------------------===// - -static bool inline isComputeAvailSetMax(RLEKind Kind) { - return Kind == RLEKind::ComputeAvailSetMax; -} - -static bool inline isComputeAvailGenKillSet(RLEKind Kind) { - return Kind == RLEKind::ComputeAvailGenKillSet; -} - -static bool inline isComputeAvailValue(RLEKind Kind) { - return Kind == RLEKind::ComputeAvailValue; -} - -static bool inline isPerformingRLE(RLEKind Kind) { - return Kind == RLEKind::PerformRLE; -} - -/// Returns true if this is an instruction that may have side effects in a -/// general sense but are inert from a load store perspective. -static bool isRLEInertInstruction(SILInstruction *Inst) { - switch (Inst->getKind()) { - case SILInstructionKind::DeallocStackInst: - case SILInstructionKind::CondFailInst: - case SILInstructionKind::IsEscapingClosureInst: - case SILInstructionKind::EndCOWMutationInst: - case SILInstructionKind::FixLifetimeInst: - case SILInstructionKind::EndAccessInst: - case SILInstructionKind::SetDeallocatingInst: - case SILInstructionKind::DeallocStackRefInst: - case SILInstructionKind::DeallocRefInst: - case SILInstructionKind::BeginBorrowInst: - case SILInstructionKind::EndBorrowInst: - return true; - case SILInstructionKind::IsUniqueInst: - // TODO: Consider is_unique to be a barrier for optimization. - return !Inst->getFunction()->hasOwnership(); - default: - return false; - } -} - -//===----------------------------------------------------------------------===// -// Basic Block Location State -//===----------------------------------------------------------------------===// -namespace { - -/// If this function has too many basic blocks or too many locations, it may -/// take a long time to compute the genset and killset. The number of memory -/// behavior or alias query we need to do in worst case is roughly linear to -/// # of BBs x(times) # of locations. -/// -/// we could run RLE on functions with 128 basic blocks and 128 locations, -/// which is a large function. -constexpr unsigned MaxLSLocationBBMultiplicationNone = 128*128; - -/// we could run optimistic RLE on functions with less than 64 basic blocks -/// and 64 locations which is a sizable function. -constexpr unsigned MaxLSLocationBBMultiplicationPessimistic = 64*64; - -/// forward declaration. -class RLEContext; - -/// State of the load store in one basic block which allows for forwarding from -/// loads, stores -> loads -class BlockState { -public: - enum class ValueState : unsigned { - CoverValues = 0, - ConcreteValues = 1, - CoverAndConcreteValues = 2, - }; - -private: - /// # of locations in the LocationVault. - unsigned LocationNum; - - /// The basic block that we are optimizing. - SILBasicBlock *BB; - - /// A bit vector for which the ith bit represents the ith LSLocation in - /// LocationVault. If the bit is set, then the location currently has an - /// downward visible value at the beginning of the basic block. - SmallBitVector ForwardSetIn; - - /// A bit vector for which the ith bit represents the ith LSLocation in - /// LocationVault. If the bit is set, then the location currently has an - /// downward visible value at the end of the basic block. - SmallBitVector ForwardSetOut; - - /// A bit vector for which the ith bit represents the ith LSLocation in - /// LocationVault. If we ignore all unknown write, what's the maximum set - /// of available locations at the current position in the basic block. - SmallBitVector ForwardSetMax; - - /// A bit vector for which the ith bit represents the ith LSLocation in - /// LocationVault. If the bit is set, then the basic block generates a - /// value for the location. - SmallBitVector BBGenSet; - - /// A bit vector for which the ith bit represents the ith LSLocation in - /// LocationVault. If the bit is set, then the basic block kills the - /// value for the location. - SmallBitVector BBKillSet; - - /// This is map between LSLocations and their available values at the - /// beginning of this basic block. - ValueTableMap ForwardValIn; - - /// This is map between LSLocations and their available values at the end of - /// this basic block. - ValueTableMap ForwardValOut; - - /// Keeps a list of replaceable instructions in the current basic block as - /// well as their SILValue replacement. - llvm::DenseMap RedundantLoads; - - /// LSLocation read or written has been extracted, expanded and mapped to the - /// bit position in the bitvector. Update it in the ForwardSetIn of the - /// current basic block. - void updateForwardSetForRead(RLEContext &Ctx, unsigned B); - void updateForwardSetForWrite(RLEContext &Ctx, unsigned B); - - /// LSLocation read or written has been extracted, expanded and mapped to the - /// B position in the Bvector. Update it in the genset and killset of the - /// current basic block. - void updateGenKillSetForRead(RLEContext &Ctx, unsigned B); - void updateGenKillSetForWrite(RLEContext &Ctx, unsigned B); - - /// LSLocation read or written has been extracted, expanded and mapped to the - /// B position in the Bvector. Update it in the MaxAvailForwardSet of the - /// current basic block. - void updateMaxAvailSetForRead(RLEContext &Ctx, unsigned B); - void updateMaxAvailSetForWrite(RLEContext &Ctx, unsigned B); - - /// LSLocation written has been extracted, expanded and mapped to the bit - /// position in the bitvector. process it using the bit position. - void updateForwardSetAndValForRead(RLEContext &Ctx, unsigned L, unsigned V); - void updateForwardSetAndValForWrite(RLEContext &Ctx, unsigned L, unsigned V); - - /// There is a read to a LSLocation, expand the LSLocation into individual - /// fields before processing them. - void processRead(RLEContext &Ctx, SILInstruction *I, SILValue Mem, - SILValue Val, RLEKind Kind); - - /// There is a write to a LSLocation, expand the LSLocation into individual - /// fields before processing them. - void processWrite(RLEContext &Ctx, SILInstruction *I, SILValue Mem, - SILValue Val, RLEKind Kind); - - /// BitVector manipulation functions. - void startTrackingLocation(SmallBitVector &BV, unsigned B); - void stopTrackingLocation(SmallBitVector &BV, unsigned B); - bool isTrackingLocation(SmallBitVector &BV, unsigned B); - void startTrackingValue(ValueTableMap &VM, unsigned L, unsigned V); - void stopTrackingValue(ValueTableMap &VM, unsigned B); - -public: - BlockState() = default; - - void init(SILBasicBlock *NewBB, unsigned bitcnt, bool optimistic) { - BB = NewBB; - LocationNum = bitcnt; - // For reachable basic blocks, the initial state of ForwardSetOut should be - // all 1's. Otherwise the dataflow solution could be too conservative. - // - // Consider this case, the forwardable value by var a = 10 before the loop - // will not be forwarded if the ForwardSetOut is set to 0 initially. - // - // var a = 10 - // for _ in 0...1024 {} - // use(a); - // - // However, by doing so, we can only do the data forwarding after the - // data flow stabilizes. - // - // We set the initial state of unreachable block to 0, as we do not have - // a value for the location. - // - // This is a bit conservative as we could be missing forwarding - // opportunities. i.e. a joint block with 1 predecessor being an - // unreachable block. - // - // we rely on other passes to clean up unreachable block. - ForwardSetIn.resize(LocationNum, false); - ForwardSetOut.resize(LocationNum, optimistic); - - // If we are running an optimistic data flow, set forward max to true - // initially. - ForwardSetMax.resize(LocationNum, optimistic); - - BBGenSet.resize(LocationNum, false); - BBKillSet.resize(LocationNum, false); - } - - /// Initialize the AvailSetMax by intersecting this basic block's - /// predecessors' AvailSetMax. - void mergePredecessorsAvailSetMax(RLEContext &Ctx); - - /// Initialize the AvailSet by intersecting this basic block' predecessors' - /// AvailSet. - void mergePredecessorAvailSet(RLEContext &Ctx); - - /// Initialize the AvailSet and AvailVal of the current basic block. - void mergePredecessorAvailSetAndValue(RLEContext &Ctx); - - /// Reached the end of the basic block, update the ForwardValOut with the - /// ForwardValIn. - void updateForwardValOut() { ForwardValOut = ForwardValIn; } - - /// Check whether the ForwardSetOut has changed. If it does, we need to - /// rerun the data flow to reach fixed point. - bool updateForwardSetOut() { - if (ForwardSetIn == ForwardSetOut) - return false; - ForwardSetOut = ForwardSetIn; - return true; - } - - /// Returns the current basic block we are processing. - SILBasicBlock *getBB() const { return BB; } - - /// Returns the ForwardValIn for the current basic block. - ValueTableMap &getForwardValIn() { return ForwardValIn; } - - /// Returns the ForwardValOut for the current basic block. - ValueTableMap &getForwardValOut() { return ForwardValOut; } - - /// Returns the redundant loads and their replacement in the currently basic - /// block. - llvm::DenseMap &getRL() { - return RedundantLoads; - } - - /// Look into the value for the given LSLocation at end of the basic block, - /// return one of the three ValueState type. - ValueState getValueStateAtEndOfBlock(RLEContext &Ctx, LSLocation &L); - - /// Wrappers to query the value state of the location in this BlockState. - bool isCoverValues(RLEContext &Ctx, LSLocation &L) { - return getValueStateAtEndOfBlock(Ctx, L) == ValueState::CoverValues; - } - bool isConcreteValues(RLEContext &Ctx, LSLocation &L) { - return getValueStateAtEndOfBlock(Ctx, L) == ValueState::ConcreteValues; - } - - /// Iterate over the instructions in the basic block in forward order and - /// process them w.r.t. the given \p Kind. - void processInstructionWithKind(RLEContext &Ctx, SILInstruction *I, - RLEKind Kind); - void processBasicBlockWithKind(RLEContext &Ctx, RLEKind Kind); - - /// Process the current basic block with the genset and killset. Return true - /// if the ForwardSetOut changes. - bool processBasicBlockWithGenKillSet(); - - /// Set up the value for redundant load elimination. - bool setupRLE(RLEContext &Ctx, SILInstruction *I, SILValue Mem); - - /// Process Instruction which writes to memory in an unknown way. - void processUnknownWriteInst(RLEContext &Ctx, SILInstruction *I, - RLEKind Kind); - void processUnknownWriteInstForGenKillSet(RLEContext &Ctx, SILInstruction *I); - void processUnknownWriteInstForRLE(RLEContext &Ctx, SILInstruction *I); - - - void processDeallocStackInst(RLEContext &Ctx, DeallocStackInst *I, - RLEKind Kind); - void processDeallocStackInstForGenKillSet(RLEContext &Ctx, DeallocStackInst *I); - void processDeallocStackInstForRLE(RLEContext &Ctx, DeallocStackInst *I); - - /// Process LoadInst. Extract LSLocations from LoadInst. - void processLoadInst(RLEContext &Ctx, LoadInst *LI, RLEKind Kind); - - /// Process LoadInst. Extract LSLocations from StoreInst. - void processStoreInst(RLEContext &Ctx, StoreInst *SI, RLEKind Kind); - - /// Returns a *single* forwardable SILValue for the given LSLocation right - /// before the InsertPt instruction. - SILValue reduceValuesAtEndOfBlock(RLEContext &Ctx, LSLocation &L); - -#ifndef NDEBUG - void dump(RLEContext &Ctx); -#endif -}; - -} // end anonymous namespace - -//===----------------------------------------------------------------------===// -// RLEContext Interface -//===----------------------------------------------------------------------===// - -namespace { - -/// This class stores global state that we use when computing redundant load and -/// their replacement in each basic block. -class RLEContext { - enum class ProcessKind { - ProcessMultipleIterations = 0, - ProcessOneIteration = 1, - ProcessNone = 2, - }; -private: - /// Function currently processing. - SILFunction *Fn; - - SILFunctionTransform *parentTransform; - - /// The alias analysis that we will use during all computations. - AliasAnalysis *AA; - - /// The type expansion analysis we will use during all computations. - TypeExpansionAnalysis *TE; - - /// The SSA updater we use to materialize covering values. - SILSSAUpdater Updater; - - /// The range that we use to iterate over the post order and reverse post - /// order of the given function. - PostOrderFunctionInfo *PO; - - /// Epilogue release analysis. - EpilogueARCFunctionInfo *EAFI; - - /// Keeps all the locations for the current function. The BitVector in each - /// BlockState is then laid on top of it to keep track of which LSLocation - /// has a downward available value. - std::vector LocationVault; - - /// Contains a map between LSLocation to their index in the LocationVault. - /// Use for fast lookup. - LSLocationIndexMap LocToBitIndex; - - /// Keeps a map between the accessed SILValue and the location. - LSLocationBaseMap BaseToLocIndex; - - /// Keeps all the loadstorevalues for the current function. The BitVector in - /// each g is then laid on top of it to keep track of which LSLocation - /// has a downward available value. - std::vector LSValueVault; - - /// Contains a map between LSLocation to their index in the LocationVault. - /// Use for fast lookup. - llvm::DenseMap ValToBitIndex; - - /// A map from each BasicBlock to its BlockState. - BasicBlockData BBToLocState; - - /// Keeps a list of basic blocks that have LoadInsts. If a basic block does - /// not have LoadInst, we do not actually perform the last iteration where - /// RLE is actually performed on the basic block. - /// - /// NOTE: This is never populated for functions which will only require 1 - /// data flow iteration. For function that requires more than 1 iteration of - /// the data flow this is populated when the first time the functions is - /// walked, i.e. when the we generate the genset and killset. - BasicBlockSet BBWithLoads; - - /// If set, RLE ignores loads from that array type. - NominalTypeDecl *ArrayType; - - /// Se to true if loads with a `ref_element_addr [immutable]` or - /// `ref_tail_addr [immutable]` base address are found. - bool immutableLoadsFound = false; - - /// Only optimize loads with a base address of `ref_element_addr [immutable]` - /// `ref_tail_addr [immutable]`. - bool onlyImmutableLoads; - -#ifndef NDEBUG - SILPrintContext printCtx; -#endif - -public: - RLEContext(SILFunction *F, SILFunctionTransform *parentTransform, - bool disableArrayLoads, bool onlyImmutableLoads); - - RLEContext(const RLEContext &) = delete; - RLEContext(RLEContext &&) = delete; - RLEContext &operator=(const RLEContext &) = delete; - RLEContext &operator=(RLEContext &&) = delete; - ~RLEContext() = default; - - /// Entry point to redundant load elimination. - bool run(); - - bool shouldOptimizeImmutableLoads() const { return immutableLoadsFound; } - - SILFunction *getFunction() const { return Fn; } - - /// Use a set of ad hoc rules to tell whether we should run a pessimistic - /// one iteration data flow on the function. - ProcessKind getProcessFunctionKind(unsigned LoadCount, unsigned StoreCount); - - /// Run the iterative data flow until convergence. - void runIterativeRLE(); - - /// Process the basic blocks for the gen and kill set. - void processBasicBlocksForGenKillSet(); - - /// Process the basic blocks with the gen and kill set. - void processBasicBlocksWithGenKillSet(); - - /// Process the basic block for values generated in the current basic - /// block. - void processBasicBlocksForAvailValue(); - - /// Process basic blocks to perform the redundant load elimination. - void processBasicBlocksForRLE(bool Optimistic); - - /// Returns the alias analysis we will use during all computations. - AliasAnalysis *getAA() const { return AA; } - - /// Returns the current type expansion analysis we are . - TypeExpansionAnalysis *getTE() const { return TE; } - - /// Returns current epilogue release function info we are using. - EpilogueARCFunctionInfo *getEAFI() const { return EAFI; } - - /// Returns the SILValue base to bit index. - LSLocationBaseMap &getBM() { return BaseToLocIndex; } - - /// Return the BlockState for the basic block this basic block belongs to. - BlockState &getBlockState(SILBasicBlock *B) { return BBToLocState[B]; } - - /// Get the bit representing the LSLocation in the LocationVault. - unsigned getLocationBit(const LSLocation &L); - - /// Given the bit, get the LSLocation from the LocationVault. - LSLocation &getLocation(const unsigned index); - - /// Get the bit representing the LSValue in the LSValueVault. - unsigned getValueBit(const LSValue &L); - - /// Given the bit, get the LSValue from the LSValueVault. - LSValue &getValue(const unsigned index); - - /// Given a LSLocation, try to collect all the LSValues for this LSLocation - /// in the given basic block. If part of the locations have covering values, - /// find the values in its predecessors. - bool collectLocationValues(SILBasicBlock *BB, LSLocation &L, - LSLocationValueMap &Values, ValueTableMap &VM); - - /// Transitively collect all the values that make up this location and - /// create a SILArgument out of them. - SILValue computePredecessorLocationValue(SILBasicBlock *BB, LSLocation &L); - - /// Returns the LoadInst if \p Inst is a load inst we want to handle. - LoadInst *isLoadInstToHandle(SILInstruction *Inst) { - if (auto *LI = dyn_cast(Inst)) { - if (!ArrayType || - LI->getType().getNominalOrBoundGenericNominal() != ArrayType) { - return LI; - } - if (onlyImmutableLoads && - !LSLocation::getBaseAddressOrObject(LI->getOperand(), - /*stopAtImmutable*/ true).second) { - return nullptr; - } - } - return nullptr; - } -}; - -} // end anonymous namespace - -void BlockState::startTrackingValue(ValueTableMap &VM, unsigned L, unsigned V) { - VM[L] = V; -} - -void BlockState::stopTrackingValue(ValueTableMap &VM, unsigned B) { - VM.erase(B); -} - -bool BlockState::isTrackingLocation(SmallBitVector &BV, unsigned B) { - return BV.test(B); -} - -void BlockState::startTrackingLocation(SmallBitVector &BV, unsigned B) { - BV.set(B); -} - -void BlockState::stopTrackingLocation(SmallBitVector &BV, unsigned B) { - BV.reset(B); -} - -void BlockState::mergePredecessorsAvailSetMax(RLEContext &Ctx) { - if (BB->pred_empty()) { - ForwardSetMax.reset(); - return; - } - - auto Iter = BB->pred_begin(); - ForwardSetMax = Ctx.getBlockState(*Iter).ForwardSetMax; - Iter = std::next(Iter); - for (auto EndIter = BB->pred_end(); Iter != EndIter; ++Iter) { - ForwardSetMax &= Ctx.getBlockState(*Iter).ForwardSetMax; - } -} - -void BlockState::mergePredecessorAvailSet(RLEContext &Ctx) { - // Clear the state if the basic block has no predecessor. - if (BB->getPredecessorBlocks().begin() == BB->getPredecessorBlocks().end()) { - ForwardSetIn.reset(); - return; - } - - auto Iter = BB->pred_begin(); - ForwardSetIn = Ctx.getBlockState(*Iter).ForwardSetOut; - Iter = std::next(Iter); - for (auto EndIter = BB->pred_end(); Iter != EndIter; ++Iter) { - ForwardSetIn &= Ctx.getBlockState(*Iter).ForwardSetOut; - } -} - -void BlockState::mergePredecessorAvailSetAndValue(RLEContext &Ctx) { - // Clear the state if the basic block has no predecessor. - if (BB->getPredecessorBlocks().begin() == BB->getPredecessorBlocks().end()) { - ForwardSetIn.reset(); - ForwardValIn.clear(); - return; - } - - auto Iter = BB->pred_begin(); - ForwardSetIn = Ctx.getBlockState(*Iter).ForwardSetOut; - ForwardValIn = Ctx.getBlockState(*Iter).ForwardValOut; - Iter = std::next(Iter); - for (auto EndIter = BB->pred_end(); Iter != EndIter; ++Iter) { - BlockState &OtherState = Ctx.getBlockState(*Iter); - ForwardSetIn &= OtherState.ForwardSetOut; - - // Merge in the predecessor state. - for (unsigned i = 0; i < LocationNum; ++i) { - if (OtherState.ForwardSetOut[i]) { - // There are multiple values from multiple predecessors, set this as - // a covering value. We do not need to track the value itself, as we - // can always go to the predecessors BlockState to find it. - ForwardValIn[i] = Ctx.getValueBit(LSValue(true)); - continue; - } - // If this location does have an available value, then clear it. - stopTrackingValue(ForwardValIn, i); - stopTrackingLocation(ForwardSetIn, i); - } - } -} - -void BlockState::processBasicBlockWithKind(RLEContext &Ctx, RLEKind Kind) { - // Iterate over instructions in forward order. - for (auto &II : *BB) { - processInstructionWithKind(Ctx, &II, Kind); - } -} - -bool BlockState::processBasicBlockWithGenKillSet() { - ForwardSetIn.reset(BBKillSet); - ForwardSetIn |= BBGenSet; - return updateForwardSetOut(); -} - -SILValue BlockState::reduceValuesAtEndOfBlock(RLEContext &Ctx, LSLocation &L) { - // First, collect current available locations and their corresponding values - // into a map. - LSLocationValueMap Values; - - LSLocationList Locs; - LSLocation::expand(L, &BB->getModule(), - TypeExpansionContext(*BB->getParent()), Locs, Ctx.getTE()); - - // Find the values that this basic block defines and the locations which - // we do not have a concrete value in the current basic block. - ValueTableMap &OTM = getForwardValOut(); - for (unsigned i = 0; i < Locs.size(); ++i) { - auto Val = Ctx.getValue(OTM[Ctx.getLocationBit(Locs[i])]); - auto AvailVal = makeCopiedValueAvailable(Val.getBase(), BB); - Values[Locs[i]] = LSValue(AvailVal, Val.getPath().value()); - } - - // Second, reduce the available values into a single SILValue we can use to - // forward. - SILValue TheForwardingValue = - LSValue::reduce(L, &BB->getModule(), Values, BB->getTerminator()); - /// Return the forwarding value. - return TheForwardingValue; -} - -bool BlockState::setupRLE(RLEContext &Ctx, SILInstruction *I, SILValue Mem) { - // Try to construct a SILValue for the current LSLocation. - // - // Collect the locations and their corresponding values into a map. - LSLocation L; - LSLocationBaseMap &BaseToLocIndex = Ctx.getBM(); - if (BaseToLocIndex.find(Mem) != BaseToLocIndex.end()) { - L = BaseToLocIndex[Mem]; - } else { - SILValue UO = getUnderlyingObject(Mem); - L = LSLocation(UO, ProjectionPath::getProjectionPath(UO, Mem)); - } - - LSLocationValueMap Values; - // Use the ForwardValIn as we are currently processing the basic block. - if (!Ctx.collectLocationValues(I->getParent(), L, Values, getForwardValIn())) - return false; - - // Reduce the available values into a single SILValue we can use to forward. - SILModule *Mod = &I->getModule(); - SILValue TheForwardingValue = - LSValue::reduce(L, Mod, Values, I); - - if (!TheForwardingValue) - return false; - - // Now we have the forwarding value, record it for forwarding!. - // - // NOTE: we do not perform the RLE right here because doing so could introduce - // new LSLocations. - // - // e.g. - // %0 = load %x - // %1 = load %x - // %2 = extract_struct %1, #a - // %3 = load %2 - // - // If we perform the RLE and replace %1 with %0, we end up having a memory - // location we do not have before, i.e. Base == %0, and Path == #a. - // - // We may be able to add the LSLocation to the vault, but it gets - // complicated very quickly, e.g. we need to resize the bit vectors size, - // etc. - // - // However, since we already know the instruction to replace and the value to - // replace it with, we can record it for now and forwarded it after all the - // forwardable values are recorded in the function. - // - RedundantLoads[cast(I)] = TheForwardingValue; - - LLVM_DEBUG(llvm::dbgs() << "FORWARD " << TheForwardingValue << " to" << *I); - return true; -} - -void BlockState::updateForwardSetForRead(RLEContext &Ctx, unsigned B) { - startTrackingLocation(ForwardSetIn, B); -} - -void BlockState::updateGenKillSetForRead(RLEContext &Ctx, unsigned B) { - startTrackingLocation(BBGenSet, B); - stopTrackingLocation(BBKillSet, B); -} - -void BlockState::updateForwardSetAndValForRead(RLEContext &Ctx, unsigned L, - unsigned V) { - // Track the new location and value. - startTrackingValue(ForwardValIn, L, V); - startTrackingLocation(ForwardSetIn, L); -} - -void BlockState::updateGenKillSetForWrite(RLEContext &Ctx, unsigned B) { - // This is a store, invalidate any location that this location may alias, as - // their values can no longer be forwarded. - LSLocation &R = Ctx.getLocation(B); - for (unsigned i = 0; i < LocationNum; ++i) { - if (!isTrackingLocation(ForwardSetMax, i)) - continue; - LSLocation &L = Ctx.getLocation(i); - if (!L.isMayAliasLSLocation(R, Ctx.getAA())) - continue; - // MayAlias, invalidate the location. - stopTrackingLocation(BBGenSet, i); - startTrackingLocation(BBKillSet, i); - } - - // Start tracking this location. - startTrackingLocation(BBGenSet, B); - stopTrackingLocation(BBKillSet, B); -} - -void BlockState::updateMaxAvailSetForWrite(RLEContext &Ctx, unsigned B) { - startTrackingLocation(ForwardSetMax, B); -} - -void BlockState::updateMaxAvailSetForRead(RLEContext &Ctx, unsigned B) { - startTrackingLocation(ForwardSetMax, B); -} - -void BlockState::updateForwardSetForWrite(RLEContext &Ctx, unsigned B) { - // This is a store, invalidate any location that this location may alias, as - // their values can no longer be forwarded. - LSLocation &R = Ctx.getLocation(B); - for (unsigned i = 0; i < LocationNum; ++i) { - if (!isTrackingLocation(ForwardSetIn, i)) - continue; - LSLocation &L = Ctx.getLocation(i); - if (!L.isMayAliasLSLocation(R, Ctx.getAA())) - continue; - // MayAlias, invalidate the location. - stopTrackingLocation(ForwardSetIn, i); - } - - // Start tracking this location. - startTrackingLocation(ForwardSetIn, B); -} - -void BlockState::updateForwardSetAndValForWrite(RLEContext &Ctx, unsigned L, - unsigned V) { - // This is a store, invalidate any location that this location may alias, as - // their values can no longer be forwarded. - LSLocation &R = Ctx.getLocation(L); - for (unsigned i = 0; i < LocationNum; ++i) { - if (!isTrackingLocation(ForwardSetIn, i)) - continue; - LSLocation &L = Ctx.getLocation(i); - if (!L.isMayAliasLSLocation(R, Ctx.getAA())) - continue; - // MayAlias, invalidate the location and value. - stopTrackingValue(ForwardValIn, i); - stopTrackingLocation(ForwardSetIn, i); - } - - // Start tracking this location and value. - startTrackingLocation(ForwardSetIn, L); - startTrackingValue(ForwardValIn, L, V); -} - -void BlockState::processWrite(RLEContext &Ctx, SILInstruction *I, SILValue Mem, - SILValue Val, RLEKind Kind) { - // Initialize the LSLocation. - LSLocation L; - LSLocationBaseMap &BaseToLocIndex = Ctx.getBM(); - if (BaseToLocIndex.find(Mem) != BaseToLocIndex.end()) { - L = BaseToLocIndex[Mem]; - } else { - SILValue UO = getUnderlyingObject(Mem); - L = LSLocation(UO, ProjectionPath::getProjectionPath(UO, Mem)); - } - - // If we can't figure out the Base or Projection Path for the write, - // process it as an unknown memory instruction. - if (!L.isValid()) { - // we can ignore unknown store instructions if we are computing the - // AvailSetMax. - if (!isComputeAvailSetMax(Kind)) { - processUnknownWriteInst(Ctx, I, Kind); - } - return; - } - - auto *Fn = I->getFunction(); - // Expand the given location and val into individual fields and process - // them as separate writes. - LSLocationList Locs; - LSLocation::expand(L, &I->getModule(), TypeExpansionContext(*Fn), Locs, - Ctx.getTE()); - - if (isComputeAvailSetMax(Kind)) { - for (unsigned i = 0; i < Locs.size(); ++i) { - updateMaxAvailSetForWrite(Ctx, Ctx.getLocationBit(Locs[i])); - } - return; - } - - // Are we computing the genset and killset ? - if (isComputeAvailGenKillSet(Kind)) { - for (unsigned i = 0; i < Locs.size(); ++i) { - updateGenKillSetForWrite(Ctx, Ctx.getLocationBit(Locs[i])); - } - return; - } - - // Are we computing available value or performing RLE? - LSValueList Vals; - LSValue::expand(Val, &I->getModule(), TypeExpansionContext(*Fn), Vals, - Ctx.getTE()); - if (isComputeAvailValue(Kind) || isPerformingRLE(Kind)) { - for (unsigned i = 0; i < Locs.size(); ++i) { - updateForwardSetAndValForWrite(Ctx, Ctx.getLocationBit(Locs[i]), - Ctx.getValueBit(Vals[i])); - } - return; - } - - llvm_unreachable("Unknown RLE compute kind"); -} - -void BlockState::processRead(RLEContext &Ctx, SILInstruction *I, SILValue Mem, - SILValue Val, RLEKind Kind) { - // Initialize the LSLocation. - LSLocation L; - LSLocationBaseMap &BaseToLocIndex = Ctx.getBM(); - if (BaseToLocIndex.find(Mem) != BaseToLocIndex.end()) { - L = BaseToLocIndex[Mem]; - } else { - SILValue UO = getUnderlyingObject(Mem); - L = LSLocation(UO, ProjectionPath::getProjectionPath(UO, Mem)); - } - - // If we can't figure out the Base or Projection Path for the read, simply - // ignore it for now. - if (!L.isValid()) - return; - - auto *Fn = I->getFunction(); - // Expand the given LSLocation and Val into individual fields and process - // them as separate reads. - LSLocationList Locs; - LSLocation::expand(L, &I->getModule(), TypeExpansionContext(*Fn), Locs, - Ctx.getTE()); - - if (isComputeAvailSetMax(Kind)) { - for (unsigned i = 0; i < Locs.size(); ++i) { - updateMaxAvailSetForRead(Ctx, Ctx.getLocationBit(Locs[i])); - } - return; - } - - // Are we computing the genset and killset. - if (isComputeAvailGenKillSet(Kind)) { - for (unsigned i = 0; i < Locs.size(); ++i) { - updateGenKillSetForRead(Ctx, Ctx.getLocationBit(Locs[i])); - } - return; - } - - // Are we computing available values ?. - bool CanForward = true; - LSValueList Vals; - LSValue::expand(Val, &I->getModule(), TypeExpansionContext(*Fn), Vals, - Ctx.getTE()); - if (isComputeAvailValue(Kind) || isPerformingRLE(Kind)) { - for (unsigned i = 0; i < Locs.size(); ++i) { - if (isTrackingLocation(ForwardSetIn, Ctx.getLocationBit(Locs[i]))) - continue; - updateForwardSetAndValForRead(Ctx, Ctx.getLocationBit(Locs[i]), - Ctx.getValueBit(Vals[i])); - // We can not perform the forwarding as we are at least missing - // some pieces of the read location. - CanForward = false; - } - } - - // Simply return if we are not performing RLE or we do not have all the - // values available to perform RLE. - if (!isPerformingRLE(Kind) || !CanForward) - return; - - // Lastly, forward value to the load. - setupRLE(Ctx, I, Mem); -} - -void BlockState::processStoreInst(RLEContext &Ctx, StoreInst *SI, RLEKind Kind) { - processWrite(Ctx, SI, SI->getDest(), SI->getSrc(), Kind); -} - -void BlockState::processLoadInst(RLEContext &Ctx, LoadInst *LI, RLEKind Kind) { - processRead(Ctx, LI, LI->getOperand(), SILValue(LI), Kind); -} - -void BlockState::processUnknownWriteInstForGenKillSet(RLEContext &Ctx, - SILInstruction *I) { - auto *AA = Ctx.getAA(); - for (unsigned i = 0; i < LocationNum; ++i) { - if (!isTrackingLocation(ForwardSetMax, i)) - continue; - // Invalidate any location this instruction may write to. - // - // TODO: checking may alias with Base is overly conservative, - // we should check may alias with base plus projection path. - LSLocation &R = Ctx.getLocation(i); - if (!AA->mayWriteToMemory(I, R.getBase())) - continue; - // MayAlias. - stopTrackingLocation(BBGenSet, i); - startTrackingLocation(BBKillSet, i); - } -} - -void BlockState::processUnknownWriteInstForRLE(RLEContext &Ctx, - SILInstruction *I) { - auto *AA = Ctx.getAA(); - for (unsigned i = 0; i < LocationNum; ++i) { - if (!isTrackingLocation(ForwardSetIn, i)) - continue; - // Invalidate any location this instruction may write to. - // - // TODO: checking may alias with Base is overly conservative, - // we should check may alias with base plus projection path. - LSLocation &R = Ctx.getLocation(i); - if (!AA->mayWriteToMemory(I, R.getBase())) - continue; - // MayAlias. - stopTrackingLocation(ForwardSetIn, i); - stopTrackingValue(ForwardValIn, i); - } -} - -void BlockState::processUnknownWriteInst(RLEContext &Ctx, SILInstruction *I, - RLEKind Kind) { - // If this is a release on a guaranteed parameter, it can not call deinit, - // which might read or write memory. - if (isIntermediateRelease(I, Ctx.getEAFI())) - return; - - // Are we computing the genset and killset ? - if (isComputeAvailGenKillSet(Kind)) { - processUnknownWriteInstForGenKillSet(Ctx, I); - return; - } - - // Are we computing the available value or doing RLE ? - if (isComputeAvailValue(Kind) || isPerformingRLE(Kind)) { - processUnknownWriteInstForRLE(Ctx, I); - return; - } - - llvm_unreachable("Unknown RLE compute kind"); -} - -void BlockState:: -processDeallocStackInstForGenKillSet(RLEContext &Ctx, DeallocStackInst *I) { - SILValue ASI = findAllocStackInst(I); - for (unsigned i = 0; i < LocationNum; ++i) { - LSLocation &R = Ctx.getLocation(i); - if (R.getBase() != ASI) - continue; - // MayAlias. - stopTrackingLocation(BBGenSet, i); - startTrackingLocation(BBKillSet, i); - } -} - -void BlockState:: -processDeallocStackInstForRLE(RLEContext &Ctx, DeallocStackInst *I) { - SILValue ASI = findAllocStackInst(I); - for (unsigned i = 0; i < LocationNum; ++i) { - LSLocation &R = Ctx.getLocation(i); - if (R.getBase() != ASI) - continue; - // MayAlias. - stopTrackingLocation(ForwardSetIn, i); - stopTrackingValue(ForwardValIn, i); - } -} - -void BlockState:: -processDeallocStackInst(RLEContext &Ctx, DeallocStackInst *I, RLEKind Kind) { - // Are we computing the genset and killset ? - if (isComputeAvailGenKillSet(Kind)) { - processDeallocStackInstForGenKillSet(Ctx, I); - return; - } - - // Are we computing the available value or doing RLE ? - if (isComputeAvailValue(Kind) || isPerformingRLE(Kind)) { - processDeallocStackInstForRLE(Ctx, I); - return; - } - - llvm_unreachable("Unknown RLE compute kind"); -} - - -void BlockState::processInstructionWithKind(RLEContext &Ctx, - SILInstruction *Inst, - RLEKind Kind) { - // This is a StoreInst, try to see whether it clobbers any forwarding value - if (auto *SI = dyn_cast(Inst)) { - processStoreInst(Ctx, SI, Kind); - return; - } - - // This is a LoadInst. Let's see if we can find a previous loaded, stored - // value to use instead of this load. - if (auto *LI = Ctx.isLoadInstToHandle(Inst)) { - processLoadInst(Ctx, LI, Kind); - return; - } - - if (auto *DSI = dyn_cast(Inst)) { - processDeallocStackInst(Ctx, DSI, Kind); - return; - } - - // If this instruction has side effects, but is inert from a load store - // perspective, skip it. - if (isRLEInertInstruction(Inst)) - return; - - // If this instruction does not read or write memory, we can skip it. - if (!Inst->mayReadOrWriteMemory()) - return; - - // If we have an instruction that may write to memory and we cannot prove - // that it and its operands cannot alias a load we have visited, - // invalidate that load. - if (Inst->mayWriteToMemory()) { - LLVM_DEBUG(llvm::dbgs() << "WRITE " << *Inst); - processUnknownWriteInst(Ctx, Inst, Kind); - return; - } - LLVM_DEBUG(llvm::dbgs() << "READ " << *Inst); -} - -RLEContext::ProcessKind -RLEContext:: -getProcessFunctionKind(unsigned LoadCount, unsigned StoreCount) { - // Don't optimize function that are marked as 'no.optimize'. - if (!Fn->shouldOptimize()) - return ProcessKind::ProcessNone; - - // Really no point optimizing here as there is no forwardable loads. - if (LoadCount + StoreCount < 2) - return ProcessKind::ProcessNone; - - bool RunOneIteration = true; - unsigned BBCount = 0; - unsigned LocationCount = LocationVault.size(); - - if (LocationCount == 0) - return ProcessKind::ProcessNone; - - // If all basic blocks will have their predecessors processed if - // the basic blocks in the functions are iterated in post order. - // Then this function can be processed in one iteration, i.e. no - // need to generate the genset and killset. - auto *PO = parentTransform->getPassManager()-> - getAnalysis()->get(Fn); - BasicBlockSet HandledBBs(Fn); - for (SILBasicBlock *B : PO->getReversePostOrder()) { - ++BBCount; - for (SILBasicBlock *pred : B->getPredecessorBlocks()) { - if (!HandledBBs.contains(pred)) { - RunOneIteration = false; - break; - } - } - HandledBBs.insert(B); - } - - // Data flow may take too long to run. - if (BBCount * LocationCount > MaxLSLocationBBMultiplicationNone) - return ProcessKind::ProcessNone; - - // This function's data flow would converge in 1 iteration. - if (RunOneIteration) - return ProcessKind::ProcessOneIteration; - - // We run one pessimistic data flow to do dead store elimination on - // the function. - if (BBCount * LocationCount > MaxLSLocationBBMultiplicationPessimistic) - return ProcessKind::ProcessOneIteration; - - return ProcessKind::ProcessMultipleIterations; -} - -#ifndef NDEBUG -void BlockState::dump(RLEContext &Ctx) { - for (unsigned i = 0; i < LocationNum; ++i) { - if (!isTrackingLocation(ForwardSetMax, i)) - continue; - - llvm::dbgs() << "Loc #" << i << ":" << (BBGenSet[i] ? " Gen" : "") - << (BBKillSet[i] ? " Kill" : ""); - if (!ForwardSetIn.empty() && ForwardSetIn.test(i)) { - llvm::dbgs() << " IN "; - ValueTableMap::const_iterator inIter = ForwardValIn.find(i); - if (inIter != ForwardValIn.end()) { - if (SILValue base = Ctx.getValue(inIter->second).getBase()) - llvm::dbgs() << base; - else - llvm::dbgs() << "no base"; - } - } - if (!ForwardSetOut.empty() && ForwardSetOut.test(i)) { - llvm::dbgs() << " OUT "; - ValueTableMap::const_iterator outIter = ForwardValOut.find(i); - if (outIter != ForwardValOut.end()) { - if (SILValue base = Ctx.getValue(outIter->second).getBase()) - llvm::dbgs() << base; - else - llvm::dbgs() << "no base"; - } - } - llvm::dbgs() << "\n"; - } -} -#endif - -//===----------------------------------------------------------------------===// -// RLEContext Implementation -//===----------------------------------------------------------------------===// - -RLEContext::RLEContext(SILFunction *F, SILFunctionTransform *parentTransform, - bool disableArrayLoads, bool onlyImmutableLoads) - : Fn(F), parentTransform(parentTransform), - AA(parentTransform->getPassManager()->getAnalysis(F)), - TE(parentTransform->getPassManager()->getAnalysis()), - PO(parentTransform->getPassManager()->getAnalysis()->get(F)), - EAFI(parentTransform->getPassManager()->getAnalysis()->get(F)), - BBToLocState(F), BBWithLoads(F), - ArrayType(disableArrayLoads - ? F->getModule().getASTContext().getArrayDecl() - : nullptr), - onlyImmutableLoads(onlyImmutableLoads) -#ifndef NDEBUG - , - printCtx(llvm::dbgs(), /*Verbose=*/false, /*Sorted=*/true) -#endif -{ -} - -LSLocation &RLEContext::getLocation(const unsigned index) { - return LocationVault[index]; -} - -unsigned RLEContext::getLocationBit(const LSLocation &Loc) { - // Return the bit position of the given Loc in the LocationVault. The bit - // position is then used to set/reset the bitvector kept by each BlockState. - // - // We should have the location populated by the enumerateLSLocation at this - // point. - auto Iter = LocToBitIndex.find(Loc); - assert(Iter != LocToBitIndex.end() && "Location should have been enum'ed"); - return Iter->second; -} - -LSValue &RLEContext::getValue(const unsigned index) { - return LSValueVault[index]; -} - -unsigned RLEContext::getValueBit(const LSValue &Val) { - // Return the bit position of the given Val in the LSValueVault. The bit - // position is then used to set/reset the bitvector kept by each g. - auto Iter = ValToBitIndex.find(Val); - - // We do not walk over the function and find all the possible LSValues - // in this function, as some of the these values will not be used, i.e. - // if the LoadInst that generates this value is actually RLE'ed. - // Instead, we create the LSValues when we need them. - if (Iter == ValToBitIndex.end()) { - ValToBitIndex[Val] = LSValueVault.size(); - LSValueVault.push_back(Val); - return ValToBitIndex[Val]; - } - return Iter->second; -} - -BlockState::ValueState BlockState::getValueStateAtEndOfBlock(RLEContext &Ctx, - LSLocation &L) { - // Find number of covering value and concrete values for the locations - // expanded from the given location. - unsigned CSCount = 0, CTCount = 0; - LSLocationList Locs; - LSLocation::expand(L, &BB->getModule(), - TypeExpansionContext(*BB->getParent()), Locs, Ctx.getTE()); - - ValueTableMap &OTM = getForwardValOut(); - for (auto &X : Locs) { - LSValue &V = Ctx.getValue(OTM[Ctx.getLocationBit(X)]); - if (V.isCoveringValue()) { - ++CSCount; - continue; - } - ++CTCount; - } - - if (CSCount == Locs.size()) - return ValueState::CoverValues; - if (CTCount == Locs.size()) - return ValueState::ConcreteValues; - return ValueState::CoverAndConcreteValues; -} - -SILValue RLEContext::computePredecessorLocationValue(SILBasicBlock *BB, - LSLocation &L) { - llvm::SmallVector, 8> Values; - BasicBlockWorklist WorkList(Fn); - - // Push in all the predecessors to get started. - for (auto Pred : BB->getPredecessorBlocks()) { - WorkList.pushIfNotVisited(Pred); - } - - while (SILBasicBlock *CurBB = WorkList.pop()) { - BlockState &Forwarder = getBlockState(CurBB); - - // There are 3 cases that can happen here. - // - // 1. The current basic block contains concrete values for the entire - // location. - // 2. The current basic block contains covering values for the entire - // location. - // 3. The current basic block contains concrete value for part of the - // location and covering values for the rest. - // - // This BlockState contains concrete values for all the expanded - // locations, collect and reduce them into a single value in the current - // basic block. - if (Forwarder.isConcreteValues(*this, L)) { - Values.push_back({CurBB, Forwarder.reduceValuesAtEndOfBlock(*this, L)}); - continue; - } - - // This BlockState does not contain concrete value for any of the expanded - // locations, collect in this block's predecessors. - if (Forwarder.isCoverValues(*this, L)) { - for (auto Pred : CurBB->getPredecessorBlocks()) { - WorkList.pushIfNotVisited(Pred); - } - continue; - } - - // This block contains concrete values for some but not all the expanded - // locations, recursively call collectLocationValues to materialize the - // value that reaches this basic block. - LSLocationValueMap LSValues; - if (!collectLocationValues(CurBB, L, LSValues, Forwarder.getForwardValOut())) - return SILValue(); - - // Reduce the available values into a single SILValue we can use to forward - SILInstruction *IPt = CurBB->getTerminator(); - Values.push_back({CurBB, LSValue::reduce(L, &BB->getModule(), LSValues, IPt)}); - } - - auto ownershipRange = - makeTransformRange(llvm::make_range(Values.begin(), Values.end()), - [](std::pair v) { - return v.second->getOwnershipKind(); - }); - - auto mergedOwnershipKind = ValueOwnershipKind::merge(ownershipRange); - - // Finally, collect all the values for the SILArgument, materialize it using - // the SSAUpdater. - Updater.initialize( - L.getType(&BB->getModule(), TypeExpansionContext(*BB->getParent())) - .getObjectType(), - mergedOwnershipKind); - - SmallVector insertedPhis; - Updater.setInsertedPhis(&insertedPhis); - - for (auto V : Values) { - Updater.addAvailableValue(V.first, V.second); - } - - auto Val = Updater.getValueInMiddleOfBlock(BB); - - for (auto *phi : insertedPhis) { - if (phi == Val) { - continue; - } - // Fix lifetime of intermediate phis - SmallVector userBBs; - for (auto use : phi->getUses()) { - userBBs.push_back(use->getParentBlock()); - } - endLifetimeAtLeakingBlocks(phi, userBBs); - } - return makeValueAvailable(Val, BB); -} - -bool RLEContext::collectLocationValues(SILBasicBlock *BB, LSLocation &L, - LSLocationValueMap &Values, - ValueTableMap &VM) { - LSLocationList CSLocs; - LSLocationList Locs; - LSLocation::expand(L, &BB->getModule(), - TypeExpansionContext(*BB->getParent()), Locs, TE); - - auto *Mod = &BB->getModule(); - // Find the locations that this basic block defines and the locations which - // we do not have a concrete value in the current basic block. - for (auto &X : Locs) { - auto Val = getValue(VM[getLocationBit(X)]); - if (!Val.isCoveringValue()) { - auto AvailValue = - makeCopiedValueAvailable(Val.getBase(), BB); - Values[X] = LSValue(AvailValue, Val.getPath().value()); - continue; - } - Values[X] = Val; - CSLocs.push_back(X); - } - - // For locations which we do not have concrete values for in this basic - // block, try to reduce it to the minimum # of locations possible, this - // will help us to generate as few SILArguments as possible. - LSLocation::reduce(L, Mod, TypeExpansionContext(*BB->getParent()), CSLocs); - - // To handle covering value, we need to go to the predecessors and - // materialize them there. - for (auto &X : CSLocs) { - SILValue V = computePredecessorLocationValue(BB, X); - if (!V) - return false; - // We've constructed a concrete value for the covering value. Expand and - // collect the newly created forwardable values. - LSLocationList Locs; - LSValueList Vals; - auto expansionContext = TypeExpansionContext(*BB->getParent()); - LSLocation::expand(X, Mod, expansionContext, Locs, TE); - LSValue::expand(V, Mod, expansionContext, Vals, TE); - - for (unsigned i = 0; i < Locs.size(); ++i) { - Values[Locs[i]] = Vals[i]; - assert(Values[Locs[i]].isValid() && "Invalid load store value"); - } - } - return true; -} - -void RLEContext::processBasicBlocksForGenKillSet() { - for (SILBasicBlock *BB : PO->getReversePostOrder()) { - LLVM_DEBUG(llvm::dbgs() << "PROCESS " << printCtx.getID(BB) - << " for Gen/Kill:\n"; - BB->print(printCtx)); - - BlockState &S = getBlockState(BB); - - // Compute the AvailSetMax at the beginning of the basic block. - S.mergePredecessorsAvailSetMax(*this); - - // Compute the genset and killset. - // - // To optimize this process, we also compute the AvailSetMax at particular - // point in the basic block. - for (auto I = BB->begin(), E = BB->end(); I != E; ++I) { - if (auto *LI = dyn_cast(&*I)) { - if (!BBWithLoads.contains(BB)) - BBWithLoads.insert(BB); - S.processLoadInst(*this, LI, RLEKind::ComputeAvailSetMax); - } - if (auto *SI = dyn_cast(&*I)) { - S.processStoreInst(*this, SI, RLEKind::ComputeAvailSetMax); - } - - S.processInstructionWithKind(*this, &*I, RLEKind::ComputeAvailGenKillSet); - } - LLVM_DEBUG(S.dump(*this)); - } -} - -void RLEContext::processBasicBlocksWithGenKillSet() { - // Process each basic block with the gen and kill set. Every time the - // ForwardSetOut of a basic block changes, the optimization is rerun on its - // successors. - BasicBlockWorklist WorkList(Fn); - - // Push into the worklist in post order so that we can pop from the back and - // get reverse post order. - for (SILBasicBlock *BB : PO->getPostOrder()) { - WorkList.push(BB); - } - while (SILBasicBlock *BB = WorkList.popAndForget()) { - LLVM_DEBUG(llvm::dbgs() << "PROCESS " << printCtx.getID(BB) - << " with Gen/Kill.\n"); - // Intersection. - BlockState &Forwarder = getBlockState(BB); - // Compute the ForwardSetIn at the beginning of the basic block. - Forwarder.mergePredecessorAvailSet(*this); - - if (Forwarder.processBasicBlockWithGenKillSet()) { - for (SILBasicBlock *succ : BB->getSuccessors()) { - WorkList.pushIfNotVisited(succ); - } - } - LLVM_DEBUG(Forwarder.dump(*this)); - } -} - -void RLEContext::processBasicBlocksForAvailValue() { - for (SILBasicBlock *BB : PO->getReversePostOrder()) { - LLVM_DEBUG(llvm::dbgs() << "PROCESS " << printCtx.getID(BB) - << " for available.\n"); - - BlockState &Forwarder = getBlockState(BB); - - // Merge the predecessors. After merging, BlockState now contains - // lists of available LSLocations and their values that reach the - // beginning of the basic block along all paths. - Forwarder.mergePredecessorAvailSetAndValue(*this); - - // Merge duplicate loads, and forward stores to - // loads. We also update lists of stores|loads to reflect the end - // of the basic block. - Forwarder.processBasicBlockWithKind(*this, RLEKind::ComputeAvailValue); - - // Update the locations with available values. We do not need to update - // the available BitVector here as they should have been initialized and - // stabilized in the processBasicBlocksWithGenKillSet. - Forwarder.updateForwardValOut(); - - LLVM_DEBUG(Forwarder.dump(*this)); - } -} - -void RLEContext::processBasicBlocksForRLE(bool Optimistic) { - for (SILBasicBlock *BB : PO->getReversePostOrder()) { - LLVM_DEBUG(llvm::dbgs() << "PROCESS " << printCtx.getID(BB) - << " for RLE.\n"); - - // If we know this is not a one iteration function which means its - // forward sets have been computed and converged, - // and this basic block does not even have LoadInsts, there is no point - // in processing every instruction in the basic block again as no store - // will be eliminated. - if (Optimistic && !BBWithLoads.contains(BB)) - continue; - - BlockState &Forwarder = getBlockState(BB); - - // Merge the predecessors. After merging, BlockState now contains - // lists of available LSLocations and their values that reach the - // beginning of the basic block along all paths. - Forwarder.mergePredecessorAvailSetAndValue(*this); - - LLVM_DEBUG(Forwarder.dump(*this)); - - // Perform the actual redundant load elimination. - Forwarder.processBasicBlockWithKind(*this, RLEKind::PerformRLE); - - // If this is not a one iteration data flow, then the forward sets - // have been computed. - if (Optimistic) - continue; - - // Update the locations with available values and their values. - Forwarder.updateForwardSetOut(); - Forwarder.updateForwardValOut(); - } -} - -void RLEContext::runIterativeRLE() { - // Generate the genset and killset for every basic block. - processBasicBlocksForGenKillSet(); - - // Process basic blocks in RPO. After the data flow converges, run last - // iteration and perform load forwarding. - processBasicBlocksWithGenKillSet(); - - // We have computed the available value bit, now go through every basic - // block and compute the forwarding value locally. This is necessary as - // when we perform the RLE in the last iteration, we must handle loops, i.e. - // predecessor blocks which have not been processed when a basic block is - // processed. - processBasicBlocksForAvailValue(); -} - -bool RLEContext::run() { - // We perform redundant load elimination in the following phases. - // - // Phase 1. Compute the genset and killset for every basic block. - // - // Phase 2. Use an iterative data flow to compute whether there is an - // available value at a given point, we do not yet care about what the value - // is. - // - // Phase 3. we compute the real forwardable value at a given point. - // - // Phase 4. we perform the redundant load elimination. - // Walk over the function and find all the locations accessed by - // this function. - int numLoads = 0, numStores = 0; - LSLocation::enumerateLSLocations(*Fn, LocationVault, - LocToBitIndex, - BaseToLocIndex, TE, - /*stopAtImmutable*/ onlyImmutableLoads, - numLoads, numStores, immutableLoadsFound); - - // Check how to optimize this function. - ProcessKind Kind = getProcessFunctionKind(numLoads, numStores); - - // We do not optimize this function at all. - if (Kind == ProcessKind::ProcessNone) - return false; - - // Do we run a multi-iteration data flow ? - const bool Optimistic = (Kind == ProcessKind::ProcessMultipleIterations); - - // These are a list of basic blocks that we actually processed. - // We do not process unreachable block, instead we set their liveouts to nil. - BasicBlockSet BBToProcess(Fn); - for (auto X : PO->getPostOrder()) - BBToProcess.insert(X); - - // For all basic blocks in the function, initialize a BB state. Since we - // know all the locations accessed in this function, we can resize the bit - // vector to the appropriate size. - for (auto bs : BBToLocState) { - bs.data.init(&bs.block, LocationVault.size(), - Optimistic && BBToProcess.contains(&bs.block)); - } - - LLVM_DEBUG(for (unsigned i = 0; i < LocationVault.size(); ++i) { - llvm::dbgs() << "LSLocation #" << i; - getLocation(i).print(llvm::dbgs()); - }); - - if (Optimistic) - runIterativeRLE(); - - // We have the available value bit computed and the local forwarding value. - // Set up the load forwarding. - processBasicBlocksForRLE(Optimistic); - - if (!parentTransform->continueWithNextSubpassRun(nullptr)) - return false; - - // Finally, perform the redundant load replacements. - llvm::SmallVector InstsToDelete; - bool SILChanged = false; - for (auto bs : BBToLocState) { - auto &Loads = bs.data.getRL(); - // Nothing to forward. - if (Loads.empty()) - continue; - // We iterate the instructions in the basic block in a deterministic order - // and use this order to perform the load forwarding. - // - // NOTE: we could end up with different SIL depending on the ordering load - // forwarding is performed. - for (auto I = bs.block.rbegin(), E = bs.block.rend(); I != E; ++I) { - auto V = dyn_cast(&*I); - if (!V) - continue; - auto Iter = Loads.find(V); - if (Iter == Loads.end()) - continue; - - if (!parentTransform->continueWithNextSubpassRun(V)) - return SILChanged; - - LLVM_DEBUG(llvm::dbgs() << "Replacing " << SILValue(Iter->first) - << "With " << Iter->second); - auto *origLoad = cast(Iter->first); - SILValue newValue = Iter->second; - if (origLoad->getOwnershipQualifier() == LoadOwnershipQualifier::Take) { - SILBuilderWithScope(origLoad).createDestroyAddr(origLoad->getLoc(), - origLoad->getOperand()); - } - SILChanged = true; - origLoad->replaceAllUsesWith(newValue); - InstsToDelete.push_back(origLoad); - ++NumForwardedLoads; - } - } - - // Erase the instructions recursively, this way, we get rid of pass - // dependence on DCE. - for (auto &X : InstsToDelete) { - - if (!parentTransform->continueWithNextSubpassRun(X)) - return SILChanged; - - // It is possible that the instruction still has uses, because it could be - // used as the replacement Value, i.e. F.second, for some other RLE pairs. - // - // TODO: we should fix this, otherwise we are missing RLE opportunities. - if (X->hasUsesOfAnyResult()) - continue; - recursivelyDeleteTriviallyDeadInstructions(X, true); - } - return SILChanged; -} - -//===----------------------------------------------------------------------===// -// Top Level Entry Point -//===----------------------------------------------------------------------===// - -namespace { - -class RedundantLoadElimination : public SILFunctionTransform { - -private: - bool disableArrayLoads; - -public: - - RedundantLoadElimination(bool disableArrayLoads) - : disableArrayLoads(disableArrayLoads) { } - - /// The entry point to the transformation. - void run() override { - SILFunction *F = getFunction(); - - LLVM_DEBUG(llvm::dbgs() << "*** RLE on function: " << F->getName() - << " ***\n"); - - RLEContext RLE(F, this, disableArrayLoads, - /*onlyImmutableLoads*/ false); - if (RLE.run()) { - invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); - } - if (!continueWithNextSubpassRun(nullptr)) - return; - - if (RLE.shouldOptimizeImmutableLoads()) { - /// Re-running RLE with cutting base addresses off at - /// `ref_element_addr [immutable]` or `ref_tail_addr [immutable]` can - /// expose additional opportunities. - RLEContext RLE2(F, this, disableArrayLoads, - /*onlyImmutableLoads*/ true); - if (RLE2.run()) { - invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); - } - } - } -}; - -} // end anonymous namespace - -SILTransform *swift::createEarlyRedundantLoadElimination() { - return new RedundantLoadElimination(/*disableArrayLoads=*/true); -} - -SILTransform *swift::createRedundantLoadElimination() { - return new RedundantLoadElimination(/*disableArrayLoads=*/false); -} diff --git a/lib/SILOptimizer/UtilityPasses/CMakeLists.txt b/lib/SILOptimizer/UtilityPasses/CMakeLists.txt index 7c8de0f172571..db071796ab53e 100644 --- a/lib/SILOptimizer/UtilityPasses/CMakeLists.txt +++ b/lib/SILOptimizer/UtilityPasses/CMakeLists.txt @@ -18,7 +18,6 @@ target_sources(swiftSILOptimizer PRIVATE FunctionOrderPrinter.cpp IVInfoPrinter.cpp InstCount.cpp - LSLocationPrinter.cpp Link.cpp LoopCanonicalizer.cpp LoopInfoPrinter.cpp diff --git a/lib/SILOptimizer/UtilityPasses/LSLocationPrinter.cpp b/lib/SILOptimizer/UtilityPasses/LSLocationPrinter.cpp deleted file mode 100644 index d5313a47aca22..0000000000000 --- a/lib/SILOptimizer/UtilityPasses/LSLocationPrinter.cpp +++ /dev/null @@ -1,270 +0,0 @@ -//===--- LSLocationPrinter.cpp - Dump all memory locations in program -----===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 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 -// -//===----------------------------------------------------------------------===// -// -// This pass tests type expansion, memlocation expansion and memlocation -// reduction. -// -//===----------------------------------------------------------------------===// - -#define DEBUG_TYPE "sil-memlocation-dumper" -#include "swift/SIL/Projection.h" -#include "swift/SIL/SILFunction.h" -#include "swift/SIL/SILValue.h" -#include "swift/SILOptimizer/Analysis/Analysis.h" -#include "swift/SILOptimizer/PassManager/Passes.h" -#include "swift/SILOptimizer/PassManager/Transforms.h" -#include "swift/SILOptimizer/Utils/LoadStoreOptUtils.h" -#include "llvm/Support/CommandLine.h" -#include "llvm/Support/Debug.h" - -using namespace swift; - -//===----------------------------------------------------------------------===// -// Top Level Driver -//===----------------------------------------------------------------------===// - -namespace { - -enum class MLKind : unsigned { - OnlyExpansion = 0, - OnlyReduction = 1, - OnlyTypeExpansion = 2, - All = 3, -}; - -} // end anonymous namespace - -static llvm::cl::opt LSLocationKinds( - "ml", llvm::cl::desc("LSLocation Kinds:"), llvm::cl::init(MLKind::All), - llvm::cl::values( - clEnumValN(MLKind::OnlyExpansion, "only-expansion", "only-expansion"), - clEnumValN(MLKind::OnlyReduction, "only-reduction", "only-reduction"), - clEnumValN(MLKind::OnlyTypeExpansion, "only-type-expansion", - "only-type-expansion"), - clEnumValN(MLKind::All, "all", "all"))); - -static llvm::cl::opt UseProjection("lslocation-dump-use-new-projection", - llvm::cl::init(false)); - -namespace { - -class LSLocationPrinter : public SILModuleTransform { - /// Type expansion analysis. - TypeExpansionAnalysis *TE; - -public: - /// Dumps the expansions of SILType accessed in the function. - /// This tests the expandTypeIntoLeafProjectionPaths function, which is - /// a function used extensively in expand and reduce functions. - /// - /// We test it to catch any suspicious things in the earliest point. - /// - void printTypeExpansion(SILFunction &Fn) { - SILModule *M = &Fn.getModule(); - ProjectionPathList PPList; - unsigned Counter = 0; - for (auto &BB : Fn) { - for (auto &II : BB) { - if (auto *LI = dyn_cast(&II)) { - SILValue V = LI->getOperand(); - // This is an address type, take it object type. - SILType Ty = V->getType().getObjectType(); - ProjectionPath::expandTypeIntoLeafProjectionPaths( - Ty, M, TypeExpansionContext(Fn), PPList); - } else if (auto *SI = dyn_cast(&II)) { - SILValue V = SI->getDest(); - // This is an address type, take it object type. - SILType Ty = V->getType().getObjectType(); - ProjectionPath::expandTypeIntoLeafProjectionPaths( - Ty, M, TypeExpansionContext(Fn), PPList); - } else { - // Not interested in these instructions yet. - continue; - } - - llvm::outs() << "#" << Counter++ << II; - for (auto &T : PPList) { - T.value().print(llvm::outs(), *M, TypeExpansionContext(Fn)); - } - PPList.clear(); - } - } - llvm::outs() << "\n"; - } - - void printTypeExpansionWithProjection(SILFunction &Fn) { - SILModule *M = &Fn.getModule(); - llvm::SmallVector, 8> PPList; - unsigned Counter = 0; - for (auto &BB : Fn) { - for (auto &II : BB) { - SILValue V; - SILType Ty; - if (auto *LI = dyn_cast(&II)) { - V = LI->getOperand(); - // This is an address type, take it object type. - Ty = V->getType().getObjectType(); - ProjectionPath::expandTypeIntoLeafProjectionPaths( - Ty, M, TypeExpansionContext(Fn), PPList); - } else if (auto *SI = dyn_cast(&II)) { - V = SI->getDest(); - // This is an address type, take it object type. - Ty = V->getType().getObjectType(); - ProjectionPath::expandTypeIntoLeafProjectionPaths( - Ty, M, TypeExpansionContext(Fn), PPList); - } else { - // Not interested in these instructions yet. - continue; - } - - llvm::outs() << "#" << Counter++ << II; - for (auto &T : PPList) { - T.value().print(llvm::outs(), *M, TypeExpansionContext(Fn)); - } - PPList.clear(); - } - } - llvm::outs() << "\n"; - } - - /// Dumps the expansions of memory locations accessed in the function. - /// This tests the expand function in LSLocation class. - /// - /// We test it to catch any suspicious things when memory location is - /// expanded, i.e. base is traced back and aggregate is expanded - /// properly. - void printMemExpansion(SILFunction &Fn) { - LSLocation L; - LSLocationList Locs; - unsigned Counter = 0; - for (auto &BB : Fn) { - for (auto &II : BB) { - if (auto *LI = dyn_cast(&II)) { - SILValue Mem = LI->getOperand(); - SILValue UO = getUnderlyingObject(Mem); - L.init(UO, ProjectionPath::getProjectionPath(UO, Mem)); - if (!L.isValid()) - continue; - LSLocation::expand(L, &Fn.getModule(), TypeExpansionContext(Fn), Locs, - TE); - } else if (auto *SI = dyn_cast(&II)) { - SILValue Mem = SI->getDest(); - SILValue UO = getUnderlyingObject(Mem); - L.init(UO, ProjectionPath::getProjectionPath(UO, Mem)); - if (!L.isValid()) - continue; - LSLocation::expand(L, &Fn.getModule(), TypeExpansionContext(Fn), Locs, - TE); - } else { - // Not interested in these instructions yet. - continue; - } - - llvm::outs() << "#" << Counter++ << II; - for (auto &Loc : Locs) { - Loc.print(llvm::outs()); - } - Locs.clear(); - } - } - llvm::outs() << "\n"; - } - - /// Dumps the reductions of set of memory locations. - /// - /// This function first calls expand on a memory location. It then calls - /// reduce, in hope to get the original memory location back. - /// - void printMemReduction(SILFunction &Fn) { - LSLocation L; - LSLocationList Locs; - LSLocationList SLocs; - unsigned Counter = 0; - for (auto &BB : Fn) { - for (auto &II : BB) { - - // Expand it first. - // - if (auto *LI = dyn_cast(&II)) { - SILValue Mem = LI->getOperand(); - SILValue UO = getUnderlyingObject(Mem); - L.init(UO, ProjectionPath::getProjectionPath(UO, Mem)); - if (!L.isValid()) - continue; - LSLocation::expand(L, &Fn.getModule(), TypeExpansionContext(Fn), Locs, - TE); - } else if (auto *SI = dyn_cast(&II)) { - SILValue Mem = SI->getDest(); - SILValue UO = getUnderlyingObject(Mem); - L.init(UO, ProjectionPath::getProjectionPath(UO, Mem)); - if (!L.isValid()) - continue; - LSLocation::expand(L, &Fn.getModule(), TypeExpansionContext(Fn), Locs, - TE); - } else { - // Not interested in these instructions yet. - continue; - } - - // Try to reduce it. - // - // Reduction should not care about the order of the memory locations in - // the set. - for (auto I = Locs.begin(); I != Locs.end(); ++I) { - SLocs.push_back(*I); - } - - // This should get the original (unexpanded) location back. - LSLocation::reduce(L, &Fn.getModule(), TypeExpansionContext(Fn), SLocs); - llvm::outs() << "#" << Counter++ << II; - for (auto &Loc : SLocs) { - Loc.print(llvm::outs()); - } - L.reset(); - Locs.clear(); - SLocs.clear(); - } - } - llvm::outs() << "\n"; - } - - void run() override { - for (auto &Fn : *getModule()) { - if (Fn.isExternalDeclaration()) continue; - - // Initialize the type expansion analysis. - TE = PM->getAnalysis(); - - llvm::outs() << "@" << Fn.getName() << "\n"; - switch (LSLocationKinds) { - case MLKind::OnlyTypeExpansion: - printTypeExpansionWithProjection(Fn); - break; - case MLKind::OnlyExpansion: - printMemExpansion(Fn); - break; - case MLKind::OnlyReduction: - printMemReduction(Fn); - break; - default: - break; - } - } - } - -}; - -} // end anonymous namespace - -SILTransform *swift::createLSLocationPrinter() { - return new LSLocationPrinter(); -} diff --git a/lib/SILOptimizer/Utils/CMakeLists.txt b/lib/SILOptimizer/Utils/CMakeLists.txt index f3fcb7cbfe420..efca12eb257fd 100644 --- a/lib/SILOptimizer/Utils/CMakeLists.txt +++ b/lib/SILOptimizer/Utils/CMakeLists.txt @@ -20,7 +20,6 @@ target_sources(swiftSILOptimizer PRIVATE KeyPathProjector.cpp LexicalDestroyFolding.cpp LexicalDestroyHoisting.cpp - LoadStoreOptUtils.cpp LoopUtils.cpp OptimizerStatsUtils.cpp PartialApplyCombiner.cpp diff --git a/lib/SILOptimizer/Utils/LoadStoreOptUtils.cpp b/lib/SILOptimizer/Utils/LoadStoreOptUtils.cpp deleted file mode 100644 index b5717fccf807b..0000000000000 --- a/lib/SILOptimizer/Utils/LoadStoreOptUtils.cpp +++ /dev/null @@ -1,349 +0,0 @@ -//===--- LoadStoreOptUtils.cpp --------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2017 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 -// -//===----------------------------------------------------------------------===// - -#define DEBUG_TYPE "sil-lsbase" -#include "swift/SILOptimizer/Utils/LoadStoreOptUtils.h" -#include "swift/SIL/InstructionUtils.h" -#include "swift/SILOptimizer/Utils/InstOptUtils.h" -#include "llvm/Support/Debug.h" - -using namespace swift; - -//===----------------------------------------------------------------------===// -// Utility Functions -//===----------------------------------------------------------------------===// -static void -removeLSLocations(LSLocationValueMap &Values, LSLocationList &NextLevel) { - for (auto &X : NextLevel) { - Values.erase(X); - } -} - -//===----------------------------------------------------------------------===// -// LSValue -//===----------------------------------------------------------------------===// -void LSValue::expand(SILValue Base, SILModule *M, TypeExpansionContext context, - LSValueList &Vals, TypeExpansionAnalysis *TE) { - for (const auto &P : TE->getTypeExpansion((*Base).getType(), M, context)) { - Vals.push_back(LSValue(Base, P.value())); - } -} - -void LSValue::reduceInner(LSLocation &Base, SILModule *M, - LSLocationValueMap &Values, SILInstruction *InsertPt) { - TypeExpansionContext context(*InsertPt->getFunction()); - - // If this is a class reference type, we have reached end of the type tree. - if (Base.getType(M, context).getClassOrBoundGenericClass()) - return; - - // This a don't expand node. - if (!shouldExpand(*M, Base.getType(M, context))) { - return; - } - - // This is a leaf node, we must have a value for it. - LSLocationList NextLevel; - Base.getNextLevelLSLocations(NextLevel, M, context); - if (NextLevel.empty()) - return; - - // This is not a leaf node, reduce the next level node one by one. - for (auto &X : NextLevel) { - LSValue::reduceInner(X, M, Values, InsertPt); - } - - // This is NOT a leaf node, we need to construct a value for it. - auto Iter = NextLevel.begin(); - - // Don't make this a reference! It may be invalidated as soon as the Values - // map is modified, e.g. later at Values[Base] = ... - LSValue FirstVal = Values[*Iter]; - - // There is only 1 children node and its value's projection path is not - // empty, keep stripping it. - if (NextLevel.size() == 1 && !FirstVal.hasEmptyProjectionPath()) { - Values[Base] = FirstVal.stripLastLevelProjection(); - // We have a value for the parent, remove all the values for children. - removeLSLocations(Values, NextLevel); - return; - } - - bool HasIdenticalBase = true; - SILValue FirstBase = FirstVal.getBase(); - for (auto &X : NextLevel) { - HasIdenticalBase &= (FirstBase == Values[X].getBase()); - } - - // This is NOT a leaf node and it has multiple children, but they have the - // same value base. - if (NextLevel.size() > 1 && HasIdenticalBase) { - if (!FirstVal.hasEmptyProjectionPath()) { - Values[Base] = FirstVal.stripLastLevelProjection(); - // We have a value for the parent, remove all the values for children. - removeLSLocations(Values, NextLevel); - return; - } - } - - // In 3 cases do we need aggregation. - // - // 1. If there is only 1 child and we cannot strip off any projections, - // that means we need to create an aggregation. - // - // 2. There are multiple children and they have the same base, but empty - // projection paths. - // - // 3. Children have values from different bases, We need to create - // extractions and aggregation in this case. - // - llvm::SmallVector Vals; - for (auto &X : NextLevel) { - Vals.push_back(Values[X].materialize(InsertPt)); - } - SILBuilder Builder(InsertPt); - Builder.setCurrentDebugScope(InsertPt->getFunction()->getDebugScope()); - - // We use an auto-generated SILLocation for now. - NullablePtr AI = - Projection::createAggFromFirstLevelProjections( - Builder, RegularLocation::getAutoGeneratedLocation(), - Base.getType(M, context).getObjectType(), Vals); - - auto AvailVal = makeValueAvailable(AI.get(), InsertPt->getParent()); - - // This is the Value for the current base. - ProjectionPath P(Base.getType(M, context)); - Values[Base] = LSValue(AvailVal, P); - removeLSLocations(Values, NextLevel); -} - -SILValue LSValue::reduce(LSLocation &Base, SILModule *M, - LSLocationValueMap &Values, SILInstruction *InsertPt) { - LSValue::reduceInner(Base, M, Values, InsertPt); - // Finally materialize and return the forwarding SILValue. - return Values.begin()->second.materialize(InsertPt); -} - -//===----------------------------------------------------------------------===// -// LSLocation -//===----------------------------------------------------------------------===// -bool -LSLocation::isMustAliasLSLocation(const LSLocation &RHS, AliasAnalysis *AA) { - // If the bases are not must-alias, the locations may not alias. - if (!AA->isMustAlias(Base, RHS.getBase())) - return false; - // If projection paths are different, then the locations cannot alias. - if (!hasIdenticalProjectionPath(RHS)) - return false; - // Must-alias base and identical projection path. Same object!. - return true; -} - -bool -LSLocation::isMayAliasLSLocation(const LSLocation &RHS, AliasAnalysis *AA) { - // If the bases do not alias, then the locations cannot alias. - if (AA->isNoAlias(Base, RHS.getBase())) - return false; - // If one projection path is a prefix of another, then the locations - // could alias. - if (hasNonEmptySymmetricPathDifference(RHS)) - return false; - // We can not prove the 2 locations do not alias. - return true; -} - -void LSLocation::getNextLevelLSLocations(LSLocationList &Locs, SILModule *Mod, - TypeExpansionContext context) { - SILType Ty = getType(Mod, context); - llvm::SmallVector Out; - Projection::getFirstLevelProjections(Ty, *Mod, context, Out); - for (auto &X : Out) { - ProjectionPath P((*Base).getType()); - P.append(Path.value()); - P.append(X); - Locs.push_back(LSLocation(Base, P)); - } -} - -void LSLocation::expand(LSLocation Base, SILModule *M, - TypeExpansionContext context, LSLocationList &Locs, - TypeExpansionAnalysis *TE) { - const ProjectionPath &BasePath = Base.getPath().value(); - for (const auto &P : - TE->getTypeExpansion(Base.getType(M, context), M, context)) { - Locs.push_back(LSLocation(Base.getBase(), BasePath, P.value())); - } -} - -/// Gets the sub-locations of \p Base in \p SubLocations. -/// Returns false if this is not possible or too complex. -static bool getSubLocations(LSLocationList &SubLocations, LSLocation Base, - SILModule *M, TypeExpansionContext context, - const LSLocationList &Locs) { - // If this is a class reference type, we have reached end of the type tree. - if (Base.getType(M, context).getClassOrBoundGenericClass()) - return false; - - // Don't expand if it would be too complex. As Locs is a list (and not a set) - // we want to avoid quadratic complexity in replaceInner(). - // Usually Locs is small anyway, because we limit expansion to 6 members. - // But with deeply nested types we could run in a corner case where Locs is - // large. - if (!shouldExpand(*M, Base.getType(M, context)) || Locs.size() >= 8) { - return false; - } - - // This is a leaf node. - Base.getNextLevelLSLocations(SubLocations, M, context); - return !SubLocations.empty(); -} - -/// Replaces \p SubLocations with \p Base in \p Locs if all sub-locations are -/// alive, i.e. present in \p Locs. -static bool replaceSubLocations(LSLocation Base, SILModule *M, - TypeExpansionContext context, - LSLocationList &Locs, - const LSLocationList &SubLocations) { - // Find whether all its children of Base are alive. - bool Alive = true; - for (auto &X : SubLocations) { - // Recurse into the next level. - LSLocationList NextInnerLevel; - if (getSubLocations(NextInnerLevel, X, M, context, Locs)) { - Alive &= replaceSubLocations(X, M, context, Locs, NextInnerLevel); - } else { - Alive &= is_contained(Locs, X); - } - } - - // All next level locations are alive, create the new aggregated location. - if (!Alive) - return false; - - auto newEnd = std::remove_if(Locs.begin(), Locs.end(), [&](const LSLocation &L) { - return is_contained(SubLocations, L); - }); - Locs.erase(newEnd, Locs.end()); - Locs.push_back(Base); - return true; -} - -void LSLocation::reduce(LSLocation Base, SILModule *M, - TypeExpansionContext context, LSLocationList &Locs) { - LSLocationList SubLocations; - if (getSubLocations(SubLocations, Base, M, context, Locs)) - replaceSubLocations(Base, M, context, Locs, SubLocations); -} - -std::pair -LSLocation::getBaseAddressOrObject(SILValue v, bool stopAtImmutable) { - bool isImmutable = false; - while (true) { - if (auto *rea = dyn_cast(v)) { - if (rea->isImmutable()) { - isImmutable = true; - if (stopAtImmutable) - return {v, true}; - } - } - if (auto *rta = dyn_cast(v)) { - if (rta->isImmutable()) { - isImmutable = true; - if (stopAtImmutable) - return {v, true}; - } - } - SILValue v2 = stripCastsWithoutMarkDependence(v); - v2 = stripSinglePredecessorArgs(v2); - if (Projection::isAddressProjection(v2)) - v2 = cast(v2)->getOperand(0); - v2 = stripIndexingInsts(v2); - v2 = lookThroughOwnershipInsts(v2); - if (v2 == v) - return {v2, isImmutable}; - v = v2; - } -} - -bool LSLocation::enumerateLSLocation(TypeExpansionContext context, SILModule *M, - SILValue Mem, - std::vector &Locations, - LSLocationIndexMap &IndexMap, - LSLocationBaseMap &BaseMap, - TypeExpansionAnalysis *TypeCache, - bool stopAtImmutable) { - // We have processed this SILValue before. - if (BaseMap.find(Mem) != BaseMap.end()) - return false; - - // Construct a Location to represent the memory written by this instruction. - // ProjectionPath currently does not handle mark_dependence so stop our - // underlying object search at these instructions. - // We still get a benefit if we cse mark_dependence instructions and then - // merge loads from them. - auto baseAndImmutable = getBaseAddressOrObject(Mem, stopAtImmutable); - SILValue UO = baseAndImmutable.first; - LSLocation L(UO, ProjectionPath::getProjectionPath(UO, Mem)); - - // If we can't figure out the Base or Projection Path for the memory location, - // simply ignore it for now. - if (!L.isValid()) - return false; - - // Record the SILValue to location mapping. - BaseMap[Mem] = L; - - // Expand the given Mem into individual fields and add them to the - // locationvault. - LSLocationList Locs; - LSLocation::expand(L, M, context, Locs, TypeCache); - for (auto &Loc : Locs) { - if (IndexMap.find(Loc) != IndexMap.end()) - continue; - IndexMap[Loc] = Locations.size(); - Locations.push_back(Loc); - } - return baseAndImmutable.first; -} - -void -LSLocation::enumerateLSLocations(SILFunction &F, - std::vector &Locations, - LSLocationIndexMap &IndexMap, - LSLocationBaseMap &BaseMap, - TypeExpansionAnalysis *TypeCache, - bool stopAtImmutable, - int &numLoads, int &numStores, - bool &immutableLoadsFound) { - // Enumerate all locations accessed by the loads or stores. - for (auto &B : F) { - for (auto &I : B) { - if (auto *LI = dyn_cast(&I)) { - if (enumerateLSLocation(F.getTypeExpansionContext(), &I.getModule(), - LI->getOperand(), Locations, IndexMap, BaseMap, - TypeCache, stopAtImmutable)) { - immutableLoadsFound = true; - } - ++numLoads; - continue; - } - if (auto *SI = dyn_cast(&I)) { - enumerateLSLocation(F.getTypeExpansionContext(), &I.getModule(), - SI->getDest(), Locations, IndexMap, BaseMap, - TypeCache, stopAtImmutable); - ++numStores; - continue; - } - } - } -} diff --git a/test/SILOptimizer/accessutils.sil b/test/SILOptimizer/accessutils.sil index ccc8fb4fb7123..9dd04a004f3c0 100644 --- a/test/SILOptimizer/accessutils.sil +++ b/test/SILOptimizer/accessutils.sil @@ -338,6 +338,32 @@ bb0(%0 : $MyArray): return %14 : $() } +sil @yieldTwoAddresses : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) + +// CHECK-LABEL: Accesses for testBeginApply +// CHECK-NEXT: Value: (**%1**, %2, %3) = begin_apply %0() : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) // user: %4 +// CHECK-NEXT: Scope: base +// CHECK-NEXT: Base: yield - (**%1**, %2, %3) = begin_apply %0() : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) // user: %4 +// CHECK-NEXT: Path: "" +// CHECK-NEXT: no Storage paths +// CHECK-NEXT: Value: (%1, **%2**, %3) = begin_apply %0() : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) // user: %5 +// CHECK-NEXT: Scope: base +// CHECK-NEXT: Base: yield - (%1, **%2**, %3) = begin_apply %0() : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) // user: %5 +// CHECK-NEXT: Path: "" +// CHECK-NEXT: no Storage paths +// CHECK-NEXT: End accesses for testBeginApply +sil @testBeginApply : $@convention(thin) () -> () { +bb0: + %0 = function_ref @yieldTwoAddresses : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) + (%1, %2, %3) = begin_apply %0() : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) + %4 = load %1 : $*String + %5 = load %2 : $*Int64 + end_apply %3 + %7 = tuple () + return %7 : $() +} + + sil_global @global1 : $Int64 sil_global @global2 : $Int64 diff --git a/test/SILOptimizer/alias-crash.sil b/test/SILOptimizer/alias-crash.sil index 39fd07bbfae2a..c5fd2802f009d 100644 --- a/test/SILOptimizer/alias-crash.sil +++ b/test/SILOptimizer/alias-crash.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elim | %FileCheck %s +// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elimination | %FileCheck %s sil_stage canonical diff --git a/test/SILOptimizer/array_contentof_opt.swift b/test/SILOptimizer/array_contentof_opt.swift index 052600212a45a..4671fd11631ad 100644 --- a/test/SILOptimizer/array_contentof_opt.swift +++ b/test/SILOptimizer/array_contentof_opt.swift @@ -24,8 +24,6 @@ public func testInt(_ a: inout [Int]) { } // CHECK-LABEL: sil {{.*}}@{{.*}}testThreeInts -// CHECK-DAG: [[FR:%[0-9]+]] = function_ref @${{.*(reserveCapacity|_createNewBuffer)}} -// CHECK-DAG: apply [[FR]] // CHECK-DAG: [[F:%[0-9]+]] = function_ref @$sSa6appendyyxnFSi_Tg5 // CHECK-DAG: apply [[F]] // CHECK-DAG: apply [[F]] diff --git a/test/SILOptimizer/basic-aa.sil b/test/SILOptimizer/basic-aa.sil index f01f29fc02314..5e2eefdfbba02 100644 --- a/test/SILOptimizer/basic-aa.sil +++ b/test/SILOptimizer/basic-aa.sil @@ -628,3 +628,21 @@ bb0(%0 : $P): return %6 : $() } +// CHECK-LABEL: @testCowMutation +// CHECK: PAIR #23. +// CHECK-NEXT: %4 = ref_tail_addr %0 : $X, $Int32 +// CHECK-NEXT: %5 = ref_tail_addr %3 : $X, $Int32 +// CHECK-NEXT: MustAlias +sil @testCowMutation : $@convention(thin) () -> () { +bb0: + %0 = alloc_ref $X + (%1, %2) = begin_cow_mutation %0 : $X + %3 = end_cow_mutation %2 : $X + %4 = ref_tail_addr %0 : $X, $Int32 + %5 = ref_tail_addr %3 : $X, $Int32 + fix_lifetime %4 : $*Int32 + fix_lifetime %5 : $*Int32 + %99 = tuple () + return %99 : $() +} + diff --git a/test/SILOptimizer/early-rle.sil b/test/SILOptimizer/early-rle.sil index 194a99850e46d..a0991c84fbc3e 100644 --- a/test/SILOptimizer/early-rle.sil +++ b/test/SILOptimizer/early-rle.sil @@ -1,4 +1,6 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -early-redundant-load-elim | %FileCheck %s +// RUN: %target-sil-opt -enable-sil-verify-all %s -early-redundant-load-elimination | %FileCheck %s + +// REQUIRES: swift_in_compiler sil_stage canonical diff --git a/test/SILOptimizer/global_init_opt.swift b/test/SILOptimizer/global_init_opt.swift index 9fb1246b0287a..95c3080536cfa 100644 --- a/test/SILOptimizer/global_init_opt.swift +++ b/test/SILOptimizer/global_init_opt.swift @@ -1,5 +1,7 @@ // RUN: %target-swift-frontend -parse-as-library -O -module-name=test %s -emit-sil | %FileCheck %s +// REQUIRES: swift_in_compiler + var gg: Int = { print("gg init") return 27 diff --git a/test/SILOptimizer/lslocation_expansion.sil b/test/SILOptimizer/lslocation_expansion.sil deleted file mode 100644 index 73b6251f36256..0000000000000 --- a/test/SILOptimizer/lslocation_expansion.sil +++ /dev/null @@ -1,263 +0,0 @@ -// RUN: %target-sil-opt -enable-expand-all %s -lslocation-dump -ml=only-expansion | %FileCheck %s - -sil_stage canonical - -import Builtin - -/////////////////////// -// Type Declarations // -/////////////////////// - -struct Int { - var value : Builtin.Int64 -} - -struct Int64 { - var value : Builtin.Int64 -} - -struct Bool { - var value : Builtin.Int1 -} - -class B { - var i : Builtin.Int32 - init() -} - -struct S1 { - var a: Int - init(a: Int, b: Int) - init() -} - -struct S2 { - var a: Int - var b: Int - init(a: Int, b: Int) - init() -} - -struct S3 { - var a: S2 - var b: Int - var c: S2 - init(a: S2, b: Int, c: S2) - init() -} - -struct S4 { - var x: S3 - var y: S3 - init(x: S3, y: S3) - init() -} - -class SelfLoop { - var a: Int - var b: Int - var c: SelfLoop - deinit - init() -} - -struct S5 { - var a: SelfLoop - init(a: SelfLoop) - init() -} - -sil @S1_init : $@convention(thin) (@thin S1.Type) -> S1 -sil @S2_init : $@convention(thin) (@thin S2.Type) -> S2 -sil @S3_init : $@convention(thin) (@thin S3.Type) -> S3 -sil @S4_init : $@convention(thin) (@thin S4.Type) -> S4 -sil @S5_init : $@convention(thin) (@thin S5.Type) -> S5 - -// CHECK-LABEL: @stack_store -// CHECK: #0 store -// CHECK-NEXT: alloc_stack -sil @stack_store : $@convention(thin) () -> () { - %1 = alloc_stack $Builtin.Int64 - %9 = integer_literal $Builtin.Int64, 0 - store %9 to %1 : $*Builtin.Int64 - %4 = tuple() - dealloc_stack %1 : $*Builtin.Int64 // id: %13 - return %4 : $() -} - -// CHECK-LABEL: @store_after_store -// CHECK: #0 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_box -// CHECK: #1 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_box -sil @store_after_store : $@convention(thin) (@owned B) -> () { -bb0(%0 : $B): - %1 = alloc_box $<τ_0_0> { var τ_0_0 } - %1a = project_box %1 : $<τ_0_0> { var τ_0_0 } , 0 - store %0 to %1a : $*B - store %0 to %1a : $*B - %4 = tuple() - return %4 : $() -} - -// CHECK-LABEL: @store_after_store_struct -// CHECK: #0 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_stack -// CHECK-NEXT: Projection Path [$*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK: #1 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_stack -// CHECK-NEXT: Projection Path [$*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -sil @store_after_store_struct : $@convention(thin) () -> () { - %1 = alloc_stack $S1 - %9 = integer_literal $Builtin.Int64, 0 // user: %10 - %10 = struct $Int (%9 : $Builtin.Int64) // user: %12 - %11 = struct_element_addr %1 : $*S1, #S1.a // user: %12 - store %10 to %11 : $*Int // id: %12 - store %10 to %11 : $*Int - %4 = tuple() - dealloc_stack %1 : $*S1 // id: %13 - return %4 : $() -} - -// Make sure all the structs get expanded correctly. -// -// CHECK-LABEL: @many_struct_allocs -// CHECK: #0 store -// CHECK-NEXT: alloc_stack $S2 -// CHECK-NEXT: Projection Path [$*S2 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S2 -// CHECK-NEXT: Projection Path [$*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK: #1 store -// CHECK-NEXT: alloc_stack $S3 -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var c: S2 in: $*S2 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S3 -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var c: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S3 -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S3 -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S3 -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK: #2 store -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var y: S3 in: $*S3 -// CHECK-NEXT: Field: var c: S2 in: $*S2 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var y: S3 in: $*S3 -// CHECK-NEXT: Field: var c: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var y: S3 in: $*S3 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var y: S3 in: $*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var y: S3 in: $*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var x: S3 in: $*S3 -// CHECK-NEXT: Field: var c: S2 in: $*S2 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var x: S3 in: $*S3 -// CHECK-NEXT: Field: var c: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var x: S3 in: $*S3 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var x: S3 in: $*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: alloc_stack $S4 -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var x: S3 in: $*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -sil hidden @many_struct_allocs : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S2, var, name "a" // users: %6, %18 - %1 = alloc_stack $S3, var, name "b" // users: %10, %17 - %2 = alloc_stack $S4, var, name "c" // users: %14, %16 - %3 = function_ref @S2_init : $@convention(thin) (@thin S2.Type) -> S2 // user: %5 - %4 = metatype $@thin S2.Type // user: %5 - %5 = apply %3(%4) : $@convention(thin) (@thin S2.Type) -> S2 // user: %6 - store %5 to %0 : $*S2 // id: %6 - // function_ref struct.S3.init (struct.S3.Type)() -> struct.S3 - %7 = function_ref @S3_init : $@convention(thin) (@thin S3.Type) -> S3 // user: %9 - %8 = metatype $@thin S3.Type // user: %9 - %9 = apply %7(%8) : $@convention(thin) (@thin S3.Type) -> S3 // user: %10 - store %9 to %1 : $*S3 // id: %10 - %11 = function_ref @S4_init : $@convention(thin) (@thin S4.Type) -> S4 // user: %13 - %12 = metatype $@thin S4.Type // user: %13 - %13 = apply %11(%12) : $@convention(thin) (@thin S4.Type) -> S4 // user: %14 - store %13 to %2 : $*S4 // id: %14 - %15 = tuple () // user: %19 - dealloc_stack %2 : $*S4 // id: %16 - dealloc_stack %1 : $*S3 // id: %17 - dealloc_stack %0 : $*S2 // id: %18 - return %15 : $() // id: %19 -} - -// CHECK-LABEL: self_loop -// CHECK: #0 store -// CHECK-NEXT: alloc_stack $S5, var, name "b"{{.*}} // users: %7, %4 -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var a: SelfLoop in: $*SelfLoop] -sil hidden @self_loop : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S5, var, name "b" // users: %4, %7 - %1 = function_ref @S5_init : $@convention(thin) (@thin S5.Type) -> S5 // user: %3 - %2 = metatype $@thin S5.Type // user: %3 - %3 = apply %1(%2) : $@convention(thin) (@thin S5.Type) -> S5 // users: %4, %5 - store %3 to %0 : $*S5 // id: %4 - release_value %3 : $S5 // id: %5 - %6 = tuple () // user: %8 - dealloc_stack %0 : $*S5 // id: %7 - return %6 : $() // id: %8 -} diff --git a/test/SILOptimizer/lslocation_reduction.sil b/test/SILOptimizer/lslocation_reduction.sil deleted file mode 100644 index 372f8a3f1adf1..0000000000000 --- a/test/SILOptimizer/lslocation_reduction.sil +++ /dev/null @@ -1,172 +0,0 @@ -// RUN: %target-sil-opt %s -lslocation-dump -ml=only-reduction | %FileCheck %s - -sil_stage canonical - -import Builtin - -/////////////////////// -// Type Declarations // -/////////////////////// - -struct Int { - var value : Builtin.Int64 -} - -struct Int64 { - var value : Builtin.Int64 -} - -struct Bool { - var value : Builtin.Int1 -} - -class B { - var i : Builtin.Int32 - init() -} - -struct S1 { - var a: Int - init(a: Int, b: Int) - init() -} - -struct S2 { - var a: Int - var b: Int - init(a: Int, b: Int) - init() -} - -struct S3 { - var a: S2 - var b: Int - var c: S2 - init(a: S2, b: Int, c: S2) - init() -} - -struct S4 { - var x: S3 - var y: S3 - init(x: S3, y: S3) - init() -} - -class SelfLoop { - var a: Int - var b: Int - var c: SelfLoop - deinit - init() -} - -struct S5 { - var a: SelfLoop - init(a: SelfLoop) - init() -} - -sil @S1_init : $@convention(thin) (@thin S1.Type) -> S1 -sil @S2_init : $@convention(thin) (@thin S2.Type) -> S2 -sil @S3_init : $@convention(thin) (@thin S3.Type) -> S3 -sil @S4_init : $@convention(thin) (@thin S4.Type) -> S4 -sil @S5_init : $@convention(thin) (@thin S5.Type) -> S5 - -// CHECK-LABEL: @stack_store -// CHECK: #0 store -// CHECK-NEXT: alloc_stack -sil @stack_store : $@convention(thin) () -> () { - %1 = alloc_stack $Builtin.Int64 - %9 = integer_literal $Builtin.Int64, 0 - store %9 to %1 : $*Builtin.Int64 - %4 = tuple() - dealloc_stack %1 : $*Builtin.Int64 // id: %13 - return %4 : $() -} - -// CHECK-LABEL: @store_after_store -// CHECK: #0 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_box -// CHECK: #1 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_box -sil @store_after_store : $@convention(thin) (@owned B) -> () { -bb0(%0 : $B): - %1 = alloc_box $<τ_0_0> { var τ_0_0 } - %1a = project_box %1 : $<τ_0_0> { var τ_0_0 } , 0 - store %0 to %1a : $*B - store %0 to %1a : $*B - %4 = tuple() - return %4 : $() -} - -// CHECK-LABEL: @store_after_store_struct -// CHECK: #0 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_stack -// CHECK-NEXT: Projection Path [$*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int] -// CHECK: #1 store -// CHECK-NEXT: [[RET0:%.+]] = alloc_stack -// CHECK-NEXT: Projection Path [$*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int] -sil @store_after_store_struct : $@convention(thin) () -> () { - %1 = alloc_stack $S1 - %9 = integer_literal $Builtin.Int64, 0 // user: %10 - %10 = struct $Int (%9 : $Builtin.Int64) // user: %12 - %11 = struct_element_addr %1 : $*S1, #S1.a // user: %12 - store %10 to %11 : $*Int // id: %12 - store %10 to %11 : $*Int - %4 = tuple() - dealloc_stack %1 : $*S1 // id: %13 - return %4 : $() -} - -// Make sure all the structs get expanded correctly. -// -// CHECK-LABEL: @many_struct_allocs -// CHECK: #0 store -// CHECK-NEXT: alloc_stack $S2 -// CHECK: #1 store -// CHECK-NEXT: alloc_stack $S3 -// CHECK: #2 store -// CHECK-NEXT: alloc_stack $S4 -sil hidden @many_struct_allocs : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S2, var, name "a" // users: %6, %18 - %1 = alloc_stack $S3, var, name "b" // users: %10, %17 - %2 = alloc_stack $S4, var, name "c" // users: %14, %16 - %3 = function_ref @S2_init : $@convention(thin) (@thin S2.Type) -> S2 // user: %5 - %4 = metatype $@thin S2.Type // user: %5 - %5 = apply %3(%4) : $@convention(thin) (@thin S2.Type) -> S2 // user: %6 - store %5 to %0 : $*S2 // id: %6 - // function_ref struct.S3.init (struct.S3.Type)() -> struct.S3 - %7 = function_ref @S3_init : $@convention(thin) (@thin S3.Type) -> S3 // user: %9 - %8 = metatype $@thin S3.Type // user: %9 - %9 = apply %7(%8) : $@convention(thin) (@thin S3.Type) -> S3 // user: %10 - store %9 to %1 : $*S3 // id: %10 - %11 = function_ref @S4_init : $@convention(thin) (@thin S4.Type) -> S4 // user: %13 - %12 = metatype $@thin S4.Type // user: %13 - %13 = apply %11(%12) : $@convention(thin) (@thin S4.Type) -> S4 // user: %14 - store %13 to %2 : $*S4 // id: %14 - %15 = tuple () // user: %19 - dealloc_stack %2 : $*S4 // id: %16 - dealloc_stack %1 : $*S3 // id: %17 - dealloc_stack %0 : $*S2 // id: %18 - return %15 : $() // id: %19 -} - -// CHECK-LABEL: self_loop -// CHECK: #0 store -// CHECK-NEXT: alloc_stack $S5, var, name "b"{{.*}} // users: %7, %4 -sil hidden @self_loop : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S5, var, name "b" // users: %4, %7 - %1 = function_ref @S5_init : $@convention(thin) (@thin S5.Type) -> S5 // user: %3 - %2 = metatype $@thin S5.Type // user: %3 - %3 = apply %1(%2) : $@convention(thin) (@thin S5.Type) -> S5 // users: %4, %5 - store %3 to %0 : $*S5 // id: %4 - release_value %3 : $S5 // id: %5 - %6 = tuple () // user: %8 - dealloc_stack %0 : $*S5 // id: %7 - return %6 : $() // id: %8 -} diff --git a/test/SILOptimizer/lslocation_type_only_expansion.sil b/test/SILOptimizer/lslocation_type_only_expansion.sil deleted file mode 100644 index 5c59f0617ef33..0000000000000 --- a/test/SILOptimizer/lslocation_type_only_expansion.sil +++ /dev/null @@ -1,311 +0,0 @@ -// RUN: %target-sil-opt %s -lslocation-dump -ml=only-type-expansion | %FileCheck %s -// RUN: %target-sil-opt %s -lslocation-dump-use-new-projection -lslocation-dump -ml=only-type-expansion | %FileCheck %s - -sil_stage canonical - -import Builtin - -struct Int { - var value : Builtin.Int64 -} - -struct Int32{ - var value : Builtin.Int32 -} - -struct Int64 { - var value : Builtin.Int64 -} - -struct Bool { - var value : Builtin.Int1 -} - -struct S1 { - var a: Int - var b: Int - init(a: Int, b: Int) - init() -} - -struct S2 { - var a: Int - var b: S1 - init(a: Int, b: S1) - init() -} - -class C1 { - var a : Int - deinit - init(a: Int) -} - -struct S3 { - var c: C1 - var a: S2 - var b: C1 - init(c: C1, a: S2, b: C1) - init() -} - -struct S4 { - var c: (Int, Int, S1) - init(c: (Int, Int, S1)) - init() -} - -struct S5 { - var c: (Int, Int, S3) - init(c: (Int, Int, S3)) - init() -} - -struct S6 { - var tuple: (Int, Int) -} - -enum Example { - case A(Int64) - case B(Int32) -} - - -sil @S1_init : $@convention(thin) (@thin S1.Type) -> S1 -sil @S2_init : $@convention(thin) (@thin S2.Type) -> S2 -sil @C1_init : $@convention(thin) (@thick C1.Type) -> @owned C1 -sil @S3_init : $@convention(thin) (@thin S3.Type) -> @owned S3 -sil @S4_init : $@convention(thin) (@thin S4.Type) -> @owned S4 -sil @S5_init : $@convention(thin) (@thin S5.Type) -> @owned S5 -sil @S6_init : $@convention(thin) (@thin S6.Type) -> S6 - -// CHECK-LABEL: @test_struct_type_expansion -// CHECK: #0 store -// CHECK-NEXT: Projection Path [$*S1 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK: #1 store -// CHECK-NEXT: Projection Path [$*S2 -// CHECK-NEXT: Field: var b: S1 in: $*S1 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S2 -// CHECK-NEXT: Field: var b: S1 in: $*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -sil hidden @test_struct_type_expansion : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S1, var, name "a" // users: %5, %12 - %1 = alloc_stack $S2, var, name "b" // users: %9, %11 - %2 = function_ref @S1_init : $@convention(thin) (@thin S1.Type) -> S1 // user: %4 - %3 = metatype $@thin S1.Type // user: %4 - %4 = apply %2(%3) : $@convention(thin) (@thin S1.Type) -> S1 // user: %5 - store %4 to %0 : $*S1 // id: %5 - %6 = function_ref @S2_init : $@convention(thin) (@thin S2.Type) -> S2 // user: %8 - %7 = metatype $@thin S2.Type // user: %8 - %8 = apply %6(%7) : $@convention(thin) (@thin S2.Type) -> S2 // user: %9 - store %8 to %1 : $*S2 // id: %9 - %10 = tuple () // user: %13 - dealloc_stack %1 : $*S2 // id: %11 - dealloc_stack %0 : $*S1 // id: %12 - return %10 : $() // id: %13 -} - -// Make sure we do not expand the reference type. -// -// CHECK-LABEL: test_class_stack_slot -// CHECK: #0 store -// CHECK-NOT: var -// CHECK: #1 store -sil hidden @test_class_stack_slot : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $C1, var, name "a" // users: %4, %7 - %1 = function_ref @C1_init : $@convention(thin) (@thick C1.Type) -> @owned C1 // user: %3 - %2 = metatype $@thick C1.Type // user: %3 - %3 = apply %1(%2) : $@convention(thin) (@thick C1.Type) -> @owned C1 // users: %4, %5 - store %3 to %0 : $*C1 // id: %4 - store %3 to %0 : $*C1 // id: %4 - strong_release %3 : $C1 // id: %5 - %6 = tuple () // user: %8 - dealloc_stack %0 : $*C1 // id: %7 - return %6 : $() // id: %8 -} - -// CHECK-LABEL: test_struct_and_class_slot -// CHECK: #0 store -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var b: C1 in: $*C1] -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var b: S1 in: $*S1 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var b: S1 in: $*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S3 -// CHECK-NEXT: Field: var c: C1 in: $*C1] -sil hidden @test_struct_and_class_slot : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S3, var, name "a" // users: %4, %7 - // function_ref test.S3.init (test.S3.Type)() -> test.S3 - %1 = function_ref @S3_init : $@convention(thin) (@thin S3.Type) -> @owned S3 // user: %3 - %2 = metatype $@thin S3.Type // user: %3 - %3 = apply %1(%2) : $@convention(thin) (@thin S3.Type) -> @owned S3 // users: %4, %5 - store %3 to %0 : $*S3 // id: %4 - release_value %3 : $S3 // id: %5 - %6 = tuple () // user: %8 - dealloc_stack %0 : $*S3 // id: %7 - return %6 : $() // id: %8 -} - -// Test tuple expansion. -// -// CHECK-LABEL: test_tuple -// CHECK: #0 store -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var c: (Int, Int, S1) in: $*(Int, Int, S1) -// CHECK-NEXT: Index: 2 in: $*S1 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var c: (Int, Int, S1) in: $*(Int, Int, S1) -// CHECK-NEXT: Index: 2 in: $*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var c: (Int, Int, S1) in: $*(Int, Int, S1) -// CHECK-NEXT: Index: 1 in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S4 -// CHECK-NEXT: Field: var c: (Int, Int, S1) in: $*(Int, Int, S1) -// CHECK-NEXT: Index: 0 in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -sil hidden @test_tuple : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S4, var, name "x" // users: %4, %7 - // function_ref test.S4.init (test.S4.Type)() -> test.S4 - %1 = function_ref @S4_init : $@convention(thin) (@thin S4.Type) -> @owned S4 // user: %3 - %2 = metatype $@thin S4.Type // user: %3 - %3 = apply %1(%2) : $@convention(thin) (@thin S4.Type) -> @owned S4 // users: %4, %5 - store %3 to %0 : $*S4 // id: %4 - release_value %3 : $S4 // id: %5 - %6 = tuple () // user: %8 - dealloc_stack %0 : $*S4 // id: %7 - return %6 : $() // id: %8 -} - -// CHECK-LABEL: tuple_test_with_reference -// CHECK: #0 store -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var c: (Int, Int, S3) in: $*(Int, Int, S3) -// CHECK-NEXT: Index: 2 in: $*S3 -// CHECK-NEXT: Field: var b: C1 in: $*C1] -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var c: (Int, Int, S3) in: $*(Int, Int, S3) -// CHECK-NEXT: Index: 2 in: $*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var b: S1 in: $*S1 -// CHECK-NEXT: Field: var b: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var c: (Int, Int, S3) in: $*(Int, Int, S3) -// CHECK-NEXT: Index: 2 in: $*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var b: S1 in: $*S1 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var c: (Int, Int, S3) in: $*(Int, Int, S3) -// CHECK-NEXT: Index: 2 in: $*S3 -// CHECK-NEXT: Field: var a: S2 in: $*S2 -// CHECK-NEXT: Field: var a: Int in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var c: (Int, Int, S3) in: $*(Int, Int, S3) -// CHECK-NEXT: Index: 2 in: $*S3 -// CHECK-NEXT: Field: var c: C1 in: $*C1] -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var c: (Int, Int, S3) in: $*(Int, Int, S3) -// CHECK-NEXT: Index: 1 in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -// CHECK-NEXT: Projection Path [$*S5 -// CHECK-NEXT: Field: var c: (Int, Int, S3) in: $*(Int, Int, S3) -// CHECK-NEXT: Index: 0 in: $*Int -// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -sil hidden @tuple_test_with_reference : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S5, var, name "x" // users: %4, %7 - // function_ref test.S5.init (test.S5.Type)() -> test.S5 - %1 = function_ref @S5_init : $@convention(thin) (@thin S5.Type) -> @owned S5 // user: %3 - %2 = metatype $@thin S5.Type // user: %3 - %3 = apply %1(%2) : $@convention(thin) (@thin S5.Type) -> @owned S5 // users: %4, %5 - store %3 to %0 : $*S5 // id: %4 - release_value %3 : $S5 // id: %5 - %6 = tuple () // user: %8 - dealloc_stack %0 : $*S5 // id: %7 - return %6 : $() // id: %8 -} - -/// CHECK-LABEL: tuple_inside_struct -/// CHECK: #0 store -/// CHECK-NEXT: Projection Path [$*S6 -/// CHECK-NEXT: Field: var tuple: (Int, Int) in: $*(Int, Int) -/// CHECK-NEXT: Index: 1 in: $*Int -/// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -/// CHECK-NEXT: Projection Path [$*S6 -/// CHECK-NEXT: Field: var tuple: (Int, Int) in: $*(Int, Int) -/// CHECK-NEXT: Index: 0 in: $*Int -/// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -sil hidden @tuple_inside_struct : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $S6, var, name "x" // users: %4, %7 - %1 = function_ref @S6_init : $@convention(thin) (@thin S6.Type) -> S6 // user: %3 - %2 = metatype $@thin S6.Type // user: %3 - %3 = apply %1(%2) : $@convention(thin) (@thin S6.Type) -> S6 // users: %4, %5 - store %3 to %0 : $*S6 // id: %4 - %6 = tuple () // user: %8 - dealloc_stack %0 : $*S6 // id: %7 - return %6 : $() // id: %8 -} - -/// Given an enum type, we expands it into nothing as its meaningless to call -/// getStoredProperties on enum without specifying the tag. Enum is sort of -/// like union in C, i.e. its memory can be interpreted differently depending -/// on the chosen tag. -/// -/// CHECK-LABEL: enum_test -/// CHECK: #0 store -/// CHECK-NOT: Int64 -/// CHECK-NOT: Int32 -/// CHECK: #1 store -/// CHECK-NEXT: Projection Path [$*Int -/// CHECK-NEXT: Field: var value: Builtin.Int64 in: $*Builtin.Int64] -sil hidden @enum_test : $@convention(thin) () -> () { -bb0: - %0 = alloc_stack $Example, var, name "ee" // users: %5, %11 - %1 = alloc_stack $Int, var, name "a" // users: %8, %10 - %2 = integer_literal $Builtin.Int64, 10 // user: %3 - %3 = struct $Int64 (%2 : $Builtin.Int64) // user: %4 - %4 = enum $Example, #Example.A!enumelt, %3 : $Int64 // user: %5 - store %4 to %0 : $*Example // id: %5 - %6 = integer_literal $Builtin.Int64, 10 // user: %7 - %7 = struct $Int (%6 : $Builtin.Int64) // user: %8 - store %7 to %1 : $*Int // id: %8 - %9 = tuple () // user: %12 - dealloc_stack %1 : $*Int // id: %10 - dealloc_stack %0 : $*Example // id: %11 - return %9 : $() // id: %12 -} diff --git a/test/SILOptimizer/mem-behavior.sil b/test/SILOptimizer/mem-behavior.sil index b7cbbff763dc7..975cd4f6860e7 100644 --- a/test/SILOptimizer/mem-behavior.sil +++ b/test/SILOptimizer/mem-behavior.sil @@ -1024,3 +1024,24 @@ bb0: return %r : $() } +// CHECK-LABEL: @test_store_assign +// CHECK: PAIR #0. +// CHECK-NEXT: store %2 to [assign] %0 : $*C +// CHECK-NEXT: %0 = argument of bb0 : $*C +// CHECK-NEXT: r=1,w=1 +// CHECK: PAIR #1. +// CHECK-NEXT: store %2 to [assign] %0 : $*C +// CHECK-NEXT: %1 = argument of bb0 : $*C +// CHECK-NEXT: r=0,w=0 +// CHECK: PAIR #2. +// CHECK-NEXT: store %2 to [assign] %0 : $*C +// CHECK-NEXT: %3 = global_addr @globalC : $*C +// CHECK-NEXT: r=1,w=1 +sil [ossa] @test_store_assign : $@convention(thin) (@inout C, @inout C, @owned C) -> () { +bb0(%0 : $*C, %1 : $*C, %2 : @owned $C): + %3 = global_addr @globalC : $*C + store %2 to [assign] %0 : $*C + %r = tuple () + return %r : $() +} + diff --git a/test/SILOptimizer/merge_exclusivity.swift b/test/SILOptimizer/merge_exclusivity.swift index 95442cd3c6fa0..7752f7deb3cfa 100644 --- a/test/SILOptimizer/merge_exclusivity.swift +++ b/test/SILOptimizer/merge_exclusivity.swift @@ -1,4 +1,4 @@ -// RUN: %target-swift-frontend -O -enforce-exclusivity=checked -emit-sil -primary-file %s | %FileCheck %s --check-prefix=TESTSIL +// RUN: %target-swift-frontend -O -enforce-exclusivity=checked -Xllvm -sil-disable-pass=redundant-load-elimination -emit-sil -primary-file %s | %FileCheck %s --check-prefix=TESTSIL // REQUIRES: optimized_stdlib,asserts // REQUIRES: PTRSIZE=64 diff --git a/test/SILOptimizer/redundant_load_and_dead_store_elim.sil b/test/SILOptimizer/redundant_load_and_dead_store_elim.sil index 34f83c5bb5729..03c3171f2548a 100644 --- a/test/SILOptimizer/redundant_load_and_dead_store_elim.sil +++ b/test/SILOptimizer/redundant_load_and_dead_store_elim.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elim -dead-store-elimination | %FileCheck %s +// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elimination -dead-store-elimination | %FileCheck %s // REQUIRES: swift_in_compiler diff --git a/test/SILOptimizer/redundant_load_elim.sil b/test/SILOptimizer/redundant_load_elim.sil index 415161510009d..1f2a5f099c204 100644 --- a/test/SILOptimizer/redundant_load_elim.sil +++ b/test/SILOptimizer/redundant_load_elim.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -enforce-exclusivity=none -enable-sil-verify-all %s -redundant-load-elim | %FileCheck %s +// RUN: %target-sil-opt -enforce-exclusivity=none -enable-sil-verify-all %s -redundant-load-elimination | %FileCheck %s // REQUIRES: swift_in_compiler @@ -191,7 +191,7 @@ bb0(%0 : $AB): // Check that we don't crash if the address is an unchecked_addr_cast. // CHECK-LABEL: sil @test_unchecked_addr_cast -// CHECK-NOT: = load +// CHECK: = load // CHECK: return sil @test_unchecked_addr_cast : $@convention(thin) (@inout A, A) -> A { bb0(%0 : $*A, %1 : $A): @@ -204,7 +204,7 @@ bb0(%0 : $*A, %1 : $A): // Multi-BB version of the previous test. // CHECK-LABEL: sil @test_forwarding_ignoring_unchecked_addr_cast2 : $@convention(thin) (@inout A, A, A) -> A { // CHECK: bb1 -// CHECK-NOT: = load +// CHECK: = load // CHECK: cond_br sil @test_forwarding_ignoring_unchecked_addr_cast2 : $@convention(thin) (@inout A, A, A) -> A { bb0(%0 : $*A, %1 : $A, %2: $A): @@ -414,12 +414,14 @@ bb0(%0 : $Builtin.Int64): } // CHECK-LABEL: sil @load_dedup_forwarding_from_aggregate_to_field -// CHECK: bb0([[INPUT_PTR:%[0-9]+]] -// CHECK-NEXT: = load [[INPUT_PTR]] -// CHECK-NEXT: struct_extract -// CHECK-NEXT: struct_extract -// CHECK-NEXT: tuple_extract -// CHECK-NEXT: return +// CHECK: bb0(%0 : $*Agg1): +// CHECK: [[L:%.*]] = load %0 +// CHECK: [[S1:%.*]] = struct_extract [[L]] +// CHECK: [[S2:%.*]] = struct_extract [[S1]] +// CHECK: [[T:%.*]] = tuple_extract [[S2]] +// CHECK-NOT: load +// CHECK: return [[T]] +// CHECK: } // end sil function 'load_dedup_forwarding_from_aggregate_to_field' sil @load_dedup_forwarding_from_aggregate_to_field : $@convention(thin) (@inout Agg1) -> (Builtin.Int32) { bb0(%0 : $*Agg1): %1 = load %0 : $*Agg1 @@ -807,11 +809,11 @@ bb2: // Preds: bb1 bb2 // Make sure we form a single SILArgument. // -// CHECK-LABEL: single_silargument_agg_in_one_block -// CHECK: bb3([[ARG:%.*]] : $TwoField): +// CHECK-LABEL: silargument_agg_in_one_block +// CHECK: bb3([[A1:%.*]] : $Int, [[A2:%.*]] : $Int): // CHECK-NOT: = load // CHECK: return -sil hidden @single_silargument_agg_in_one_block : $@convention(thin) (Bool) -> () { +sil hidden @silargument_agg_in_one_block : $@convention(thin) (Bool) -> () { bb0(%0 : $Bool): %1 = alloc_stack $TwoField, var, name "x" // users: %5, %7, %13, %15, %19 cond_br undef, bb1, bb2 // id: %2 @@ -892,9 +894,6 @@ bb9: // Preds: bb7 bb8 return %22 : $Int // id: %24 } -// Make sure we can re-use the SILArgument inserted in bb8 for forwarding -// in bb9 and bb10. -// // CHECK-LABEL: reuse_silargument_multiple_bb_forwarding // CHECK: bb7: // CHECK: br bb10(%3 : $Int) @@ -1121,23 +1120,6 @@ bb2: return %9999 : $() } -// CHECK-LABEL: sil hidden @redundant_load_over_intermediate_release_with_epilogue_release : $@convention(thin) (@owned AB) -> () { -// CHECK: [[AD:%.*]] = ref_element_addr -// CHECK: [[AD2:%.*]] = load [[AD]] -// CHECK: release_value -// CHECK-NOT: [[AD3:%.*]] = load [[AD]] -// CHECK: return -sil hidden @redundant_load_over_intermediate_release_with_epilogue_release : $@convention(thin) (@owned AB) -> () { -bb0(%0 : $AB): - %1 = ref_element_addr %0 : $AB, #AB.value - %2 = load %1 : $*Int - release_value %0 : $AB - %3 = load %1 : $*Int - release_value %0 : $AB - %4 = tuple () - return %4 : $() -} - // CHECK-LABEL: sil hidden @redundant_load_over_intermediate_release_without_epilogue_release : $@convention(thin) (@owned AB) -> () { // CHECK: [[AD:%.*]] = ref_element_addr // CHECK: [[AD2:%.*]] = load [[AD]] @@ -1296,3 +1278,20 @@ bb0: return %9 : $Int64 } +sil @yieldTwoAddresses : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) + +// CHECK-LABEL: sil @dont_remove_load_from_different_yield_result : +// CHECK: [[L1:%[0-9]+]] = load +// CHECK: [[L2:%[0-9]+]] = load +// CHECK: tuple ([[L1]] : $String, [[L2]] : $Int64) +// CHECK-LABEL: } // end sil function 'dont_remove_load_from_different_yield_result' +sil @dont_remove_load_from_different_yield_result : $@convention(thin) () -> (String, Int64) { +bb0: + %0 = function_ref @yieldTwoAddresses : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) + (%1, %2, %3) = begin_apply %0() : $@yield_once @convention(thin) () -> (@yields @in_guaranteed String, @yields @in_guaranteed Int64) + %4 = load %1 : $*String + %5 = load %2 : $*Int64 + end_apply %3 + %7 = tuple (%4 : $String, %5 : $Int64) + return %7 : $(String, Int64) +} diff --git a/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil b/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil index a55289bfdc596..db8d7e7013139 100644 --- a/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil +++ b/test/SILOptimizer/redundant_load_elim_nontrivial_ossa.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elim | %FileCheck %s +// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elimination | %FileCheck %s // TODO : Add a version with semantic-arc-opts when #34971 is landed or DCE is enabled on OSSA // REQUIRES: swift_in_compiler @@ -36,6 +36,11 @@ struct Agg1 { var a : Agg2 } +struct Agg3 { + var k : Klass + var i : Int +} + class AB { var value: Klass var value2: Klass @@ -167,46 +172,6 @@ bb0(%0 : @owned $AB): return %copy5 : $Klass } -// Check that we don't crash if the address is an unchecked_addr_cast. -// CHECK-LABEL: sil [ossa] @test_unchecked_addr_cast : -// CHECK-NOT: = load -// CHECK-LABEL: } // end sil function 'test_unchecked_addr_cast' -sil [ossa] @test_unchecked_addr_cast : $@convention(thin) (@inout A, @owned A) -> @owned A { -bb0(%0 : $*A, %1 : @owned $A): - %2 = unchecked_addr_cast %0 : $*A to $*A - store %1 to [init] %2 : $*A - %l1 = load [take] %2 : $*A - return %l1 : $A -} - -// Multi-BB version of the previous test. -// CHECK-LABEL: sil [ossa] @test_forwarding_ignoring_unchecked_addr_cast2 : -// CHECK-NOT: = load -// CHECK-LABEL: } // end sil function 'test_forwarding_ignoring_unchecked_addr_cast2' -sil [ossa] @test_forwarding_ignoring_unchecked_addr_cast2 : $@convention(thin) (@inout A, @owned A, @owned A) -> () { -bb0(%0 : $*A, %1 : @owned $A, %2 : @owned $A): - %3 = unchecked_addr_cast %0 : $*A to $*A - store %1 to [init] %3 : $*A - br bb1 - -bb1: - %5 = load [copy] %3 : $*A - %6 = load [take] %3 : $*A - %copy2 = copy_value %2 : $A - destroy_value %5 : $A - destroy_value %6 : $A - store %copy2 to [assign] %3 : $*A - cond_br undef, bb3, bb2 - -bb3: - br bb1 - -bb2: - destroy_value %2 : $A - %res = tuple () - return %res : $() -} - // CHECK-LABEL: sil [ossa] @test_read_dependence_allows_forwarding_multi_bb_1 : // CHECK: bb0 // CHECK: store @@ -313,10 +278,13 @@ bb0(%0 : @owned $Agg1): } // CHECK-LABEL: sil [ossa] @store_promotion : -// CHECK: store -// CHECK-NEXT: destroy_addr -// CHECK-NEXT: destroy_value -// CHECK-NEXT: destroy_value +// CHECK: %1 = alloc_box +// CHECK-NEXT: project_box +// CHECK-NEXT: [[C:%.*]] = copy_value %0 +// CHECK-NEXT: destroy_value [[C]] +// CHECK-NEXT: destroy_value %0 +// CHECK-NEXT: destroy_value %1 +// CHECK-NEXT: tuple // CHECK-LABEL: } // end sil function 'store_promotion' sil [ossa] @store_promotion : $@convention(thin) (@owned Klass) -> () { bb0(%0 : @owned $Klass): @@ -333,9 +301,11 @@ bb0(%0 : @owned $Klass): } // CHECK-LABEL: promote_partial_load : -// CHECK: alloc_stack -// CHECK-NOT: = load -// CHECK: [[RESULT:%[0-9]+]] = struct_extract +// CHECK: [[S:%.*]] = struct $NonTrivialStruct +// CHECK-NOT: store +// CHECK: [[D:%.*]] = destructure_struct [[S]] +// CHECK-NOT: load +// CHECK: return [[D]] // CHECK-LABEL: } // end sil function 'promote_partial_load' sil [ossa] @promote_partial_load : $@convention(thin) (@owned Klass) -> @owned Klass { bb0(%0 : @owned $Klass): @@ -383,14 +353,11 @@ bb0(%0 : @owned $AB): return %6 : $() } -// Make sure we RLE the load in BB2. -// -// CHECK-LABEL: test_silargument_rle : -// CHECK: bb2 -// CHECK-NOT: = load -// CHECK: cond_br -// CHECK-LABEL: } // end sil function 'test_silargument_rle' -sil [ossa] @test_silargument_rle : $@convention(thin) (@owned Klass) -> () { +// CHECK-LABEL: no_rle_liferange_with_exit : +// CHECK: bb3: +// CHECK-NEXT: load +// CHECK-LABEL: } // end sil function 'no_rle_liferange_with_exit' +sil [ossa] @no_rle_liferange_with_exit : $@convention(thin) (@owned Klass) -> () { bb0(%0 : @owned $Klass): %copy0 = copy_value %0 : $Klass %g = global_addr @total_klass : $*Klass @@ -398,28 +365,28 @@ bb0(%0 : @owned $Klass): %6 = alloc_ref $AX %borrow6 = begin_borrow %6 : $AX %8 = ref_element_addr %borrow6 : $AX, #AX.current - store %copy0 to [assign] %8 : $*Klass + store %copy0 to [init] %8 : $*Klass end_borrow %borrow6 : $AX - cond_br undef, bb0a, bb0b - -bb0a: - br bb2 + cond_br undef, bb1, bb2 -bb0b: +bb1: br bb3 bb2: + br bb3 + +bb3: %9 = load [copy] %g : $*Klass store %9 to [assign] %g : $*Klass - cond_br undef, bb2b, bb2a + cond_br undef, bb5, bb4 -bb2a: - br bb2 - -bb2b: +bb4: br bb3 -bb3: +bb5: + br bb6 + +bb6: destroy_value %6 : $AX %44 = tuple () return %44 : $() @@ -466,15 +433,13 @@ bb6: // CHECK-LABEL: } // end sil function 'test_read_dependence_allows_forwarding_multi_bb_2' sil [ossa] @test_read_dependence_allows_forwarding_multi_bb_2 : $@convention(thin) (@inout A, @owned A, @owned A) -> () { bb0(%0 : $*A, %1 : @owned $A, %2 : @owned $A): - store %1 to [init] %0 : $*A - %3 = unchecked_addr_cast %0 : $*A to $*A - %4 = unchecked_addr_cast %3 : $*A to $*A + store %1 to [assign] %0 : $*A br bb1 bb1: // This means that the first store is not dead. %2c = copy_value %2 : $A - %6 = load [copy] %3 : $*A + %6 = load [copy] %0 : $*A %7 = load [copy] %0 : $*A %8 = load [take] %0 : $*A %22 = function_ref @use_a : $@convention(thin) (@owned A) -> () @@ -494,10 +459,12 @@ bb2: } // CHECK-LABEL: sil [ossa] @load_to_load_loop : -// CHECK: bb1([[BBARG:%[0-9]+]] -// CHECK-NOT: load -// CHECK: bb2: -// CHECK-NOT: load +// CHECK: bb1: +// CHECK-NEXT: load +// CHECK-NOT: load +// CHECK: bb3: +// CHECK-NEXT: load +// CHECK-NOT: load // CHECK-LABEL: } // end sil function 'load_to_load_loop' sil [ossa] @load_to_load_loop : $@convention(thin) (@owned Klass) -> () { bb0(%0 : @owned $Klass): @@ -525,12 +492,12 @@ bb1: %6 = load [copy] %0ele : $*Klass %11125 = function_ref @use_Klass : $@convention(thin) (@owned Klass) -> () %11126 = apply %11125(%6) : $@convention(thin) (@owned Klass) -> () - cond_br undef, bb1a, bb2 + cond_br undef, bb2, bb3 -bb1a: +bb2: br bb1 -bb2: +bb3: %7 = load [take] %0ele : $*Klass %111125 = function_ref @use_Klass : $@convention(thin) (@owned Klass) -> () %111126 = apply %111125(%7) : $@convention(thin) (@owned Klass) -> () @@ -603,11 +570,11 @@ bb2: // Make sure we form a single SILArgument. // -// CHECK-LABEL: single_silargument_agg_in_one_block : -// CHECK: bb3([[ARG:%.*]] : @owned $TwoField): +// CHECK-LABEL: silargument_agg_in_one_block : +// CHECK: bb3([[ARG1:%.*]] : @owned $Klass, [[ARG2:%.*]] : @owned $Klass): // CHECK-NOT: = load -// CHECK-LABEL: } // end sil function 'single_silargument_agg_in_one_block' -sil hidden [ossa] @single_silargument_agg_in_one_block : $@convention(thin) (@owned Klass, @owned Klass) -> () { +// CHECK-LABEL: } // end sil function 'silargument_agg_in_one_block' +sil hidden [ossa] @silargument_agg_in_one_block : $@convention(thin) (@owned Klass, @owned Klass) -> () { bb0(%0 : @owned $Klass, %1 : @owned $Klass): %stk = alloc_stack $TwoField, var, name "x" cond_br undef, bb1, bb2 @@ -802,10 +769,12 @@ bb2: } // CHECK-LABEL: sil hidden [ossa] @redundant_load_over_intermediate_release_with_epilogue_release : -// CHECK: [[AD:%.*]] = ref_element_addr -// CHECK: [[AD2:%.*]] = load [copy] [[AD]] -// CHECK-NOT: [[AD3:%.*]] = load [take] [[AD]] -// CHECK: destroy_value +// CHECK: [[AD:%.*]] = ref_element_addr +// CHECK: [[L:%.*]] = load [take] [[AD]] +// CHECK-NEXT: [[C:%.*]] = copy_value [[L]] +// CHECK-NOT: load +// CHECK: destroy_value [[L]] +// CHECK: destroy_value [[C]] // CHECK-LABEL: } // end sil function 'redundant_load_over_intermediate_release_with_epilogue_release' sil hidden [ossa] @redundant_load_over_intermediate_release_with_epilogue_release : $@convention(thin) (@owned AB) -> () { bb0(%0 : @owned $AB): @@ -888,12 +857,12 @@ sil @test_rle_in_guaranteed_callee : $@convention(thin) (@in_guaranteed Klass) - // CHECK-LABEL: sil [ossa] @test_rle_in_guaranteed_entry : // CHECK-NOT: load // CHECK-LABEL: } // end sil function 'test_rle_in_guaranteed_entry' -sil [ossa] @test_rle_in_guaranteed_entry : $@convention(thin) (@in Klass, @owned Klass) -> () { +sil [ossa] @test_rle_in_guaranteed_entry : $@convention(thin) (@inout Klass, @owned Klass) -> () { bb0(%0 : $*Klass, %1 : @owned $Klass): store %1 to [assign] %0 : $*Klass %f_callee = function_ref @test_rle_in_guaranteed_callee : $@convention(thin) (@in_guaranteed Klass) -> () %r1 = apply %f_callee(%0) : $@convention(thin) (@in_guaranteed Klass) -> () - %value_again = load [take] %0 : $*Klass + %value_again = load [copy] %0 : $*Klass %f_sink = function_ref @test_rle_in_guaranteed_sink : $@convention(thin) (Klass) -> () %r2 = apply %f_sink(%value_again) : $@convention(thin) (Klass) -> () destroy_value %value_again : $Klass @@ -1010,28 +979,11 @@ bb3: // CHECK-LABEL: @ignore_unreachable_complex : // CHECK: [[V:%[0-9]+]] = load [copy] -// CHECK: copy_value [[V]] -// CHECK: copy_value [[V]] +// CHECK: [[C:%.*]] = copy_value [[V]] // CHECK: bb1: -// CHECK-NEXT: destroy_value -// CHECK-NEXT: cond_br -// CHECK: bb2: -// CHECK-NEXT: destroy_value -// CHECK-NEXT: cond_br -// CHECK: bb3: -// CHECK-NEXT: destroy_value -// CHECK-NEXT: br bb5 -// CHECK: bb4: -// CHECK-NEXT: destroy_value -// CHECK-NEXT: br bb5 // CHECK: bb5: +// CHECK-NEXT: destroy_value [[C]] // CHECK-NEXT: tuple -// CHECK: bb6: -// CHECK-NEXT: br bb8 -// CHECK: bb7: -// CHECK-NEXT: br bb8 -// CHECK: bb8: -// CHECK-NEXT: unreachable // CHECK: } // end sil function 'ignore_unreachable_complex' sil [ossa] @ignore_unreachable_complex : $@convention(thin) (@in_guaranteed NonTrivialStruct) -> () { bb0(%0 : $*NonTrivialStruct): @@ -1047,16 +999,14 @@ bb2: cond_br undef, bb4, bb7 bb3: - %val2 = load [copy] %ele : $*Klass - destroy_value %val2 : $Klass br bb5 bb4: - %val3 = load [copy] %ele : $*Klass - destroy_value %val3 : $Klass br bb5 bb5: + %val2 = load [copy] %ele : $*Klass + destroy_value %val2 : $Klass %res = tuple () return %res : $() @@ -1071,11 +1021,9 @@ bb8: } // CHECK-LABEL: @infinite_loop_and_unreachable : -// CHECK: [[V:%[0-9]+]] = load [copy] -// CHECK: [[C1:%[0-9]+]] = copy_value [[V]] +// CHECK: load [copy] // CHECK: bb1: -// CHECK: [[C2:%[0-9]+]] = copy_value [[C1]] -// CHECK: destroy_value [[C2]] +// CHECK: load [copy] // CHECK: } // end sil function 'infinite_loop_and_unreachable' sil [ossa] @infinite_loop_and_unreachable : $@convention(thin) (@in_guaranteed NonTrivialStruct) -> () { bb0(%0 : $*NonTrivialStruct): @@ -1203,7 +1151,10 @@ bb14: } // CHECK-LABEL: @test_available_value3 : -// CHECK-NOT: load +// CHECK: bb9: +// CHECK-NEXT: load +// CHECK: bb13: +// CHECK-NEXT: load // CHECK: } // end sil function 'test_available_value3' sil [ossa] @test_available_value3 : $@convention(method) (@owned NonTrivialStruct) -> () { bb0(%0 : @owned $NonTrivialStruct): @@ -1279,3 +1230,96 @@ bb0(%0 : $*MyArray): %t = tuple () return %t : $() } + +// CHECK-LABEL: @load_trivial_from_store : +// CHECK: [[B:%.*]] = begin_borrow %0 +// CHECK: [[I:%.*]] = struct_extract %2 : $Agg3, #Agg3.i +// CHECK: end_borrow [[B]] +// CHECK: store %0 to [assign] %1 +// CHECK: return [[I]] +// CHECK: } // end sil function 'load_trivial_from_store' +sil [ossa] @load_trivial_from_store : $@convention(thin) (@owned Agg3, @inout Agg3) -> Int { +bb0(%0 : @owned $Agg3, %1 : $*Agg3): + store %0 to [assign] %1 : $*Agg3 + %3 = struct_element_addr %1 : $*Agg3, #Agg3.i + %4 = load [trivial] %3 : $*Int + return %4 : $Int +} + +// CHECK-LABEL: @load_copy_from_store : +// CHECK: [[B:%.*]] = begin_borrow %0 +// CHECK: [[K:%.*]] = struct_extract %2 : $Agg3, #Agg3.k +// CHECK: [[C:%.*]] = copy_value [[K]] +// CHECK: end_borrow [[B]] +// CHECK: store %0 to [assign] %1 +// CHECK: return [[C]] +// CHECK: } // end sil function 'load_copy_from_store' +sil [ossa] @load_copy_from_store : $@convention(thin) (@owned Agg3, @inout Agg3) -> @owned Klass { +bb0(%0 : @owned $Agg3, %1 : $*Agg3): + store %0 to [assign] %1 : $*Agg3 + %3 = struct_element_addr %1 : $*Agg3, #Agg3.k + %4 = load [copy] %3 : $*Klass + return %4 : $Klass +} +// CHECK-LABEL: @load_take_from_store_assign : +// CHECK: [[DS:%.*]] = destructure_struct %0 +// CHECK: [[TADDR:%.*]] = struct_element_addr %1 : $*Agg2, #Agg2.t +// CHECK: ([[T0:%.*]], [[T1:%.*]]) = destructure_tuple [[DS]] +// CHECK: [[TA0:%.*]] = tuple_element_addr [[TADDR]] : $*(Klass, Klass), 0 +// CHECK: destroy_addr [[TA0]] +// CHECK: [[TA1:%.*]] = tuple_element_addr [[TADDR]] : $*(Klass, Klass), 1 +// CHECK: store [[T1]] to [assign] [[TA1]] +// CHECK: return [[T0]] +// CHECK: } // end sil function 'load_take_from_store_assign' +sil [ossa] @load_take_from_store_assign : $@convention(thin) (@owned Agg2, @inout Agg2, @owned Klass) -> @owned Klass { +bb0(%0 : @owned $Agg2, %1 : $*Agg2, %2 : @owned $Klass): + store %0 to [assign] %1 : $*Agg2 + %3 = struct_element_addr %1 : $*Agg2, #Agg2.t + %4 = tuple_element_addr %3 : $*(Klass, Klass), 0 + %6 = load [take] %4 : $*Klass + store %2 to [init] %4 : $*Klass + return %6 : $Klass +} + +// CHECK-LABEL: @load_take_from_store_init : +// CHECK: [[DS:%.*]] = destructure_struct %0 +// CHECK: [[TADDR:%.*]] = struct_element_addr %1 : $*Agg2, #Agg2.t +// CHECK: ([[T0:%.*]], [[T1:%.*]]) = destructure_tuple [[DS]] +// CHECK: [[TA1:%.*]] = tuple_element_addr [[TADDR]] : $*(Klass, Klass), 1 +// CHECK: store [[T1]] to [init] [[TA1]] +// CHECK-NOT: load +// CHECK: return [[T0]] +// CHECK: } // end sil function 'load_take_from_store_init' +sil [ossa] @load_take_from_store_init : $@convention(thin) (@owned Agg2, @inout Agg2, @owned Klass) -> @owned Klass { +bb0(%0 : @owned $Agg2, %1 : $*Agg2, %2 : @owned $Klass): + destroy_addr %1 : $*Agg2 + store %0 to [init] %1 : $*Agg2 + %3 = struct_element_addr %1 : $*Agg2, #Agg2.t + %4 = tuple_element_addr %3 : $*(Klass, Klass), 0 + %6 = load [take] %4 : $*Klass + store %2 to [init] %4 : $*Klass + return %6 : $Klass +} +// CHECK-LABEL: @load_take_from_load : +// CHECK: [[SE:%.*]] = struct_element_addr %0 : $*Agg2, #Agg2.t +// CHECK: [[TA0:%.*]] = tuple_element_addr [[SE]] : $*(Klass, Klass), 0 +// CHECK: [[T0:%.*]] = load [take] [[TA0]] +// CHECK: [[C:%.*]] = copy_value [[T0]] +// CHECK: [[TA1:%.*]] = tuple_element_addr [[SE]] : $*(Klass, Klass), 1 +// CHECK: [[T1:%.*]] = load [copy] [[TA1]] +// CHECK: [[T:%.*]] = tuple ([[T0]] : $Klass, [[T1]] : $Klass) +// CHECK: [[AGG:%.*]] = struct $Agg2 ([[T]] : $(Klass, Klass)) +// CHECK: destroy_value [[AGG]] +// CHECK: return [[C]] +// CHECK: } // end sil function 'load_take_from_load' +sil [ossa] @load_take_from_load : $@convention(thin) (@inout Agg2, @owned Klass) -> @owned Klass { +bb0(%0 : $*Agg2, %1 : @owned $Klass): + %3 = load [copy] %0 : $*Agg2 + destroy_value %3 : $Agg2 + %5 = struct_element_addr %0 : $*Agg2, #Agg2.t + %6 = tuple_element_addr %5 : $*(Klass, Klass), 0 + %8 = load [take] %6 : $*Klass + store %1 to [init] %6 : $*Klass + return %8 : $Klass +} + diff --git a/test/SILOptimizer/redundant_load_elim_ossa.sil b/test/SILOptimizer/redundant_load_elim_ossa.sil index 08ea478d33363..b5c1760605882 100644 --- a/test/SILOptimizer/redundant_load_elim_ossa.sil +++ b/test/SILOptimizer/redundant_load_elim_ossa.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elim | %FileCheck %s +// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elimination | %FileCheck %s // TODO : Add a version with semantic-arc-opts when #34971 is landed or DCE is enabled on OSSA // REQUIRES: swift_in_compiler @@ -220,43 +220,6 @@ bb0(%0 : @owned $AB): return %5 : $Int } -// Check that we don't crash if the address is an unchecked_addr_cast. -// CHECK-LABEL: sil [ossa] @test_unchecked_addr_cast : -// CHECK-NOT: = load -// CHECK-LABEL: } // end sil function 'test_unchecked_addr_cast' -sil [ossa] @test_unchecked_addr_cast : $@convention(thin) (@inout A, A) -> A { -bb0(%0 : $*A, %1 : $A): - %2 = unchecked_addr_cast %0 : $*A to $*A - store %1 to [trivial] %2 : $*A - %l1 = load [trivial] %2 : $*A - return %l1 : $A -} - -// Multi-BB version of the previous test. -// CHECK-LABEL: sil [ossa] @test_forwarding_ignoring_unchecked_addr_cast2 : $@convention(thin) (@inout A, A, A) -> A { -// CHECK: bb1 -// CHECK-NOT: = load -// CHECK: cond_br -// CHECK-LABEL: } // end sil function 'test_forwarding_ignoring_unchecked_addr_cast2' -sil [ossa] @test_forwarding_ignoring_unchecked_addr_cast2 : $@convention(thin) (@inout A, A, A) -> A { -bb0(%0 : $*A, %1 : $A, %2: $A): - %3 = unchecked_addr_cast %0 : $*A to $*A - store %1 to [trivial] %3 : $*A - br bb1 - -bb1: - %5 = load [trivial] %3 : $*A - %6 = load [trivial] %3 : $*A - store %2 to [trivial] %3 : $*A - cond_br undef, bb1a, bb2 - -bb1a: - br bb1 - -bb2: - return %5 : $A -} - // CHECK-LABEL: sil [ossa] @test_read_dependence_allows_forwarding_multi_bb_1 : $@convention(thin) (@inout A, A) -> A { // CHECK: bb0 // CHECK: store @@ -352,10 +315,13 @@ bb0(%0 : $Agg1): } // CHECK-LABEL: sil [ossa] @store_promotion : -// CHECK: store -// CHECK-NEXT: destroy_addr -// CHECK-NEXT: destroy_value -// CHECK-NEXT: destroy_value +// CHECK: %1 = alloc_box +// CHECK-NEXT: project_box +// CHECK-NEXT: [[C:%.*]] = copy_value %0 +// CHECK-NEXT: destroy_value [[C]] +// CHECK-NEXT: destroy_value %0 +// CHECK-NEXT: destroy_value %1 +// CHECK-NEXT: tuple // CHECK-LABEL: } // end sil function 'store_promotion' sil [ossa] @store_promotion : $@convention(thin) (@owned B) -> () { bb0(%0 : @owned $B): @@ -880,11 +846,11 @@ bb2: // Make sure we form a single SILArgument. // -// CHECK-LABEL: single_silargument_agg_in_one_block : -// CHECK: bb3([[ARG:%.*]] : $TwoField): +// CHECK-LABEL: silargument_agg_in_one_block : +// CHECK: bb3([[ARG1:%.*]] : $Int, [[ARG2:%.*]] : $Int): // CHECK-NOT: = load -// CHECK-LABEL: } // end sil function 'single_silargument_agg_in_one_block' -sil hidden [ossa] @single_silargument_agg_in_one_block : $@convention(thin) (Bool) -> () { +// CHECK-LABEL: } // end sil function 'silargument_agg_in_one_block' +sil hidden [ossa] @silargument_agg_in_one_block : $@convention(thin) (Bool) -> () { bb0(%0 : $Bool): %1 = alloc_stack $TwoField, var, name "x" cond_br undef, bb1, bb2 @@ -1418,11 +1384,12 @@ bb0(%0 : $B, %1 : $*Agg1): // TODO: When RLE uses TBAA it should remove the second load. // -// CHECK: sil @tbaa_struct -// CHECK: load -// CHECK: store -// CHECK: load -// CHECK: return +// CHECK-LABEL: sil @tbaa_struct +// CHECK: [[L:%.*]] = load +// CHECK: store +// CHECK-NOT: load +// CHECK: tuple ([[L]] : $A, [[L]] : $A) +// CHECK: } // end sil function 'tbaa_struct' sil @tbaa_struct : $@convention(thin) (Builtin.RawPointer, A2) -> (A, A) { bb0(%0 : $Builtin.RawPointer, %1 : $A2): %2 = pointer_to_address %0 : $Builtin.RawPointer to [strict] $*A @@ -1436,7 +1403,7 @@ bb0(%0 : $Builtin.RawPointer, %1 : $A2): // TODO: Even with TBAA, RLE should not remove the second load. // -// CHECK: sil @tbaa_bind_memory +// CHECK-LABEL: sil @tbaa_bind_memory // CHECK: load // CHECK: bind_memory // CHECK: store diff --git a/test/SILOptimizer/redundant_load_elim_ossa_complex.sil b/test/SILOptimizer/redundant_load_elim_ossa_complex.sil index 84c2eabdc5dfe..85fb225ead01a 100644 --- a/test/SILOptimizer/redundant_load_elim_ossa_complex.sil +++ b/test/SILOptimizer/redundant_load_elim_ossa_complex.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elim | %FileCheck %s +// RUN: %target-sil-opt -enable-sil-verify-all %s -redundant-load-elimination | %FileCheck %s // TODO : Add a version with semantic-arc-opts when #34971 is landed or DCE is enabled on OSSA // REQUIRES: swift_in_compiler @@ -46,8 +46,9 @@ bb0(%0 : $*NonTrivialStruct): } // CHECK-LABEL: sil [ossa] @rle_simple2 : -// CHECK: load -// CHECK-NOT: load +// CHECK: load +// CHECK: bb1: +// CHECK-NEXT: load // CHECK-LABEL: } // end sil function 'rle_simple2' sil [ossa] @rle_simple2 : $@convention(thin) (@in NonTrivialStruct) -> @owned Klass { bb0(%0 : $*NonTrivialStruct): @@ -69,8 +70,9 @@ bb3: } // CHECK-LABEL: sil [ossa] @rle_simple3 : -// CHECK: load -// CHECK-NOT: load +// CHECK: load +// CHECK: bb1: +// CHECK-NEXT: load // CHECK-LABEL: } // end sil function 'rle_simple3' sil [ossa] @rle_simple3 : $@convention(thin) (@in NonTrivialStruct) -> @owned Klass { bb0(%0 : $*NonTrivialStruct): @@ -261,12 +263,9 @@ bb1: return %val1 : $Klass } -// Test to make sure we generate only one copy_value of the load // CHECK-LABEL: sil [ossa] @rle_nodoublecopy2 : -// CHECK: load -// CHECK: copy_value -// CHECK-NOT: copy_value -// CHECK-NOT: load +// CHECK: bb3: +// CHECK-NEXT: load [take] // CHECK-LABEL: } // end sil function 'rle_nodoublecopy2' sil [ossa] @rle_nodoublecopy2 : $@convention(thin) (@in NonTrivialStruct) -> @owned Klass { bb0(%0 : $*NonTrivialStruct): @@ -366,8 +365,8 @@ bb0(%0 : $*TripleKlass): } // CHECK-LABEL: sil [ossa] @rle_redundantload_does_not_postdominate1 : -// CHECK: load -// CHECK-NOT: load +// CHECK: bb5: +// CHECK-NEXT: load [take] // CHECK-LABEL: } // end sil function 'rle_redundantload_does_not_postdominate1' sil [ossa] @rle_redundantload_does_not_postdominate1 : $@convention(thin) (@in NonTrivialStruct) -> () { bb0(%0 : $*NonTrivialStruct): @@ -400,8 +399,8 @@ bb6: } // CHECK-LABEL: sil [ossa] @rle_redundantload_does_not_postdominate2 : -// CHECK: load -// CHECK-NOT: load +// CHECK: bb1: +// CHECK-NEXT: load [take] // CHECK-LABEL: } // end sil function 'rle_redundantload_does_not_postdominate2' sil [ossa] @rle_redundantload_does_not_postdominate2 : $@convention(thin) (@in NonTrivialStruct, @owned Klass) -> () { bb0(%0 : $*NonTrivialStruct, %1 : @owned $Klass): @@ -428,8 +427,8 @@ bb2: } // CHECK-LABEL: sil [ossa] @rle_redundantload_does_not_postdominate3 : -// CHECK: load -// CHECK-NOT: load +// CHECK: bb1: +// CHECK-NEXT: load [copy] // CHECK-LABEL: } // end sil function 'rle_redundantload_does_not_postdominate3' sil [ossa] @rle_redundantload_does_not_postdominate3 : $@convention(thin) (@in NonTrivialStruct) -> () { bb0(%0 : $*NonTrivialStruct): @@ -453,8 +452,8 @@ bb2: } // CHECK-LABEL: sil [ossa] @rle_redundantload_does_not_postdominate4 : -// CHECK: load -// CHECK-NOT: load +// CHECK: bb1: +// CHECK-NEXT: load [copy] // CHECK-LABEL: } // end sil function 'rle_redundantload_does_not_postdominate4' sil [ossa] @rle_redundantload_does_not_postdominate4 : $@convention(thin) (@in NonTrivialStruct) -> () { bb0(%0 : $*NonTrivialStruct): @@ -653,10 +652,6 @@ bb8a: br bb10 bb9: - %21 = struct_element_addr %1 : $*NonTrivialStruct, #NonTrivialStruct.val - %22 = load [copy] %21 : $*Klass - %23 = function_ref @use_klass : $@convention(thin) (@owned Klass) -> () - %24 = apply %23(%22) : $@convention(thin) (@owned Klass) -> () br bb10 bb10: @@ -669,11 +664,6 @@ bb10: // CHECK-LABEL: sil [ossa] @load_to_load_conflicting_branches_diamond : // CHECK: bb0( // CHECK: = load -// CHECK: bb1: -// CHECK-NOT: = load -// CHECK: store -// CHECK-NOT: = load -// CHECK: bb2: // CHECK: bb3([[A:%[0-9]+]] : @owned $Klass): // CHECK-NOT: = load // CHECK: apply %{{[0-9]+}}([[A]]) @@ -740,8 +730,8 @@ bb3(%4 : @owned $NonTrivialStruct): } // CHECK-LABEL: sil [ossa] @rle_forwarding_arg_in_diff_region : -// CHECK: bb3 -// CHECK-NOT: = load +// CHECK: bb6: +// CHECK-NEXT: load [copy] // CHECK-LABEL: } // end sil function 'rle_forwarding_arg_in_diff_region' sil [ossa] @rle_forwarding_arg_in_diff_region : $@convention(thin) (@in NonTrivialStruct) -> @owned Klass { bb0(%0 : $*NonTrivialStruct): @@ -778,8 +768,8 @@ bb7: } // CHECK-LABEL: @rle_fixlifetimeof_intermediate_phis : -// CHECK: load -// CHECK-NOT: load +// CHECK: bb5: +// CHECK-NEXT: load [copy] // CHECK-LABEL: end sil function 'rle_fixlifetimeof_intermediate_phis' sil [ossa] @rle_fixlifetimeof_intermediate_phis : $@convention(thin) (@in NonTrivialStruct, @owned Klass, @owned Klass) -> @owned Klass { bb0(%0 : $*NonTrivialStruct, %1 : @owned $Klass, %2 : @owned $Klass): @@ -825,7 +815,8 @@ bb9: } // CHECK-LABEL: @rle_ownershipkindmergetest : -// CHECK-NOT: load +// CHECK: bb3: +// CHECK-NEXT: load [copy] // CHECK-LABEL: end sil function 'rle_ownershipkindmergetest' sil [ossa] @rle_ownershipkindmergetest : $@convention(thin) (@owned FakeOptional) -> () { bb0(%0 : @owned $FakeOptional): diff --git a/test/SILOptimizer/redundant_load_elim_with_casts.sil b/test/SILOptimizer/redundant_load_elim_with_casts.sil index 40873b8562c3d..8ebf561417281 100644 --- a/test/SILOptimizer/redundant_load_elim_with_casts.sil +++ b/test/SILOptimizer/redundant_load_elim_with_casts.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -enable-sil-verify-all %s -module-name Swift -redundant-load-elim | %FileCheck -check-prefix=CHECK-FUTURE %s +// RUN: %target-sil-opt -enable-sil-verify-all %s -module-name Swift -redundant-load-elimination | %FileCheck -check-prefix=CHECK-FUTURE %s // // FIXME: Contains tests which are handled by old RLE, but not current one. Mostly due to casting. Eventually we probably should // handle these cases if they turn out to be important. diff --git a/test/SILOptimizer/specialize_opaque_type_archetypes.swift b/test/SILOptimizer/specialize_opaque_type_archetypes.swift index 6e98769e80ccf..181ffe26e38cf 100644 --- a/test/SILOptimizer/specialize_opaque_type_archetypes.swift +++ b/test/SILOptimizer/specialize_opaque_type_archetypes.swift @@ -2,9 +2,9 @@ // RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking %S/Inputs/specialize_opaque_type_archetypes_2.swift -module-name External -emit-module -emit-module-path %t/External.swiftmodule // RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking %S/Inputs/specialize_opaque_type_archetypes_3.swift -enable-library-evolution -module-name External2 -emit-module -emit-module-path %t/External2.swiftmodule // RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking %S/Inputs/specialize_opaque_type_archetypes_4.swift -I %t -enable-library-evolution -module-name External3 -emit-module -emit-module-path %t/External3.swiftmodule -// RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking %S/Inputs/specialize_opaque_type_archetypes_3.swift -I %t -enable-library-evolution -module-name External2 -Osize -emit-module -o - | %target-sil-opt -module-name External2 | %FileCheck --check-prefix=RESILIENT %s -// RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking -I %t -module-name A -enforce-exclusivity=checked -Osize -emit-sil -sil-verify-all %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize -// RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking -I %t -module-name A -enforce-exclusivity=checked -enable-library-evolution -Osize -emit-sil -sil-verify-all %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize +// RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking %S/Inputs/specialize_opaque_type_archetypes_3.swift -I %t -enable-library-evolution -module-name External2 -Osize -Xllvm -sil-disable-pass=redundant-load-elimination -emit-module -o - | %target-sil-opt -module-name External2 | %FileCheck --check-prefix=RESILIENT %s +// RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking -I %t -module-name A -enforce-exclusivity=checked -Osize -Xllvm -sil-disable-pass=redundant-load-elimination -emit-sil -sil-verify-all %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize +// RUN: %target-swift-frontend -enable-copy-propagation=requested-passes-only -enable-lexical-lifetimes=false -disable-availability-checking -I %t -module-name A -enforce-exclusivity=checked -enable-library-evolution -Osize -Xllvm -sil-disable-pass=redundant-load-elimination -emit-sil -sil-verify-all %s | %FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-%target-ptrsize // REQUIRES: swift_in_compiler diff --git a/test/SILOptimizer/sroa_unreferenced_members.swift b/test/SILOptimizer/sroa_unreferenced_members.swift index 1114158a1bf35..501ef7ec7d4d0 100644 --- a/test/SILOptimizer/sroa_unreferenced_members.swift +++ b/test/SILOptimizer/sroa_unreferenced_members.swift @@ -3,7 +3,7 @@ import gizmo // CHECK: ModifyStruct -// CHECK: = alloc_stack $Drill +// CHECK-NOT: = alloc_stack $Drill // CHECK: ret func ModifyStruct(inDrill : Drill) -> Int32 { var D : Drill = inDrill diff --git a/utils/swift-autocomplete.bash b/utils/swift-autocomplete.bash index 1bf12e1af8e44..fa1d0fbe71740 100644 --- a/utils/swift-autocomplete.bash +++ b/utils/swift-autocomplete.bash @@ -57,7 +57,6 @@ _swift_complete() -keep-will-throw-call \ -looprotate-single-block-loop \ -looprotate-size-limit \ - -lslocation-dump-use-new-projection \ -max-local-apply-recur-depth \ -max-partial-store-count \ -optimize-opaque-address-lowering \