Skip to content

Commit 7bbe5c6

Browse files
committed
LoopInvariantCodeMotion: don't hoist loads and stores if the memory location is not initialized at loop exits.
If the memory is not initialized at all exits, it would be wrong to insert stores at exit blocks.
1 parent 761b88f commit 7bbe5c6

File tree

3 files changed

+120
-2
lines changed

3 files changed

+120
-2
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LoopInvariantCodeMotion.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,12 @@ private extension MovableInstructions {
693693
accessPath: AccessPath,
694694
context: FunctionPassContext
695695
) -> Bool {
696+
697+
// If the memory is not initialized at all exits, it would be wrong to insert stores at exit blocks.
698+
guard memoryIsInitializedAtAllExits(of: loop, accessPath: accessPath, context) else {
699+
return false
700+
}
701+
696702
// Initially load the value in the loop pre header.
697703
let builder = Builder(before: loop.preheader!.terminator, context)
698704
var firstStore: StoreInst?
@@ -831,6 +837,40 @@ private extension MovableInstructions {
831837

832838
return changed
833839
}
840+
841+
func memoryIsInitializedAtAllExits(of loop: Loop, accessPath: AccessPath, _ context: FunctionPassContext) -> Bool {
842+
843+
// Perform a simple dataflow analysis which checks if there is a path from a `load [take]`
844+
// (= the only kind of instruction which can de-initialize the memory) to a loop exit without
845+
// a `store` in between.
846+
847+
var stores = InstructionSet(context)
848+
defer { stores.deinitialize() }
849+
for case let store as StoreInst in loadsAndStores where store.storesTo(accessPath) {
850+
stores.insert(store)
851+
}
852+
853+
var exitInsts = InstructionSet(context)
854+
defer { exitInsts.deinitialize() }
855+
exitInsts.insert(contentsOf: loop.exitBlocks.lazy.map { $0.instructions.first! })
856+
857+
var worklist = InstructionWorklist(context)
858+
defer { worklist.deinitialize() }
859+
for case let load as LoadInst in loadsAndStores where load.loadOwnership == .take && load.loadsFrom(accessPath) {
860+
worklist.pushIfNotVisited(load)
861+
}
862+
863+
while let inst = worklist.pop() {
864+
if stores.contains(inst) {
865+
continue
866+
}
867+
if exitInsts.contains(inst) {
868+
return false
869+
}
870+
worklist.pushSuccessors(of: inst)
871+
}
872+
return true
873+
}
834874
}
835875

836876
private extension Instruction {

SwiftCompilerSources/Sources/SIL/DataStructures/Worklist.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public typealias ValueWorklist = Worklist<ValueSet>
7777
public typealias OperandWorklist = Worklist<OperandSet>
7878

7979
extension InstructionWorklist {
80-
public mutating func pushPredecessors(of inst: Instruction, ignoring ignoreInst: Instruction) {
80+
public mutating func pushPredecessors(of inst: Instruction, ignoring ignoreInst: Instruction? = nil) {
8181
if let prev = inst.previous {
8282
if prev != ignoreInst {
8383
pushIfNotVisited(prev)
@@ -92,7 +92,7 @@ extension InstructionWorklist {
9292
}
9393
}
9494

95-
public mutating func pushSuccessors(of inst: Instruction, ignoring ignoreInst: Instruction) {
95+
public mutating func pushSuccessors(of inst: Instruction, ignoring ignoreInst: Instruction? = nil) {
9696
if let succ = inst.next {
9797
if succ != ignoreInst {
9898
pushIfNotVisited(succ)

test/SILOptimizer/licm.sil

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1927,3 +1927,81 @@ bb3:
19271927
%13 = tuple ()
19281928
return %13
19291929
}
1930+
1931+
// Just check that LICM doesn't produce invalid SIL.
1932+
// CHECK-LABEL: sil [ossa] @uninitialized_at_entry_and_exit :
1933+
// CHECK-LABEL: } // end sil function 'uninitialized_at_entry_and_exit'
1934+
sil [ossa] @uninitialized_at_entry_and_exit : $@convention(thin) (@owned S) -> @out S {
1935+
bb0(%0 : $*S, %1 : @owned $S):
1936+
br bb1
1937+
1938+
bb1:
1939+
%5 = copy_value %1
1940+
store %5 to [init] %0
1941+
%7 = load [take] %0
1942+
destroy_value %7
1943+
cond_br undef, bb2, bb3
1944+
1945+
bb2:
1946+
br bb1
1947+
1948+
bb3:
1949+
store %1 to [init] %0
1950+
%r = tuple ()
1951+
return %r
1952+
}
1953+
1954+
// CHECK-LABEL: sil [ossa] @uninitialized_at_entry :
1955+
// CHECK: bb1:
1956+
// CHECK-NEXT: [[C:%.*]] = copy_value %1
1957+
// CHECK-NEXT: cond_br
1958+
// CHECK: bb2:
1959+
// CHECK-NEXT: destroy_value [[C]]
1960+
// CHECK: bb3:
1961+
// CHECK-NEXT: store [[C]] to [init] %0
1962+
// CHECK-LABEL: } // end sil function 'uninitialized_at_entry'
1963+
sil [ossa] @uninitialized_at_entry : $@convention(thin) (@owned S) -> @out S {
1964+
bb0(%0 : $*S, %1 : @owned $S):
1965+
br bb1
1966+
1967+
bb1:
1968+
%5 = copy_value %1
1969+
store %5 to [init] %0
1970+
cond_br undef, bb2, bb3
1971+
1972+
bb2:
1973+
%7 = load [take] %0
1974+
destroy_value %7
1975+
br bb1
1976+
1977+
bb3:
1978+
destroy_value %1
1979+
%r = tuple ()
1980+
return %r
1981+
}
1982+
1983+
// Just check that LICM doesn't produce invalid SIL.
1984+
// CHECK-LABEL: sil [ossa] @uninitialized_at_exit :
1985+
// CHECK-LABEL: } // end sil function 'uninitialized_at_exit'
1986+
sil [ossa] @uninitialized_at_exit : $@convention(thin) (@in S) -> () {
1987+
bb0(%0 : $*S):
1988+
br bb1
1989+
1990+
bb1:
1991+
%4 = load [take] %0
1992+
%5 = move_value %4
1993+
store %5 to [init] %0
1994+
%6 = load [take] %0
1995+
cond_br undef, bb2, bb3
1996+
1997+
bb2:
1998+
%8 = move_value %6
1999+
store %8 to [init] %0
2000+
br bb1
2001+
2002+
bb3:
2003+
destroy_value %6
2004+
%r = tuple ()
2005+
return %r
2006+
}
2007+

0 commit comments

Comments
 (0)