Skip to content

Commit 8961e29

Browse files
committed
swift side effects: some additions and refactoring
add `Function.getSideEffects(forArgument:,atIndex:,withConvention:)`
1 parent f6fb3c7 commit 8961e29

File tree

2 files changed

+89
-31
lines changed

2 files changed

+89
-31
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/ComputeSideEffects.swift

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,17 @@ private struct CollectedEffects {
203203

204204
if escapeWalker.hasUnknownUses(argument: argument) {
205205
// Worst case: we don't know anything about how the argument escapes.
206-
addEffects(globalEffects.restrictedTo(argument: argument, withConvention: argument.convention), to: argument)
206+
addEffects(globalEffects.restrictedTo(argument: argument.at(SmallProjectionPath(.anything)),
207+
withConvention: argument.convention), to: argument)
207208

208209
} else if escapeWalker.foundTakingLoad {
209210
// In most cases we can just ignore loads. But if the load is actually "taking" the
210211
// underlying memory allocation, we must consider this as a "destroy", because we don't
211212
// know what's happening with the loaded value. If there is any destroying instruction in the
212213
// function, it might be the destroy of the loaded value.
213214
let effects = SideEffects.GlobalEffects(ownership: globalEffects.ownership)
214-
addEffects(effects.restrictedTo(argument: argument, withConvention: argument.convention), to: argument)
215+
addEffects(effects.restrictedTo(argument: argument.at(SmallProjectionPath(.anything)),
216+
withConvention: argument.convention), to: argument)
215217

216218
} else if escapeWalker.foundConsumingPartialApply && globalEffects.ownership.destroy {
217219
// Similar situation with apply instructions which consume the callee closure.
@@ -236,7 +238,7 @@ private struct CollectedEffects {
236238
} else {
237239
// The callee doesn't have any computed effects. At least we can do better
238240
// if it has any defined effect attribute (like e.g. `[readnone]`).
239-
globalEffects.merge(with: callee.definedSideEffects)
241+
globalEffects.merge(with: callee.definedGlobalEffects)
240242
}
241243
}
242244

@@ -252,15 +254,12 @@ private struct CollectedEffects {
252254
if let calleePath = calleeEffect.copy { addEffects(.copy, to: argument, fromInitialPath: calleePath) }
253255
if let calleePath = calleeEffect.destroy { addEffects(.destroy, to: argument, fromInitialPath: calleePath) }
254256
} else {
255-
var calleeEffects = callee.definedSideEffects
256-
257257
let convention = callee.getArgumentConvention(for: calleeArgIdx)
258-
if convention.isIndirectIn {
259-
calleeEffects.memory.read = true
260-
} else if convention == .indirectOut {
261-
calleeEffects.memory.write = true
262-
}
263-
addEffects(calleeEffects.restrictedTo(argument: argument, withConvention: convention), to: argument)
258+
let wholeArgument = argument.at(SmallProjectionPath(.anything))
259+
let calleeEffects = callee.getSideEffects(forArgument: wholeArgument,
260+
atIndex: calleeArgIdx,
261+
withConvention: convention)
262+
addEffects(calleeEffects.restrictedTo(argument: wholeArgument, withConvention: convention), to: argument)
264263
}
265264
}
266265
}
@@ -274,7 +273,7 @@ private struct CollectedEffects {
274273
// deallocates an object.
275274
if let destructors = calleeAnalysis.getDestructors(of: addressOrValue.type) {
276275
for destructor in destructors {
277-
globalEffects.merge(with: destructor.effects.accumulatedSideEffects)
276+
globalEffects.merge(with: destructor.getSideEffects())
278277
}
279278
} else {
280279
globalEffects = .worstEffects

SwiftCompilerSources/Sources/SIL/Effects.swift

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,6 @@ public struct FunctionEffects : CustomStringConvertible, NoReflectionChildren {
3131
self.sideEffects = nil
3232
}
3333

34-
/// The "accumulated" side-effects of the function, which includes the global effects and
35-
/// all argument effects.
36-
public var accumulatedSideEffects: SideEffects.GlobalEffects {
37-
if let sideEffects = sideEffects {
38-
return sideEffects.accumulatedEffects
39-
}
40-
return .worstEffects
41-
}
42-
4334
public var description: String {
4435
var numArgEffects = escapeEffects.arguments.reduce(0, { max($0, $1.argumentIndex) }) + 1
4536
if let sideEffects = sideEffects {
@@ -70,14 +61,77 @@ public struct FunctionEffects : CustomStringConvertible, NoReflectionChildren {
7061

7162
extension Function {
7263

73-
/// The global side effects of the function which are defined by attributes.
64+
/// Returns the global side effects of the function.
65+
public func getSideEffects() -> SideEffects.GlobalEffects {
66+
if let sideEffects = effects.sideEffects {
67+
/// There are computed side effects.
68+
return sideEffects.accumulatedEffects
69+
} else {
70+
71+
var effects = definedGlobalEffects
72+
73+
// Even a `[readnone]` function can read from indirect arguments.
74+
if (0..<numArguments).contains(where: {getArgumentConvention(for: $0).isIndirectIn}) {
75+
effects.memory.read = true
76+
}
77+
// Even `[readnone]` and `[readonly]` functions write to indirect results.
78+
if numIndirectResultArguments > 0 {
79+
effects.memory.write = true
80+
}
81+
return effects
82+
}
83+
}
84+
85+
/// Returns the side effects for a function argument.
7486
///
75-
/// This API should be used if there are no derived side-effects available, i.e. `effects.sideEffects` is nil.
76-
/// It returns "worstEffects" unless an effect attribute (e.g. `[readnone]`) is set for the function.
77-
public var definedSideEffects: SideEffects.GlobalEffects {
87+
/// The `argument` can be a function argument in this function or an apply argument in a caller.
88+
public func getSideEffects(forArgument argument: ProjectedValue,
89+
atIndex argumentIndex: Int,
90+
withConvention convention: ArgumentConvention) -> SideEffects.GlobalEffects {
91+
if let sideEffects = effects.sideEffects {
92+
/// There are computed side effects.
93+
var result = SideEffects.GlobalEffects()
94+
let argEffect = sideEffects.getArgumentEffects(for: argumentIndex)
95+
if let effectPath = argEffect.read, effectPath.mayOverlap(with: argument.path) {
96+
result.memory.read = true
97+
}
98+
if let effectPath = argEffect.write, effectPath.mayOverlap(with: argument.path) {
99+
result.memory.write = true
100+
}
101+
if let effectPath = argEffect.copy, effectPath.mayOverlap(with: argument.path) {
102+
result.ownership.copy = true
103+
}
104+
if let effectPath = argEffect.destroy, effectPath.mayOverlap(with: argument.path) {
105+
result.ownership.destroy = true
106+
}
107+
return result
108+
} else {
109+
/// Even for defined effects, there might be additional effects due to the argument conventions.
110+
var result = definedGlobalEffects
111+
if convention.isIndirectIn {
112+
// Even a `[readnone]` function can read from an indirect argument.
113+
result.memory.read = true
114+
} else if convention == .indirectOut {
115+
// Even `[readnone]` and `[readonly]` functions write to indirect results.
116+
result.memory.write = true
117+
}
118+
return result.restrictedTo(argument: argument, withConvention: convention)
119+
}
120+
}
121+
122+
/// Global effect of the function, defined by effect attributes.
123+
public var definedGlobalEffects: SideEffects.GlobalEffects {
124+
switch name {
125+
case "_swift_stdlib_malloc_size", "_swift_stdlib_has_malloc_size":
126+
// These C runtime functions, which are used in the array implementation, have defined effects.
127+
return SideEffects.GlobalEffects(memory: SideEffects.Memory(read: true))
128+
default:
129+
break
130+
}
78131
var result = SideEffects.GlobalEffects.worstEffects
79132
switch effectAttribute {
80133
case .none:
134+
// The common case: there is no effect attribute, so we have to assume the worst effects.
81135
break
82136
case .readNone:
83137
result.memory.read = false
@@ -421,9 +475,9 @@ public struct SideEffects : CustomStringConvertible, NoReflectionChildren {
421475
}
422476

423477
/// Removes effects, which cannot occur for an `argument` value with a given `convention`.
424-
public func restrictedTo(argument: Value, withConvention convention: ArgumentConvention) -> GlobalEffects {
478+
public func restrictedTo(argument: ProjectedValue, withConvention convention: ArgumentConvention) -> GlobalEffects {
425479
var result = self
426-
let isTrivial = argument.type.isTrivial(in: argument.function)
480+
let isTrivial = argument.value.hasTrivialNonPointerType
427481
if isTrivial {
428482
// There cannot be any ownership effects on trivial arguments.
429483
result.ownership = SideEffects.Ownership()
@@ -439,14 +493,19 @@ public struct SideEffects : CustomStringConvertible, NoReflectionChildren {
439493
result.ownership.copy = false
440494
result.ownership.destroy = false
441495

442-
// Note that `directGuaranteed` still has a "destroy" effect, because an object stored in
443-
// a class property could be destroyed.
444-
case .directOwned, .directUnowned, .directGuaranteed:
496+
case .directGuaranteed:
497+
// Note that `directGuaranteed` still has a "destroy" effect, because an object stored in
498+
// a class property could be destroyed.
499+
if argument.path.hasNoClassProjection {
500+
result.ownership.destroy = false
501+
}
502+
fallthrough
503+
case .directOwned, .directUnowned:
445504
if isTrivial {
446505
// Trivial direct arguments cannot have class properties which could be loaded from/stored to.
447506
result.memory = SideEffects.Memory()
448507
}
449-
break
508+
450509
case .indirectInout, .indirectInoutAliasable:
451510
break
452511
}

0 commit comments

Comments
 (0)