Skip to content

Commit 07ac8b3

Browse files
committed
Add new loop invariant code motion.
1 parent 3514b2b commit 07ac8b3

File tree

17 files changed

+1403
-106
lines changed

17 files changed

+1403
-106
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ swift_compiler_sources(Optimizer
2525
LifetimeDependenceDiagnostics.swift
2626
LifetimeDependenceInsertion.swift
2727
LifetimeDependenceScopeFixup.swift
28+
LoopInvariantCodeMotion.swift
2829
ObjectOutliner.swift
2930
ObjCBridgingOptimization.swift
3031
MergeCondFails.swift

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LoopInvariantCodeMotion.swift

Lines changed: 1126 additions & 0 deletions
Large diffs are not rendered by default.

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ private func registerSwiftPasses() {
106106
registerPass(tempLValueElimination, { tempLValueElimination.run($0) })
107107
registerPass(generalClosureSpecialization, { generalClosureSpecialization.run($0) })
108108
registerPass(autodiffClosureSpecialization, { autodiffClosureSpecialization.run($0) })
109+
registerPass(loopInvariantCodeMotionPass, { loopInvariantCodeMotionPass.run($0) })
109110

110111
// Instruction passes
111112
registerForSILCombine(BeginBorrowInst.self, { run(BeginBorrowInst.self, $0) })

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 89 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,22 @@ extension Instruction {
484484
}
485485
return false
486486
}
487+
488+
/// Returns true if `otherInst` is in the same block and is strictly dominated by this instruction or
489+
/// the parent block of the instruction dominates parent block of `otherInst`.
490+
func dominates(
491+
_ otherInst: Instruction,
492+
_ domTree: DominatorTree
493+
) -> Bool {
494+
if parentBlock == otherInst.parentBlock {
495+
return dominatesInSameBlock(otherInst)
496+
} else {
497+
return parentBlock.dominates(
498+
otherInst.parentBlock,
499+
domTree
500+
)
501+
}
502+
}
487503

488504
/// If this instruction uses a (single) existential archetype, i.e. it has a type-dependent operand,
489505
/// returns the concrete type if it is known.
@@ -583,36 +599,22 @@ extension StoreInst {
583599
extension LoadInst {
584600
@discardableResult
585601
func trySplit(_ context: FunctionPassContext) -> Bool {
586-
var elements = [Value]()
587-
let builder = Builder(before: self, context)
588602
if type.isStruct {
589-
if (type.nominal as! StructDecl).hasUnreferenceableStorage {
590-
return false
591-
}
592-
guard let fields = type.getNominalFields(in: parentFunction) else {
603+
guard !(type.nominal as! StructDecl).hasUnreferenceableStorage,
604+
let fields = type.getNominalFields(in: parentFunction) else {
593605
return false
594606
}
595-
for idx in 0..<fields.count {
596-
let fieldAddr = builder.createStructElementAddr(structAddress: address, fieldIndex: idx)
597-
let splitLoad = builder.createLoad(fromAddress: fieldAddr, ownership: self.splitOwnership(for: fieldAddr))
598-
elements.append(splitLoad)
599-
}
600-
let newStruct = builder.createStruct(type: self.type, elements: elements)
601-
self.replace(with: newStruct, context)
607+
608+
_ = splitStruct(fields: fields, context)
609+
602610
return true
603611
} else if type.isTuple {
604-
var elements = [Value]()
605-
let builder = Builder(before: self, context)
606-
for idx in 0..<type.tupleElements.count {
607-
let fieldAddr = builder.createTupleElementAddr(tupleAddress: address, elementIndex: idx)
608-
let splitLoad = builder.createLoad(fromAddress: fieldAddr, ownership: self.splitOwnership(for: fieldAddr))
609-
elements.append(splitLoad)
610-
}
611-
let newTuple = builder.createTuple(type: self.type, elements: elements)
612-
self.replace(with: newTuple, context)
612+
_ = splitTuple(context)
613+
613614
return true
615+
} else {
616+
return false
614617
}
615-
return false
616618
}
617619

618620
private func splitOwnership(for fieldValue: Value) -> LoadOwnership {
@@ -623,6 +625,70 @@ extension LoadInst {
623625
return fieldValue.type.isTrivial(in: parentFunction) ? .trivial : self.loadOwnership
624626
}
625627
}
628+
629+
func trySplit(
630+
alongPath projectionPath: SmallProjectionPath,
631+
_ context: FunctionPassContext
632+
) -> [LoadInst]? {
633+
if projectionPath.isEmpty {
634+
return nil
635+
}
636+
637+
let (fieldKind, index, pathRemainder) = projectionPath.pop()
638+
639+
var elements: [LoadInst]
640+
641+
switch fieldKind {
642+
case .structField where type.isStruct:
643+
guard !(type.nominal as! StructDecl).hasUnreferenceableStorage,
644+
let fields = type.getNominalFields(in: parentFunction) else {
645+
return nil
646+
}
647+
648+
elements = splitStruct(fields: fields, context)
649+
case .tupleField where type.isTuple:
650+
elements = splitTuple(context)
651+
default:
652+
return nil
653+
}
654+
655+
if let recursiveSplitLoad = elements[index].trySplit(alongPath: pathRemainder, context) {
656+
elements.remove(at: index)
657+
elements += recursiveSplitLoad
658+
}
659+
660+
return elements
661+
}
662+
663+
private func splitStruct(fields: NominalFieldsArray, _ context: FunctionPassContext) -> [LoadInst] {
664+
var elements = [LoadInst]()
665+
let builder = Builder(before: self, context)
666+
667+
for idx in 0..<fields.count {
668+
let fieldAddr = builder.createStructElementAddr(structAddress: address, fieldIndex: idx)
669+
let splitLoad = builder.createLoad(fromAddress: fieldAddr, ownership: self.splitOwnership(for: fieldAddr))
670+
elements.append(splitLoad)
671+
}
672+
let newStruct = builder.createStruct(type: self.type, elements: elements)
673+
self.replace(with: newStruct, context)
674+
675+
return elements
676+
}
677+
678+
private func splitTuple(_ context: FunctionPassContext) -> [LoadInst] {
679+
var elements = [LoadInst]()
680+
let builder = Builder(before: self, context)
681+
682+
for idx in 0..<type.tupleElements.count {
683+
let fieldAddr = builder.createTupleElementAddr(tupleAddress: address, elementIndex: idx)
684+
let splitLoad = builder.createLoad(fromAddress: fieldAddr, ownership: self.splitOwnership(for: fieldAddr))
685+
elements.append(splitLoad)
686+
}
687+
let newTuple = builder.createTuple(type: self.type, elements: elements)
688+
self.replace(with: newTuple, context)
689+
690+
return elements
691+
}
626692
}
627693

628694
extension FunctionPassContext {

SwiftCompilerSources/Sources/SIL/DataStructures/Stack.swift

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public struct Stack<Element> : CollectionLikeSequence {
140140
public mutating func deinitialize() { removeAll() }
141141
}
142142

143-
extension Stack {
143+
public extension Stack {
144144
/// Mark a stack location for future iteration.
145145
///
146146
/// TODO: Marker should be ~Escapable.
@@ -155,7 +155,7 @@ extension Stack {
155155
let low: Marker
156156
let high: Marker
157157

158-
init(in stack: Stack, low: Marker, high: Marker) {
158+
public init(in stack: Stack, low: Marker, high: Marker) {
159159
if low.slab.data == nil {
160160
assert(low.index == 0, "invalid empty stack marker")
161161
// `low == nil` and `high == nil` is a valid empty segment,
@@ -173,7 +173,7 @@ extension Stack {
173173
self.high = high
174174
}
175175

176-
func makeIterator() -> Stack.Iterator {
176+
public func makeIterator() -> Stack.Iterator {
177177
return Iterator(slab: low.slab, index: low.index,
178178
lastSlab: high.slab, endIndex: high.index)
179179
}
@@ -219,3 +219,87 @@ extension Stack {
219219
}
220220
}
221221
}
222+
223+
public struct StackWithCount<Element> : CollectionLikeSequence {
224+
public private(set) var count = 0
225+
private var underlyingStack: Stack<Element>
226+
227+
public typealias Iterator = Stack<Element>.Iterator
228+
229+
public init(_ context: some Context) {
230+
self.underlyingStack = Stack<Element>(context)
231+
}
232+
233+
public func makeIterator() -> Stack<Element>.Iterator {
234+
underlyingStack.makeIterator()
235+
}
236+
237+
public var first: Element? { underlyingStack.first }
238+
public var last: Element? { underlyingStack.last }
239+
240+
public mutating func push(_ element: Element) {
241+
count += 1
242+
underlyingStack.push(element)
243+
}
244+
245+
/// The same as `push` to provide an Array-like append API.
246+
public mutating func append(_ element: Element) { push(element) }
247+
248+
public mutating func append<S: Sequence>(contentsOf other: S) where S.Element == Element {
249+
for elem in other {
250+
append(elem)
251+
}
252+
}
253+
254+
public var isEmpty: Bool { underlyingStack.isEmpty }
255+
256+
public mutating func pop() -> Element? {
257+
if underlyingStack.isEmpty {
258+
return nil
259+
}
260+
261+
count -= 1
262+
return underlyingStack.pop()
263+
}
264+
265+
public mutating func removeAll() {
266+
underlyingStack.removeAll()
267+
}
268+
269+
/// TODO: once we have move-only types, make this a real deinit.
270+
public mutating func deinitialize() { removeAll() }
271+
}
272+
273+
public extension StackWithCount {
274+
typealias Marker = Stack<Element>.Marker
275+
276+
struct Segment : CollectionLikeSequence {
277+
var underlyingSegment: Stack<Element>.Segment
278+
279+
public init(in stack: StackWithCount, low: Marker, high: Marker) {
280+
underlyingSegment = Stack<Element>.Segment(in: stack.underlyingStack, low: low, high: high)
281+
}
282+
283+
public func makeIterator() -> StackWithCount.Iterator {
284+
return underlyingSegment.makeIterator()
285+
}
286+
}
287+
288+
var top: Marker { underlyingStack.top }
289+
290+
func assertValid(marker: Marker) { underlyingStack.assertValid(marker: marker) }
291+
292+
mutating func withMarker<R>(
293+
_ body: (inout Stack<Element>, Marker) throws -> R) rethrows -> R {
294+
return try underlyingStack.withMarker(body)
295+
}
296+
297+
mutating func withMarker<R>(
298+
pushElements body: (inout Stack<Element>) throws -> R,
299+
withNewElements handleNewElements: ((Segment) -> ())
300+
) rethrows -> R {
301+
return try underlyingStack.withMarker(pushElements: body) { [self] segment in
302+
handleNewElements(Segment(in: self, low: segment.low, high: segment.high))
303+
}
304+
}
305+
}

SwiftCompilerSources/Sources/SIL/Effects.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,14 @@ public struct SideEffects : CustomStringConvertible, NoReflectionChildren {
505505
/// This is true when the function (or a callee, transitively) contains a
506506
/// deinit barrier instruction.
507507
public var isDeinitBarrier: Bool
508+
509+
public static var noEffects: GlobalEffects {
510+
return GlobalEffects(memory: .noEffects, ownership: .noEffects, allocates: false, isDeinitBarrier: false)
511+
}
512+
513+
public var isOnlyReading: Bool {
514+
return !memory.write && ownership == .noEffects && !allocates && !isDeinitBarrier
515+
}
508516

509517
/// When called with default arguments, it creates an "effect-free" GlobalEffects.
510518
public init(memory: Memory = Memory(read: false, write: false),
@@ -643,6 +651,10 @@ public struct SideEffects : CustomStringConvertible, NoReflectionChildren {
643651
copy = copy || other.copy
644652
destroy = destroy || other.destroy
645653
}
654+
655+
public static var noEffects: Ownership {
656+
return Ownership(copy: false, destroy: false)
657+
}
646658

647659
public static var worstEffects: Ownership {
648660
Ownership(copy: true, destroy: true)

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ public class Instruction : CustomStringConvertible, Hashable {
9696
BridgedContext.moveInstructionBefore(bridged, otherInstruction.bridged)
9797
context.notifyInstructionsChanged()
9898
}
99+
100+
public final func copy(before otherInstruction: Instruction, _ context: some MutatingContext) {
101+
BridgedContext.copyInstructionBefore(bridged, otherInstruction.bridged)
102+
context.notifyInstructionsChanged()
103+
}
99104

100105
public var mayTrap: Bool { false }
101106

@@ -177,6 +182,10 @@ public class Instruction : CustomStringConvertible, Hashable {
177182
public static func ==(lhs: Instruction, rhs: Instruction) -> Bool {
178183
lhs === rhs
179184
}
185+
186+
public func isIdenticalTo(_ otherInst: Instruction) -> Bool {
187+
return bridged.isIdenticalTo(otherInst.bridged)
188+
}
180189

181190
public func hash(into hasher: inout Hasher) {
182191
hasher.combine(ObjectIdentifier(self))

SwiftCompilerSources/Sources/SIL/Utilities/AccessUtils.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,11 @@ public struct AccessPath : CustomStringConvertible, Hashable {
392392
public func isEqualOrContains(_ other: AccessPath) -> Bool {
393393
return getProjection(to: other) != nil
394394
}
395+
396+
/// Returns true if this access contains `other` access and is not equal.
397+
public func contains(_ other: AccessPath) -> Bool {
398+
return !(getProjection(to: other)?.isEmpty ?? true)
399+
}
395400

396401
public var materializableProjectionPath: SmallProjectionPath? {
397402
if projectionPath.isMaterializable {

SwiftCompilerSources/Sources/SIL/Utilities/SmallProjectionPath.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,18 @@ public struct SmallProjectionPath : Hashable, CustomStringConvertible, NoReflect
534534
return false
535535
}
536536
}
537+
538+
public var isConstant: Bool {
539+
let (kind, _, subPath) = pop()
540+
switch kind {
541+
case .root:
542+
return true
543+
case .structField, .tupleField, .enumCase, .classField, .existential, .indexedElement:
544+
return subPath.isConstant
545+
default:
546+
return false
547+
}
548+
}
537549
}
538550

539551
//===----------------------------------------------------------------------===//

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
/// This macro follows the same conventions as PASS(Id, Tag, Description),
5252
/// but is used for IRGen passes which are built outside of the
5353
/// SILOptimizer library.
54-
///
54+
///
5555
/// An IRGen pass is created by IRGen and needs to be registered with the pass
5656
/// manager dynamically.
5757
#ifndef IRGEN_PASS
@@ -148,6 +148,8 @@ PASS(TempRValueElimination, "temp-rvalue-elimination",
148148
"Remove short-lived immutable temporary copies")
149149
PASS(TempLValueElimination, "temp-lvalue-elimination",
150150
"Remove short-lived immutable temporary l-values")
151+
PASS(LoopInvariantCodeMotion, "loop-invariant-code-motion",
152+
"New Loop Invariant Code Motion")
151153

152154
// NOTE - ExperimentalSwiftBasedClosureSpecialization and AutodiffClosureSpecialization are a WIP
153155
PASS(ExperimentalSwiftBasedClosureSpecialization, "experimental-swift-based-closure-specialization",

0 commit comments

Comments
 (0)