Skip to content

Commit b08f2de

Browse files
committed
Add AccessBase.findSingleInitializer
1 parent 6341391 commit b08f2de

File tree

2 files changed

+186
-2
lines changed

2 files changed

+186
-2
lines changed

SwiftCompilerSources/Sources/Optimizer/Utilities/AddressUtils.swift

Lines changed: 143 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ protocol AddressUseVisitor {
3737
/// end_access, end_apply, abort_apply, end_borrow.
3838
mutating func scopeEndingAddressUse(of operand: Operand) -> WalkResult
3939

40-
/// A address leaf use cannot propagate the address bits beyond the
41-
/// instruction.
40+
/// An address leaf use propagates neither the address bits, nor the
41+
/// in-memory value beyond the instruction.
4242
///
4343
/// StoringInstructions are leaf uses.
4444
mutating func leafAddressUse(of operand: Operand) -> WalkResult
@@ -186,3 +186,144 @@ extension AddressUseVisitor {
186186
}
187187
}
188188
}
189+
190+
extension AccessBase {
191+
/// If this access base has a single initializer, return it, along
192+
/// with the initialized address. This does not guarantee that all
193+
/// uses of that address are dominated by the store or even that the
194+
/// store is a direct use of `address`.
195+
func findSingleInitializer(_ context: some Context)
196+
-> (initialAddress: Value, initializingStore: Instruction)? {
197+
let baseAddr: Value
198+
switch self {
199+
case let .stack(allocStack):
200+
baseAddr = allocStack
201+
case let .argument(arg):
202+
baseAddr = arg
203+
default:
204+
return nil
205+
}
206+
var walker = AddressInitializationWalker(context: context)
207+
if walker.walkDownUses(ofAddress: baseAddr, path: SmallProjectionPath())
208+
== .abortWalk {
209+
return nil
210+
}
211+
return (initialAddress: baseAddr, initializingStore: walker.initializer!)
212+
}
213+
}
214+
215+
// Walk the address def-use paths to find a single initialization.
216+
//
217+
// Implements AddressUseVisitor to guarantee that we can't miss any
218+
// stores. This separates escapingAddressUse from leafAddressUse.
219+
//
220+
// TODO: Make AddressDefUseWalker always conform to AddressUseVisitor once we're
221+
// ready to debug changes to escape analysis etc...
222+
//
223+
// Future:
224+
// AddressUseVisitor
225+
// (how to transitively follow uses, complete classification)
226+
// -> AddressPathDefUseWalker
227+
// (follow projections and track Path,
228+
// client handles all other uses, such as access scopes)
229+
// -> AddressProjectionDefUseWalker
230+
// (follow projections, track Path, ignore access scopes,
231+
// merge all other callbacks into only two:
232+
// instantaneousAddressUse vs. escapingAddressUse)
233+
//
234+
// FIXME: This currently assumes that isAddressInitialization catches
235+
// writes to the memory address. We need a complete abstraction that
236+
// distinguishes between `mayWriteToMemory` for dependence vs. actual
237+
// modification of memory.
238+
struct AddressInitializationWalker: AddressDefUseWalker, AddressUseVisitor {
239+
let context: any Context
240+
241+
var walkDownCache = WalkerCache<SmallProjectionPath>()
242+
243+
var isProjected = false
244+
var initializer: Instruction?
245+
246+
private mutating func setInitializer(instruction: Instruction) -> WalkResult {
247+
// An initializer must be unique and store the full value.
248+
if initializer != nil || isProjected {
249+
initializer = nil
250+
return .abortWalk
251+
}
252+
initializer = instruction
253+
return .continueWalk
254+
}
255+
}
256+
257+
// Implement AddressDefUseWalker
258+
extension AddressInitializationWalker {
259+
mutating func leafUse(address: Operand, path: SmallProjectionPath)
260+
-> WalkResult {
261+
isProjected = !path.isEmpty
262+
return classifyAddress(operand: address)
263+
}
264+
}
265+
266+
// Implement AddresUseVisitor
267+
extension AddressInitializationWalker {
268+
/// An address projection produces a single address result and does not
269+
/// escape its address operand in any other way.
270+
mutating func projectedAddressUse(of operand: Operand, into value: Value)
271+
-> WalkResult {
272+
// AddressDefUseWalker should catch most of these.
273+
return .abortWalk
274+
}
275+
276+
mutating func scopedAddressUse(of operand: Operand) -> WalkResult {
277+
// AddressDefUseWalker currently skips most of these.
278+
return .abortWalk
279+
}
280+
281+
mutating func scopeEndingAddressUse(of operand: Operand) -> WalkResult {
282+
// AddressDefUseWalker currently skips most of these.
283+
return .continueWalk
284+
}
285+
286+
mutating func leafAddressUse(of operand: Operand) -> WalkResult {
287+
if operand.isAddressInitialization {
288+
return setInitializer(instruction: operand.instruction)
289+
}
290+
// FIXME: check mayWriteToMemory but ignore non-stores. Currently,
291+
// stores should all be checked my isAddressInitialization, but
292+
// this is not robust.
293+
return .continueWalk
294+
}
295+
296+
mutating func appliedAddressUse(of operand: Operand, by apply: FullApplySite)
297+
-> WalkResult {
298+
if operand.isAddressInitialization {
299+
return setInitializer(instruction: operand.instruction)
300+
}
301+
guard let convention = apply.convention(of: operand) else {
302+
return .continueWalk
303+
}
304+
return convention.isIndirectIn ? .continueWalk : .abortWalk
305+
}
306+
307+
mutating func loadedAddressUse(of operand: Operand, into value: Value)
308+
-> WalkResult {
309+
return .continueWalk
310+
}
311+
312+
mutating func loadedAddressUse(of operand: Operand, into address: Operand)
313+
-> WalkResult {
314+
return .continueWalk
315+
}
316+
317+
mutating func dependentAddressUse(of operand: Operand, into value: Value)
318+
-> WalkResult {
319+
return .continueWalk
320+
}
321+
322+
mutating func escapingAddressUse(of operand: Operand) -> WalkResult {
323+
return .abortWalk
324+
}
325+
326+
mutating func unknownAddressUse(of operand: Operand) -> WalkResult {
327+
return .abortWalk
328+
}
329+
}

SwiftCompilerSources/Sources/Optimizer/Utilities/OptUtils.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,49 @@ extension Builder {
183183
}
184184
}
185185

186+
extension Value {
187+
/// Return true if all elements occur on or after `instruction` in
188+
/// control flow order. If this returns true, then zero or more uses
189+
/// of `self` may be operands of `instruction` itself.
190+
///
191+
/// This performs a backward CFG walk from `instruction` to `self`.
192+
func usesOccurOnOrAfter(instruction: Instruction, _ context: some Context)
193+
-> Bool {
194+
var users = InstructionSet(context)
195+
defer { users.deinitialize() }
196+
uses.lazy.map({ $0.instruction }).forEach { users.insert($0) }
197+
198+
var worklist = InstructionWorklist(context)
199+
defer { worklist.deinitialize() }
200+
201+
let pushPreds = { (block: BasicBlock) in
202+
block.predecessors.lazy.map({ pred in pred.terminator }).forEach {
203+
worklist.pushIfNotVisited($0)
204+
}
205+
}
206+
if let prev = instruction.previous {
207+
worklist.pushIfNotVisited(prev)
208+
} else {
209+
pushPreds(instruction.parentBlock)
210+
}
211+
let definingInst = self.definingInstruction
212+
while let lastInst = worklist.pop() {
213+
for inst in ReverseInstructionList(first: lastInst) {
214+
if users.contains(inst) {
215+
return false
216+
}
217+
if inst == definingInst {
218+
break
219+
}
220+
}
221+
if lastInst.parentBlock != self.parentBlock {
222+
pushPreds(lastInst.parentBlock)
223+
}
224+
}
225+
return true
226+
}
227+
}
228+
186229
extension Value {
187230
/// Makes this new owned value available to be used in the block `destBlock`.
188231
///

0 commit comments

Comments
 (0)