@@ -54,6 +54,24 @@ private extension Instruction {
5454 }
5555}
5656
57+ private extension Argument {
58+ func verify( _ context: FunctionPassContext ) {
59+ if let phi = Phi ( self ) , phi. value. ownership == . guaranteed {
60+ var forwardingBorrowedFromFound = false
61+ for use in phi. value. uses {
62+ require ( use. instruction is BorrowedFromInst ,
63+ " guaranteed phi: \( self ) \n has non borrowed-from use: \( use) " )
64+ if use. index == 0 {
65+ require ( !forwardingBorrowedFromFound, " phi \( self ) has multiple forwarding borrowed-from uses " )
66+ forwardingBorrowedFromFound = true
67+ }
68+ }
69+ require ( forwardingBorrowedFromFound,
70+ " missing forwarding borrowed-from user of guaranteed phi \( phi) " )
71+ }
72+ }
73+ }
74+
5775extension BorrowedFromInst : VerifiableInstruction {
5876 func verify( _ context: FunctionPassContext ) {
5977 var computedEVs = Stack < Value > ( context)
@@ -74,20 +92,121 @@ extension BorrowedFromInst : VerifiableInstruction {
7492 }
7593}
7694
77- private extension Argument {
95+ extension LoadBorrowInst : VerifiableInstruction {
7896 func verify( _ context: FunctionPassContext ) {
79- if let phi = Phi ( self ) , phi. value. ownership == . guaranteed {
80- var forwardingBorrowedFromFound = false
81- for use in phi. value. uses {
82- require ( use. instruction is BorrowedFromInst ,
83- " guaranteed phi: \( self ) \n has non borrowed-from use: \( use) " )
84- if use. index == 0 {
85- require ( !forwardingBorrowedFromFound, " phi \( self ) has multiple forwarding borrowed-from uses " )
86- forwardingBorrowedFromFound = true
97+ if isUnchecked {
98+ return
99+ }
100+
101+ var mutatingInstructions = MutatingUsesWalker ( context)
102+ defer { mutatingInstructions. deinitialize ( ) }
103+
104+ mutatingInstructions. findMutatingUses ( of: self . address)
105+ mutatingInstructions. verifyNoMutatingUsesInLiverange ( of: self )
106+ }
107+ }
108+
109+ // Used to check if any instruction is mutating the memory location within the liverange of a `load_borrow`.
110+ // Note that it is not checking if an instruction _may_ mutate the memory, but it's checking if any instruction
111+ // _definitely_ will mutate the memory.
112+ // Otherwise the risk would be too big for false alarms. It also means that this verification is not perfect and
113+ // might miss some subtle violations.
114+ private struct MutatingUsesWalker : AddressDefUseWalker {
115+ let context : FunctionPassContext
116+ var mutatingInstructions : InstructionSet
117+
118+ init ( _ context: FunctionPassContext ) {
119+ self . context = context
120+ self . mutatingInstructions = InstructionSet ( context)
121+ }
122+
123+ mutating func deinitialize( ) {
124+ self . mutatingInstructions. deinitialize ( )
125+ }
126+
127+ mutating func findMutatingUses( of address: Value ) {
128+ _ = walkDownUses ( ofAddress: address, path: UnusedWalkingPath ( ) )
129+ }
130+
131+ mutating func verifyNoMutatingUsesInLiverange( of load: LoadBorrowInst ) {
132+ // The liverange of a `load_borrow` can end in a branch instruction. In such cases we handle the re-borrow liveranges
133+ // (starting at the `borrowed_from` in the successor block) separately.
134+ // This worklist contains the starts of the individual linear liveranges,
135+ // i.e. `load_borrow` and `borrowed_from` instructions.
136+ var linearLiveranges = SpecificInstructionWorklist < SingleValueInstruction > ( context)
137+ defer { linearLiveranges. deinitialize ( ) }
138+
139+ linearLiveranges. pushIfNotVisited ( load)
140+ while let startInst = linearLiveranges. pop ( ) {
141+ verifyNoMutatingUsesInLinearLiverange ( of: startInst)
142+
143+ for use in startInst. uses {
144+ if let phi = Phi ( using: use) {
145+ linearLiveranges. pushIfNotVisited ( phi. borrowedFrom!)
87146 }
88147 }
89- require ( forwardingBorrowedFromFound,
90- " missing forwarding borrowed-from user of guaranteed phi \( phi) " )
148+ }
149+ }
150+
151+ private mutating func verifyNoMutatingUsesInLinearLiverange( of startInst: SingleValueInstruction ) {
152+ assert ( startInst is LoadBorrowInst || startInst is BorrowedFromInst )
153+
154+ var instWorklist = InstructionWorklist ( context)
155+ defer { instWorklist. deinitialize ( ) }
156+
157+ // It would be good enough to only consider `end_borrow`s (and branches), but adding all users doesn't harm.
158+ for use in startInst. uses {
159+ instWorklist. pushPredecessors ( of: use. instruction, ignoring: startInst)
160+ }
161+
162+ while let inst = instWorklist. pop ( ) {
163+ require ( !mutatingInstructions. contains ( inst) ,
164+ " Load borrow invalidated by a local write " , atInstruction: inst)
165+ instWorklist. pushPredecessors ( of: inst, ignoring: startInst)
166+ }
167+ }
168+
169+ mutating func leafUse( address: Operand , path: UnusedWalkingPath ) -> WalkResult {
170+ if address. isMutatedAddress {
171+ mutatingInstructions. insert ( address. instruction)
172+ }
173+ return . continueWalk
174+ }
175+ }
176+
177+ private extension Operand {
178+ // Returns true if the operand's instruction is definitely writing to the operand's address value.
179+ var isMutatedAddress : Bool {
180+ assert ( value. type. isAddress)
181+ switch instruction {
182+ case let store as StoringInstruction :
183+ return self == store. destinationOperand
184+ case let copy as SourceDestAddrInstruction :
185+ if self == copy. destinationOperand {
186+ return true
187+ } else if self == copy. sourceOperand && copy. isTakeOfSrc {
188+ return true
189+ }
190+ return false
191+ case let load as LoadInst :
192+ return load. loadOwnership == . take
193+ case let partialApply as PartialApplyInst :
194+ if !partialApply. isOnStack,
195+ let convention = partialApply. convention ( of: self )
196+ {
197+ switch convention {
198+ case . indirectIn, . indirectInGuaranteed:
199+ // Such operands are consumed by the `partial_apply` and therefore cound as "written".
200+ return true
201+ default :
202+ return false
203+ }
204+ }
205+ return false
206+ case is DestroyAddrInst , is IsUniqueInst :
207+ return true
208+ default :
209+ return false
91210 }
92211 }
93212}
0 commit comments