Skip to content

Commit 83e49c1

Browse files
committed
SIL Verifier: implement load-borrow-immutability checkin in the swift verifier
1 parent f3cee91 commit 83e49c1

File tree

5 files changed

+131
-478
lines changed

5 files changed

+131
-478
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/Verifier.swift

Lines changed: 130 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
5775
extension 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.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!)
87146
}
88147
}
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
91210
}
92211
}
93212
}

lib/SIL/Verifier/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
target_sources(swiftSIL PRIVATE
22
DebugInfoVerifier.cpp
3-
LoadBorrowImmutabilityChecker.cpp
43
LinearLifetimeChecker.cpp
54
MemoryLifetimeVerifier.cpp
65
GuaranteedPhiVerifier.cpp

0 commit comments

Comments
 (0)