Skip to content

Commit fa25ba8

Browse files
authored
Merge pull request #84584 from eeckstein/fix-licm
LoopInvariantCodeMotion: fix a few ownership errors when hoisting load and store instructions
2 parents 7502c76 + b527020 commit fa25ba8

File tree

4 files changed

+335
-9
lines changed

4 files changed

+335
-9
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/LoopInvariantCodeMotion.swift

Lines changed: 93 additions & 3 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?
@@ -721,7 +727,18 @@ private extension MovableInstructions {
721727
guard let firstStore else {
722728
return false
723729
}
724-
730+
731+
// We currently don't support split `load [take]`, i.e. `load [take]` which does _not_ load all
732+
// non-trivial fields of the initial value.
733+
for case let load as LoadInst in loadsAndStores {
734+
if load.loadOwnership == .take,
735+
let path = accessPath.getProjection(to: load.address.accessPath),
736+
!firstStore.destination.type.isProjectingEntireNonTrivialMembers(path: path, in: load.parentFunction)
737+
{
738+
return false
739+
}
740+
}
741+
725742
var ssaUpdater = SSAUpdater(
726743
function: firstStore.parentFunction,
727744
type: firstStore.destination.type.objectType,
@@ -792,7 +809,13 @@ private extension MovableInstructions {
792809
let rootVal = currentVal ?? ssaUpdater.getValue(inMiddleOf: block)
793810

794811
if loadInst.operand.value.accessPath == accessPath {
795-
loadInst.replace(with: rootVal, context)
812+
if loadInst.loadOwnership == .copy {
813+
let builder = Builder(before: loadInst, context)
814+
let copy = builder.createCopyValue(operand: rootVal)
815+
loadInst.replace(with: copy, context)
816+
} else {
817+
loadInst.replace(with: rootVal, context)
818+
}
796819
changed = true
797820
continue
798821
}
@@ -802,7 +825,11 @@ private extension MovableInstructions {
802825
}
803826

804827
let builder = Builder(before: loadInst, context)
805-
let projection = rootVal.createProjection(path: projectionPath, builder: builder)
828+
let projection = if loadInst.loadOwnership == .copy {
829+
rootVal.createProjectionAndCopy(path: projectionPath, builder: builder)
830+
} else {
831+
rootVal.createProjection(path: projectionPath, builder: builder)
832+
}
806833
loadInst.replace(with: projection, context)
807834

808835
changed = true
@@ -831,6 +858,69 @@ private extension MovableInstructions {
831858

832859
return changed
833860
}
861+
862+
func memoryIsInitializedAtAllExits(of loop: Loop, accessPath: AccessPath, _ context: FunctionPassContext) -> Bool {
863+
864+
// Perform a simple dataflow analysis which checks if there is a path from a `load [take]`
865+
// (= the only kind of instruction which can de-initialize the memory) to a loop exit without
866+
// a `store` in between.
867+
868+
var stores = InstructionSet(context)
869+
defer { stores.deinitialize() }
870+
for case let store as StoreInst in loadsAndStores where store.storesTo(accessPath) {
871+
stores.insert(store)
872+
}
873+
874+
var exitInsts = InstructionSet(context)
875+
defer { exitInsts.deinitialize() }
876+
exitInsts.insert(contentsOf: loop.exitBlocks.lazy.map { $0.instructions.first! })
877+
878+
var worklist = InstructionWorklist(context)
879+
defer { worklist.deinitialize() }
880+
for case let load as LoadInst in loadsAndStores where load.loadOwnership == .take && load.loadsFrom(accessPath) {
881+
worklist.pushIfNotVisited(load)
882+
}
883+
884+
while let inst = worklist.pop() {
885+
if stores.contains(inst) {
886+
continue
887+
}
888+
if exitInsts.contains(inst) {
889+
return false
890+
}
891+
worklist.pushSuccessors(of: inst)
892+
}
893+
return true
894+
}
895+
}
896+
897+
private extension Type {
898+
func isProjectingEntireNonTrivialMembers(path: SmallProjectionPath, in function: Function) -> Bool {
899+
let (kind, index, subPath) = path.pop()
900+
switch kind {
901+
case .root:
902+
return true
903+
case .structField:
904+
guard let fields = getNominalFields(in: function) else {
905+
return false
906+
}
907+
for (fieldIdx, fieldType) in fields.enumerated() {
908+
if fieldIdx != index && !fieldType.isTrivial(in: function) {
909+
return false
910+
}
911+
}
912+
return fields[index].isProjectingEntireNonTrivialMembers(path: subPath, in: function)
913+
case .tupleField:
914+
for (elementIdx, elementType) in tupleElements.enumerated() {
915+
if elementIdx != index && !elementType.isTrivial(in: function) {
916+
return false
917+
}
918+
}
919+
return tupleElements[index].isProjectingEntireNonTrivialMembers(path: subPath, in: function)
920+
default:
921+
fatalError("path is not materializable")
922+
}
923+
}
834924
}
835925

836926
private extension Instruction {

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,20 +125,32 @@ extension Value {
125125
return true
126126
}
127127

128+
/// Project out a sub-field of this value according to `path`.
129+
/// If this is an "owned" value the result is an "owned" value which forwards the original value.
130+
/// This only works if _all_ non-trivial fields are projected. Otherwise some non-trivial results of
131+
/// `destructure_struct` or `destructure_tuple` will be leaked.
128132
func createProjection(path: SmallProjectionPath, builder: Builder) -> Value {
129133
let (kind, index, subPath) = path.pop()
134+
let result: Value
130135
switch kind {
131136
case .root:
132137
return self
133138
case .structField:
134-
let structExtract = builder.createStructExtract(struct: self, fieldIndex: index)
135-
return structExtract.createProjection(path: subPath, builder: builder)
139+
if ownership == .owned {
140+
result = builder.createDestructureStruct(struct: self).results[index]
141+
} else {
142+
result = builder.createStructExtract(struct: self, fieldIndex: index)
143+
}
136144
case .tupleField:
137-
let tupleExtract = builder.createTupleExtract(tuple: self, elementIndex: index)
138-
return tupleExtract.createProjection(path: subPath, builder: builder)
145+
if ownership == .owned {
146+
result = builder.createDestructureTuple(tuple: self).results[index]
147+
} else {
148+
result = builder.createTupleExtract(tuple: self, elementIndex: index)
149+
}
139150
default:
140151
fatalError("path is not materializable")
141152
}
153+
return result.createProjection(path: subPath, builder: builder)
142154
}
143155

144156
func createAddressProjection(path: SmallProjectionPath, builder: Builder) -> Value {

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)

0 commit comments

Comments
 (0)