Skip to content

Commit 8a8a895

Browse files
committed
alias analysis: compute more precise memory effects of builtin "once"
* Check if the address in question is even visible from outside the function * Return the memory effects of the called function Also, add a new API `Instruction.memoryEffects`, which is internally used by `mayReadFromMemory` et al.
1 parent 3560360 commit 8a8a895

File tree

6 files changed

+89
-66
lines changed

6 files changed

+89
-66
lines changed

SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,17 @@ struct AliasAnalysis {
6868
let inst = bridgedInst.instruction
6969
let val = bridgedVal.value
7070
let path = AliasAnalysis.getPtrOrAddressPath(for: val)
71-
if let apply = inst as? ApplySite {
72-
let effect = getMemoryEffect(of: apply, for: val, path: path, context)
73-
switch (effect.read, effect.write) {
74-
case (false, false): return .None
75-
case (true, false): return .MayRead
76-
case (false, true): return .MayWrite
77-
case (true, true): return .MayReadWrite
71+
switch inst {
72+
case let apply as ApplySite:
73+
return getMemoryEffect(ofApply: apply, for: val, path: path, context).bridged
74+
case let builtin as BuiltinInst:
75+
return getMemoryEffect(ofBuiltin: builtin, for: val, path: path, context).bridged
76+
default:
77+
if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), context) {
78+
return .MayReadWrite
7879
}
80+
return .None
7981
}
80-
if val.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst, isAddress: true), context) {
81-
return .MayReadWrite
82-
}
83-
return .None
8482
},
8583

8684
// isObjReleasedFn
@@ -121,7 +119,7 @@ struct AliasAnalysis {
121119
}
122120
}
123121

124-
private func getMemoryEffect(of apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {
122+
private func getMemoryEffect(ofApply apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {
125123
let calleeAnalysis = context.calleeAnalysis
126124
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: calleeAnalysis, isAddress: true)
127125
let memoryEffects: SideEffects.Memory
@@ -132,7 +130,7 @@ private func getMemoryEffect(of apply: ApplySite, for address: Value, path: Smal
132130
memoryEffects = result.memory
133131
} else {
134132
// `address` has unknown escapes. So we have to take the global effects of the called function(s).
135-
memoryEffects = calleeAnalysis.getSideEffects(of: apply).memory
133+
memoryEffects = calleeAnalysis.getSideEffects(ofApply: apply).memory
136134
}
137135
// Do some magic for `let` variables. Function calls cannot modify let variables.
138136
// The only exception is that the let variable is directly passed to an indirect out of the
@@ -144,14 +142,28 @@ private func getMemoryEffect(of apply: ApplySite, for address: Value, path: Smal
144142
return memoryEffects
145143
}
146144

145+
private func getMemoryEffect(ofBuiltin builtin: BuiltinInst, for address: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Memory {
146+
147+
switch builtin.id {
148+
case .Once, .OnceWithContext:
149+
if !address.at(path).isEscaping(using: AddressVisibleByBuiltinOnceVisitor(), context) {
150+
return SideEffects.Memory()
151+
}
152+
let callee = builtin.operands[1].value
153+
return context.calleeAnalysis.getSideEffects(ofCallee: callee).memory
154+
default:
155+
return builtin.memoryEffects
156+
}
157+
}
158+
147159
private func getOwnershipEffect(of apply: ApplySite, for value: Value, path: SmallProjectionPath, _ context: FunctionPassContext) -> SideEffects.Ownership {
148160
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: context.calleeAnalysis, isAddress: false)
149161
if let result = value.at(path).visit(using: visitor, context) {
150162
// The resulting effects are the argument effects to which `value` escapes to.
151163
return result.ownership
152164
} else {
153165
// `value` has unknown escapes. So we have to take the global effects of the called function(s).
154-
return visitor.calleeAnalysis.getSideEffects(of: apply).ownership
166+
return visitor.calleeAnalysis.getSideEffects(ofApply: apply).ownership
155167
}
156168
}
157169

@@ -180,6 +192,11 @@ private struct SideEffectsVisitor : EscapeVisitorWithResult {
180192
var followLoads: Bool { !isAddress }
181193
}
182194

195+
private struct AddressVisibleByBuiltinOnceVisitor : EscapeVisitor {
196+
var followTrivialTypes: Bool { true }
197+
var followLoads: Bool { false }
198+
}
199+
183200
/// Lets `ProjectedValue.isEscaping` return true if the value is "escaping" to the `target` instruction.
184201
private struct EscapesToInstructionVisitor : EscapeVisitor {
185202
let target: Instruction
@@ -229,3 +246,13 @@ private struct IsIndirectResultWalker: AddressDefUseWalker {
229246
}
230247
}
231248

249+
private extension SideEffects.Memory {
250+
var bridged: swift.MemoryBehavior {
251+
switch (read, write) {
252+
case (false, false): return .None
253+
case (true, false): return .MayRead
254+
case (false, true): return .MayWrite
255+
case (true, true): return .MayReadWrite
256+
}
257+
}
258+
}

SwiftCompilerSources/Sources/Optimizer/Analysis/CalleeAnalysis.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public struct CalleeAnalysis {
2525
// getMemBehaviorFn
2626
{ (bridgedApply: BridgedInstruction, observeRetains: Bool, bca: BridgedCalleeAnalysis) -> swift.MemoryBehavior in
2727
let apply = bridgedApply.instruction as! ApplySite
28-
let e = bca.analysis.getSideEffects(of: apply)
28+
let e = bca.analysis.getSideEffects(ofApply: apply)
2929
return e.getMemBehavior(observeRetains: observeRetains)
3030
}
3131
)
@@ -60,8 +60,12 @@ public struct CalleeAnalysis {
6060
}
6161

6262
/// Returns the global (i.e. not argument specific) side effects of an apply.
63-
public func getSideEffects(of apply: ApplySite) -> SideEffects.GlobalEffects {
64-
guard let callees = getCallees(callee: apply.callee) else {
63+
public func getSideEffects(ofApply apply: ApplySite) -> SideEffects.GlobalEffects {
64+
return getSideEffects(ofCallee: apply.callee)
65+
}
66+
67+
public func getSideEffects(ofCallee callee: Value) -> SideEffects.GlobalEffects {
68+
guard let callees = getCallees(callee: callee) else {
6569
return .worstEffects
6670
}
6771

SwiftCompilerSources/Sources/SIL/Instruction.swift

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -73,32 +73,24 @@ public class Instruction : CustomStringConvertible, Hashable {
7373
return mayTrap || mayWriteToMemory
7474
}
7575

76-
final public var mayReadFromMemory: Bool {
76+
final public var memoryEffects: SideEffects.Memory {
7777
switch bridged.getMemBehavior() {
78-
case .MayRead, .MayReadWrite, .MayHaveSideEffects:
79-
return true
80-
default:
81-
return false
78+
case .None:
79+
return SideEffects.Memory()
80+
case .MayRead:
81+
return SideEffects.Memory(read: true)
82+
case .MayWrite:
83+
return SideEffects.Memory(write: true)
84+
case .MayReadWrite, .MayHaveSideEffects:
85+
return SideEffects.Memory(read: true, write: true)
86+
default:
87+
fatalError("invalid memory behavior")
8288
}
8389
}
8490

85-
final public var mayWriteToMemory: Bool {
86-
switch bridged.getMemBehavior() {
87-
case .MayWrite, .MayReadWrite, .MayHaveSideEffects:
88-
return true
89-
default:
90-
return false
91-
}
92-
}
93-
94-
final public var mayReadOrWriteMemory: Bool {
95-
switch bridged.getMemBehavior() {
96-
case .MayRead, .MayWrite, .MayReadWrite, .MayHaveSideEffects:
97-
return true
98-
default:
99-
return false
100-
}
101-
}
91+
final public var mayReadFromMemory: Bool { memoryEffects.read }
92+
final public var mayWriteToMemory: Bool { memoryEffects.write }
93+
final public var mayReadOrWriteMemory: Bool { memoryEffects.read || memoryEffects.write }
10294

10395
public final var mayRelease: Bool {
10496
return bridged.mayRelease()

lib/SILOptimizer/Analysis/MemoryBehavior.cpp

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -314,34 +314,11 @@ MemBehavior MemoryBehaviorVisitor::visitMarkUnresolvedMoveAddrInst(
314314
}
315315

316316
MemBehavior MemoryBehaviorVisitor::visitBuiltinInst(BuiltinInst *BI) {
317-
// If our callee is not a builtin, be conservative and return may have side
318-
// effects.
319-
if (!BI) {
320-
return MemBehavior::MayHaveSideEffects;
321-
}
322-
323-
// If the builtin is read none, it does not read or write memory.
324-
if (!BI->mayReadOrWriteMemory()) {
325-
LLVM_DEBUG(llvm::dbgs() << " Found apply of read none builtin. Returning"
326-
" None.\n");
327-
return MemBehavior::None;
328-
}
329-
330-
// If the builtin is side effect free, then it can only read memory.
331-
if (!BI->mayHaveSideEffects()) {
332-
LLVM_DEBUG(llvm::dbgs() << " Found apply of side effect free builtin. "
333-
"Returning MayRead.\n");
334-
return MemBehavior::MayRead;
317+
MemBehavior mb = BI->getMemoryBehavior();
318+
if (mb != MemBehavior::None) {
319+
return AA->getMemoryBehaviorOfInst(V, BI);
335320
}
336-
337-
// FIXME: If the value (or any other values from the instruction that the
338-
// value comes from) that we are tracking does not escape and we don't alias
339-
// any of the arguments of the apply inst, we should be ok.
340-
341-
// Otherwise be conservative and return that we may have side effects.
342-
LLVM_DEBUG(llvm::dbgs() << " Found apply of side effect builtin. "
343-
"Returning MayHaveSideEffects.\n");
344-
return MemBehavior::MayHaveSideEffects;
321+
return MemBehavior::None;
345322
}
346323

347324
MemBehavior MemoryBehaviorVisitor::visitTryApplyInst(TryApplyInst *AI) {

lib/SILOptimizer/UtilityPasses/MemBehaviorDumper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class MemBehaviorDumper : public SILModuleTransform {
6868
case SILInstructionKind::LoadInst:
6969
case SILInstructionKind::StoreInst:
7070
case SILInstructionKind::CopyAddrInst:
71+
case SILInstructionKind::BuiltinInst:
7172
return true;
7273
default:
7374
return false;

test/SILOptimizer/mem-behavior.sil

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,3 +980,25 @@ bb0(%0 : $Builtin.Int32):
980980
return %r : $()
981981
}
982982

983+
sil_global @gi : $Int
984+
sil @g_init : $@convention(c) () -> ()
985+
986+
// CHECK-LABEL: @test_builtin_once
987+
// CHECK: PAIR #0.
988+
// CHECK-NEXT: %4 = builtin "once"(%0 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $()
989+
// CHECK-NEXT: %1 = global_addr @gi : $*Int
990+
// CHECK-NEXT: r=1,w=1
991+
// CHECK: PAIR #1.
992+
// CHECK-NEXT: %4 = builtin "once"(%0 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $()
993+
// CHECK-NEXT: %2 = alloc_stack $Int
994+
// CHECK-NEXT: r=0,w=0
995+
sil @test_builtin_once : $@convention(thin) (Builtin.RawPointer) -> () {
996+
bb0(%0 : $Builtin.RawPointer):
997+
%1 = global_addr @gi : $*Int
998+
%2 = alloc_stack $Int
999+
%3 = function_ref @g_init : $@convention(c) () -> ()
1000+
%4 = builtin "once"(%0 : $Builtin.RawPointer, %3 : $@convention(c) () -> ()) : $()
1001+
dealloc_stack %2 : $*Int
1002+
%6 = tuple ()
1003+
return %6 : $()
1004+
}

0 commit comments

Comments
 (0)