@@ -54,6 +54,24 @@ private extension Instruction {
54
54
}
55
55
}
56
56
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
+
57
75
extension BorrowedFromInst : VerifiableInstruction {
58
76
func verify( _ context: FunctionPassContext ) {
59
77
var computedEVs = Stack < Value > ( context)
@@ -74,20 +92,121 @@ extension BorrowedFromInst : VerifiableInstruction {
74
92
}
75
93
}
76
94
77
- private extension Argument {
95
+ extension LoadBorrowInst : VerifiableInstruction {
78
96
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. verifyNoMutatingUsesInLiferange ( of: self )
106
+ }
107
+ }
108
+
109
+ // Used to check if any instruction is mutating the memory location within the liferange 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 verifyNoMutatingUsesInLiferange( of load: LoadBorrowInst ) {
132
+ // The liferange of a `load_borrow` can end in a branch instruction. In such cases we handle the re-borrow liferanges
133
+ // (starting at the `borrowed_from` in the successor block) separately.
134
+ // This worklist contains the starts of the individual linear liferanges,
135
+ // i.e. `load_borrow` and `borrowed_from` instructions.
136
+ var linearLiferanges = SpecificInstructionWorklist < SingleValueInstruction > ( context)
137
+ defer { linearLiferanges. deinitialize ( ) }
138
+
139
+ linearLiferanges. pushIfNotVisited ( load)
140
+ while let startInst = linearLiferanges. pop ( ) {
141
+ verifyNoMutatingUsesInLinearLiferange ( of: startInst)
142
+
143
+ for use in startInst. uses {
144
+ if let phi = Phi ( using: use) {
145
+ linearLiferanges. pushIfNotVisited ( phi. borrowedFrom!)
87
146
}
88
147
}
89
- require ( forwardingBorrowedFromFound,
90
- " missing forwarding borrowed-from user of guaranteed phi \( phi) " )
148
+ }
149
+ }
150
+
151
+ private mutating func verifyNoMutatingUsesInLinearLiferange( 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
91
210
}
92
211
}
93
212
}
0 commit comments