Skip to content

Commit 004d0b1

Browse files
authored
Merge pull request #61715 from eeckstein/alias-analysis
Use new escape- and side-effect analysis in alias analysis and other optimizations
2 parents 77527c9 + 75aace0 commit 004d0b1

File tree

58 files changed

+861
-9112
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+861
-9112
lines changed

SwiftCompilerSources/Sources/Optimizer/Analysis/AliasAnalysis.swift

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,148 @@ struct AliasAnalysis {
6060
// Any other non-address value means: all addresses of any referenced class instances within the value.
6161
return SmallProjectionPath(.anyValueFields).push(.anyClassField).push(.anyValueFields)
6262
}
63+
64+
static func register() {
65+
AliasAnalysis_register(
66+
// getMemEffectsFn
67+
{ (bridgedCtxt: BridgedPassContext, bridgedVal: BridgedValue, bridgedInst: BridgedInstruction) -> BridgedMemoryBehavior in
68+
let context = PassContext(_bridged: bridgedCtxt)
69+
let inst = bridgedInst.instruction
70+
let val = bridgedVal.value
71+
let path = AliasAnalysis.getPtrOrAddressPath(for: val)
72+
if let apply = inst as? ApplySite {
73+
let effect = getMemoryEffect(of: apply, for: val, path: path, context)
74+
switch (effect.read, effect.write) {
75+
case (false, false): return NoneBehavior
76+
case (true, false): return MayReadBehavior
77+
case (false, true): return MayWriteBehavior
78+
case (true, true): return MayReadWriteBehavior
79+
}
80+
}
81+
if val.at(path).isAddressEscaping(using: EscapesToInstructionVisitor(target: inst), context) {
82+
return MayReadWriteBehavior
83+
}
84+
return NoneBehavior
85+
},
86+
87+
// isObjReleasedFn
88+
{ (bridgedCtxt: BridgedPassContext, bridgedObj: BridgedValue, bridgedInst: BridgedInstruction) -> Bool in
89+
let context = PassContext(_bridged: bridgedCtxt)
90+
let inst = bridgedInst.instruction
91+
let obj = bridgedObj.value
92+
let path = SmallProjectionPath(.anyValueFields)
93+
if let apply = inst as? ApplySite {
94+
let effect = getOwnershipEffect(of: apply, for: obj, path: path, context)
95+
return effect.destroy
96+
}
97+
return obj.at(path).isEscaping(using: EscapesToInstructionVisitor(target: inst), context)
98+
},
99+
100+
// isAddrVisibleFromObj
101+
{ (bridgedCtxt: BridgedPassContext, bridgedAddr: BridgedValue, bridgedObj: BridgedValue) -> Bool in
102+
let context = PassContext(_bridged: bridgedCtxt)
103+
let addr = bridgedAddr.value.at(AliasAnalysis.getPtrOrAddressPath(for: bridgedAddr.value))
104+
105+
// This is similar to `canReferenceSameFieldFn`, except that all addresses of all objects are
106+
// considered which are transitively visible from `bridgedObj`.
107+
let anythingReachableFromObj = bridgedObj.value.at(SmallProjectionPath(.anything))
108+
return addr.canAddressAlias(with: anythingReachableFromObj, context)
109+
},
110+
111+
// canReferenceSameFieldFn
112+
{ (bridgedCtxt: BridgedPassContext, bridgedLhs: BridgedValue, bridgedRhs: BridgedValue) -> Bool in
113+
let context = PassContext(_bridged: bridgedCtxt)
114+
115+
// If `lhs` or `rhs` is not an address, but an object, it means: check for alias of any class
116+
// field address of the object.
117+
let lhs = bridgedLhs.value.at(AliasAnalysis.getPtrOrAddressPath(for: bridgedLhs.value))
118+
let rhs = bridgedRhs.value.at(AliasAnalysis.getPtrOrAddressPath(for: bridgedRhs.value))
119+
return lhs.canAddressAlias(with: rhs, context)
120+
}
121+
)
122+
}
123+
}
124+
125+
private func getMemoryEffect(of apply: ApplySite, for address: Value, path: SmallProjectionPath, _ context: PassContext) -> SideEffects.Memory {
126+
let calleeAnalysis = context.calleeAnalysis
127+
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: calleeAnalysis)
128+
let memoryEffects: SideEffects.Memory
129+
130+
// First try to figure out to which argument(s) the address "escapes" to.
131+
if let result = address.at(path).visitAddress(using: visitor, context) {
132+
// The resulting effects are the argument effects to which `address` escapes to.
133+
memoryEffects = result.memory
134+
} else {
135+
// `address` has unknown escapes. So we have to take the global effects of the called function(s).
136+
memoryEffects = calleeAnalysis.getSideEffects(of: apply).memory
137+
}
138+
// Do some magic for `let` variables. Function calls cannot modify let variables.
139+
// The only exception is that the let variable is directly passed to an indirect out of the
140+
// apply.
141+
// TODO: make this a more formal and verified approach.
142+
if memoryEffects.write && address.accessBase.isLet && !address.isIndirectResult(of: apply) {
143+
return SideEffects.Memory(read: memoryEffects.read, write: false)
144+
}
145+
return memoryEffects
146+
}
147+
148+
private func getOwnershipEffect(of apply: ApplySite, for value: Value, path: SmallProjectionPath, _ context: PassContext) -> SideEffects.Ownership {
149+
let visitor = SideEffectsVisitor(apply: apply, calleeAnalysis: context.calleeAnalysis)
150+
if let result = value.at(path).visit(using: visitor, context) {
151+
// The resulting effects are the argument effects to which `value` escapes to.
152+
return result.ownership
153+
} else {
154+
// `value` has unknown escapes. So we have to take the global effects of the called function(s).
155+
return visitor.calleeAnalysis.getSideEffects(of: apply).ownership
156+
}
157+
}
158+
159+
private struct SideEffectsVisitor : EscapeVisitorWithResult {
160+
let apply: ApplySite
161+
let calleeAnalysis: CalleeAnalysis
162+
var result = SideEffects.GlobalEffects()
163+
164+
mutating func visitUse(operand: Operand, path: EscapePath) -> UseResult {
165+
let user = operand.instruction
166+
if user is ReturnInst {
167+
// Anything which is returned cannot escape to an instruction inside the function.
168+
return .ignore
169+
}
170+
if user == apply {
171+
if let argIdx = apply.argumentIndex(of: operand) {
172+
let e = calleeAnalysis.getSideEffects(of: apply, forArgument: argIdx, path: path.projectionPath)
173+
result.merge(with: e)
174+
}
175+
}
176+
return .continueWalk
177+
}
178+
}
179+
180+
private extension Value {
181+
/// Returns true if this address is passed as indirect out of `apply`.
182+
func isIndirectResult(of apply: ApplySite) -> Bool {
183+
guard let fullApply = apply as? FullApplySite else {
184+
return false
185+
}
186+
if fullApply.numIndirectResultArguments == 0 {
187+
return false
188+
}
189+
190+
var walker = IsIndirectResultWalker(apply: fullApply)
191+
return walker.walkDownUses(ofAddress: self, path: UnusedWalkingPath()) == .abortWalk
192+
}
193+
}
194+
195+
private struct IsIndirectResultWalker: AddressDefUseWalker {
196+
let apply: FullApplySite
197+
198+
mutating func leafUse(address: Operand, path: UnusedWalkingPath) -> WalkResult {
199+
if address.instruction == apply,
200+
let argIdx = apply.argumentIndex(of: address),
201+
argIdx < apply.numIndirectResultArguments {
202+
return .abortWalk
203+
}
204+
return .continueWalk
205+
}
63206
}
207+

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,6 @@ private func registerSwiftPasses() {
7979
}
8080

8181
private func registerSwiftAnalyses() {
82+
AliasAnalysis.register()
8283
CalleeAnalysis.register()
8384
}

include/swift/SILOptimizer/Analysis/AccessStorageAnalysis.h

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,19 @@
1919
// FunctionAccessStorage.unidentifiedAccess. This does not imply that all
2020
// accesses within the function have Unidentified AccessStorage.
2121
//
22-
// Note: This interprocedural analysis can be easily augmented to simultaneously
23-
// compute FunctionSideEffects, without using a separate analysis, by adding
24-
// FunctionSideEffects as a member of FunctionAccessStorage. However, passes
25-
// that use AccessStorageAnalysis do not currently need SideEffectAnalysis.
26-
//
2722
//===----------------------------------------------------------------------===//
2823
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H
2924
#define SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H
3025

3126
#include "swift/SIL/MemAccessUtils.h"
3227
#include "swift/SIL/SILFunction.h"
3328
#include "swift/SIL/SILInstruction.h"
34-
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
29+
#include "swift/SILOptimizer/Analysis/BottomUpIPAnalysis.h"
3530

3631
namespace swift {
3732

33+
class BasicCalleeAnalysis;
34+
3835
/// Information about a formal access within a function pertaining to a
3936
/// particular AccessStorage location.
4037
class StorageAccessInfo : public AccessStorage {
@@ -340,16 +337,121 @@ class FunctionAccessStorage {
340337
/// Use the GenericFunctionEffectAnalysis API to get the results of the analysis:
341338
/// - geEffects(SILFunction*)
342339
/// - getCallSiteEffects(FunctionEffects &callEffects, FullApplySite fullApply)
343-
class AccessStorageAnalysis
344-
: public GenericFunctionEffectAnalysis<FunctionAccessStorage> {
340+
class AccessStorageAnalysis : public BottomUpIPAnalysis {
341+
342+
/// Stores the analysis data, e.g. side-effects, for a function.
343+
struct FunctionInfo : public FunctionInfoBase<FunctionInfo> {
344+
345+
/// The function effects.
346+
FunctionAccessStorage functionEffects;
347+
348+
/// Back-link to the function.
349+
SILFunction *F;
350+
351+
/// Used during recomputation to indicate if the side-effects of a caller
352+
/// must be updated.
353+
bool needUpdateCallers = false;
354+
355+
FunctionInfo(SILFunction *F) : F(F) {}
356+
357+
/// Clears the analysis data on invalidation.
358+
void clear() { functionEffects.clear(); }
359+
};
360+
361+
typedef BottomUpFunctionOrder<FunctionInfo> FunctionOrder;
362+
363+
enum {
364+
/// The maximum call-graph recursion depth for recomputing the analysis.
365+
/// This is a relatively small number to reduce compile time in case of
366+
/// large cycles in the call-graph.
367+
/// In case of no cycles, we should not hit this limit at all because the
368+
/// pass manager processes functions in bottom-up order.
369+
MaxRecursionDepth = 5
370+
};
371+
372+
/// All the function effect information for the whole module.
373+
llvm::DenseMap<SILFunction *, FunctionInfo *> functionInfoMap;
374+
375+
/// The allocator for the map of values in FunctionInfoMap.
376+
llvm::SpecificBumpPtrAllocator<FunctionInfo> allocator;
377+
378+
/// Callee analysis, used for determining the callees at call sites.
379+
BasicCalleeAnalysis *BCA;
380+
345381
public:
346382
AccessStorageAnalysis()
347-
: GenericFunctionEffectAnalysis<FunctionAccessStorage>(
348-
SILAnalysisKind::AccessStorage) {}
383+
: BottomUpIPAnalysis(SILAnalysisKind::AccessStorage) {}
349384

350385
static bool classof(const SILAnalysis *S) {
351386
return S->getKind() == SILAnalysisKind::AccessStorage;
352387
}
388+
389+
const FunctionAccessStorage &getEffects(SILFunction *F) {
390+
FunctionInfo *functionInfo = getFunctionInfo(F);
391+
if (!functionInfo->isValid())
392+
recompute(functionInfo);
393+
return functionInfo->functionEffects;
394+
}
395+
396+
/// Get the merged effects of all callees at the given call site from the
397+
/// callee's perspective (don't transform parameter effects).
398+
void getCalleeEffects(FunctionAccessStorage &calleeEffects,
399+
FullApplySite fullApply);
400+
401+
/// Get the merge effects of all callees at the given call site from the
402+
/// caller's perspective. Parameter effects are translated into information
403+
/// for the caller's arguments, and local effects are dropped.
404+
void getCallSiteEffects(FunctionAccessStorage &callEffects,
405+
FullApplySite fullApply) {
406+
FunctionAccessStorage calleeEffects;
407+
getCalleeEffects(calleeEffects, fullApply);
408+
callEffects.mergeFromApply(calleeEffects, fullApply);
409+
}
410+
411+
BasicCalleeAnalysis *getBasicCalleeAnalysis() { return BCA; }
412+
413+
virtual void initialize(SILPassManager *PM) override;
414+
415+
/// Invalidate all information in this analysis.
416+
virtual void invalidate() override;
417+
418+
/// Invalidate all of the information for a specific function.
419+
virtual void invalidate(SILFunction *F, InvalidationKind K) override;
420+
421+
/// Notify the analysis about a newly created function.
422+
virtual void notifyAddedOrModifiedFunction(SILFunction *F) override {}
423+
424+
/// Notify the analysis about a function which will be deleted from the
425+
/// module.
426+
virtual void notifyWillDeleteFunction(SILFunction *F) override {
427+
invalidate(F, InvalidationKind::Nothing);
428+
}
429+
430+
/// Notify the analysis about changed witness or vtables.
431+
virtual void invalidateFunctionTables() override {}
432+
433+
private:
434+
/// Gets or creates FunctionAccessStorage for \p F.
435+
FunctionInfo *getFunctionInfo(SILFunction *F) {
436+
FunctionInfo *&functionInfo = functionInfoMap[F];
437+
if (!functionInfo) {
438+
functionInfo = new (allocator.Allocate()) FunctionInfo(F);
439+
}
440+
return functionInfo;
441+
}
442+
443+
/// Analyze the side-effects of a function, including called functions.
444+
/// Visited callees are added to \p BottomUpOrder until \p RecursionDepth
445+
/// reaches MaxRecursionDepth.
446+
void analyzeFunction(FunctionInfo *functionInfo, FunctionOrder &bottomUpOrder,
447+
int recursionDepth);
448+
449+
void analyzeCall(FunctionInfo *functionInfo, FullApplySite fullApply,
450+
FunctionOrder &bottomUpOrder, int recursionDepth);
451+
452+
/// Recomputes the side-effect information for the function \p Initial and
453+
/// all called functions, up to a recursion depth of MaxRecursionDepth.
454+
void recompute(FunctionInfo *initialInfo);
353455
};
354456

355457
} // end namespace swift

include/swift/SILOptimizer/Analysis/AliasAnalysis.h

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@
2020

2121
namespace swift {
2222

23-
class SideEffectAnalysis;
24-
class EscapeAnalysis;
25-
2623
/// This class is a simple wrapper around an alias analysis cache. This is
2724
/// needed since we do not have an "analysis" infrastructure.
2825
class AliasAnalysis {
@@ -74,8 +71,7 @@ class AliasAnalysis {
7471

7572
using TBAACacheKey = std::pair<SILType, SILType>;
7673

77-
SideEffectAnalysis *SEA;
78-
EscapeAnalysis *EA;
74+
SILPassManager *PM;
7975

8076
/// A cache for the computation of TBAA. True means that the types may
8177
/// alias. False means that the types must not alias.
@@ -125,8 +121,7 @@ class AliasAnalysis {
125121
bool isInImmutableScope(SILInstruction *inst, SILValue V);
126122

127123
public:
128-
AliasAnalysis(SideEffectAnalysis *SEA, EscapeAnalysis *EA)
129-
: SEA(SEA), EA(EA) {}
124+
AliasAnalysis(SILPassManager *PM) : PM(PM) {}
130125

131126
static SILAnalysisKind getAnalysisKind() { return SILAnalysisKind::Alias; }
132127

@@ -158,11 +153,6 @@ class AliasAnalysis {
158153
return alias(V1, V2, TBAAType1, TBAAType2) == AliasResult::MayAlias;
159154
}
160155

161-
/// \returns True if the release of the \p releasedReference can access or
162-
/// free memory accessed by \p User.
163-
bool mayValueReleaseInterfereWithInstruction(SILInstruction *User,
164-
SILValue releasedReference);
165-
166156
/// Use the alias analysis to determine the memory behavior of Inst with
167157
/// respect to V.
168158
MemoryBehavior computeMemoryBehavior(SILInstruction *Inst, SILValue V);
@@ -206,6 +196,19 @@ class AliasAnalysis {
206196

207197
/// Returns true if \p Ptr may be released by the builtin \p BI.
208198
bool canBuiltinDecrementRefCount(BuiltinInst *BI, SILValue Ptr);
199+
200+
/// Returns true if the address(es of) `addr` can escape to `toInst`.
201+
MemoryBehavior getMemoryBehaviorOfInst(SILValue addr, SILInstruction *toInst);
202+
203+
/// Returns true if the object(s of) `obj` can escape to `toInst`.
204+
bool isObjectReleasedByInst(SILValue obj, SILInstruction *toInst);
205+
206+
/// Is the `addr` within all reachable objects/addresses, when start walking
207+
/// from `obj`?
208+
bool isAddrVisibleFromObject(SILValue addr, SILValue obj);
209+
210+
/// Returns true if `lhs` can reference the same field as `rhs`.
211+
bool canReferenceSameField(SILValue lhs, SILValue rhs);
209212
};
210213

211214

include/swift/SILOptimizer/Analysis/Analysis.def

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ SIL_ANALYSIS(Destructor)
3434
SIL_ANALYSIS(DifferentiableActivity)
3535
SIL_ANALYSIS(Dominance)
3636
SIL_ANALYSIS(EpilogueARC)
37-
SIL_ANALYSIS(Escape)
3837
SIL_ANALYSIS(InductionVariable)
3938
SIL_ANALYSIS(Loop)
4039
SIL_ANALYSIS(LoopRegion)
@@ -44,7 +43,6 @@ SIL_ANALYSIS(PostDominance)
4443
SIL_ANALYSIS(PostOrder)
4544
SIL_ANALYSIS(ProtocolConformance)
4645
SIL_ANALYSIS(RCIdentity)
47-
SIL_ANALYSIS(SideEffect)
4846
SIL_ANALYSIS(TypeExpansion)
4947
SIL_ANALYSIS(PassManagerVerifier)
5048
SIL_ANALYSIS(DeadEndBlocks)

0 commit comments

Comments
 (0)