@@ -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
836926private extension Instruction {
0 commit comments