Skip to content

Commit b66fa09

Browse files
committed
Add AccessedStorageAnalysis.
An interprocedural analysis pass that summarizes the dynamically enforced formal accesses within a function. These summaries will be used by a new AccessEnforcementOpts pass to locally fold access scopes and remove dynamic checks based on whole module analysis.
1 parent 8a003a4 commit b66fa09

16 files changed

+1103
-184
lines changed

include/swift/SIL/MemAccessUtils.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ namespace swift {
2424

2525
// stripAddressAccess() is declared in InstructionUtils.h.
2626

27+
inline bool accessKindMayConflict(SILAccessKind a, SILAccessKind b) {
28+
return !(a == SILAccessKind::Read && b == SILAccessKind::Read);
29+
}
30+
2731
/// Represents the identity of a stored class property as a combination
2832
/// of a base and a single projection. Eventually the goal is to make this
2933
/// more precise and consider, casts, etc.
@@ -88,6 +92,7 @@ class AccessedStorage {
8892
enum Kind {
8993
Box, Stack, Global, Class, Argument, Nested, Unidentified
9094
};
95+
static const char *getKindName(Kind k);
9196

9297
/// If the given address source is an identified access base, return the kind
9398
/// of access base. Otherwise, return Unidentified.
@@ -108,6 +113,9 @@ class AccessedStorage {
108113

109114
AccessedStorage(SILValue base, Kind kind);
110115

116+
AccessedStorage(SILValue object, Projection projection)
117+
: kind(Class), objProj(object, projection) {}
118+
111119
// Return true if this is a valid storage location.
112120
operator bool() const {
113121
return kind != Unidentified || value;
@@ -186,6 +194,9 @@ class AccessedStorage {
186194
/// determined. Otherwise returns null. For diagnostics and checking via the
187195
/// ValueDecl if we are processing a `let` variable.
188196
const ValueDecl *getDecl(SILFunction *F) const;
197+
198+
void print(raw_ostream &os) const;
199+
void dump() const;
189200
};
190201
} // end namespace swift
191202

@@ -283,6 +294,10 @@ AccessedStorage findAccessedStorageOrigin(SILValue sourceAddr);
283294
/// a begin_access marker. To determine whether to emit begin_access:
284295
/// storage = findAccessedStorage(address)
285296
/// needsAccessMarker = storage && isPossibleFormalAccessBase(storage)
297+
///
298+
/// Warning: This is only valid for SIL with well-formed accessed. For example,
299+
/// it will not handle address-type phis. Optimization passes after
300+
/// DiagnoseStaticExclusivity may violate these assumptions.
286301
bool isPossibleFormalAccessBase(const AccessedStorage &storage, SILFunction *F);
287302

288303
/// Visit each address accessed by the given memory operation.

include/swift/SIL/Projection.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ class Projection {
474474
createAggFromFirstLevelProjections(SILBuilder &B, SILLocation Loc,
475475
SILType BaseType,
476476
llvm::SmallVectorImpl<SILValue> &Values);
477+
478+
void print(raw_ostream &os, SILType baseType) const;
477479
private:
478480
/// Convenience method for getting the raw underlying index as a pointer.
479481
TypeBase *getPointer() const {
@@ -685,8 +687,8 @@ class ProjectionPath {
685687

686688
void verify(SILModule &M);
687689

688-
raw_ostream &print(raw_ostream &OS, SILModule &M);
689-
void dump(SILModule &M);
690+
raw_ostream &print(raw_ostream &OS, SILModule &M) const;
691+
void dump(SILModule &M) const;
690692
};
691693

692694
/// Returns the hashcode for the new projection path.
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
//===--- AccessedStorageAnalysis.h - Accessed Storage Analysis --*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// This file implements an interprocedural analysis pass that summarizes the
14+
// dynamically enforced formal accesses within a function. These summaries are
15+
// used by AccessEnforcementOpts to locally fold access scopes and remove
16+
// dynamic checks based on whole module analysis.
17+
//
18+
// Note: This can be easily augmented to simultaneously compute
19+
// FunctionSideEffects by adding FunctionSideEffects as a member of
20+
// FunctionAccessedStorage. However, currently, the only use of
21+
// FunctionAccessedStorage is local to summarizeFunction().
22+
//
23+
//===----------------------------------------------------------------------===//
24+
#ifndef SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H_
25+
#define SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H_
26+
27+
#include "swift/SIL/MemAccessUtils.h"
28+
#include "swift/SIL/SILFunction.h"
29+
#include "swift/SIL/SILInstruction.h"
30+
#include "swift/SILOptimizer/Analysis/SideEffectAnalysis.h"
31+
32+
namespace swift {
33+
34+
/// Information about a formal access within a function pertaining to a
35+
/// particular AccessedStorage location.
36+
struct StorageAccessInfo {
37+
SILAccessKind accessKind;
38+
bool noNestedConflict = false;
39+
40+
StorageAccessInfo() {}
41+
42+
template <typename B>
43+
explicit StorageAccessInfo(B *beginAccess)
44+
: accessKind(beginAccess->getAccessKind()),
45+
noNestedConflict(beginAccess->hasNoNestedConflict()) {}
46+
47+
bool mergeFrom(const StorageAccessInfo &RHS);
48+
};
49+
50+
/// The per-function result of AccessedStorageAnalysis.
51+
///
52+
/// Maps each unique AccessedStorage location to StorageAccessInfo.
53+
///
54+
/// Any unidentified accesses are summarized as a single unidentifiedAccess
55+
/// property.
56+
class FunctionAccessedStorage {
57+
using AccessedStorageMap =
58+
llvm::SmallDenseMap<AccessedStorage, StorageAccessInfo, 8>;
59+
60+
AccessedStorageMap storageAccessMap;
61+
Optional<SILAccessKind> unidentifiedAccess;
62+
63+
public:
64+
FunctionAccessedStorage() {}
65+
66+
void clear() {
67+
storageAccessMap.clear();
68+
unidentifiedAccess = None;
69+
}
70+
71+
/// Sets the most conservative effects, if we don't know anything about the
72+
/// function.
73+
void setWorstEffects() {
74+
storageAccessMap.clear();
75+
unidentifiedAccess = SILAccessKind::Modify;
76+
}
77+
78+
/// Summarize the given function's effects using this FunctionAccessedStorage
79+
/// object.
80+
//
81+
// Return true if the function's' effects have been fully summarized without
82+
// visiting it's body.
83+
bool summarizeFunction(SILFunction *F);
84+
85+
/// Summarize the callee side effects of a call instruction using this
86+
/// FunctionAccessedStorage object without analyzing the callee function
87+
/// bodies or scheduling the callees for bottom-up propagation.
88+
///
89+
/// The side effects are represented from the callee's perspective. Parameter
90+
/// effects are not translated into information on the caller's argument, and
91+
/// local effects are not dropped.
92+
///
93+
/// Return true if this call-site's effects are summarized without visiting
94+
/// the callee.
95+
///
96+
/// TODO: Summarize ArraySemanticsCall accesses.
97+
bool summarizeCall(FullApplySite fullApply) {
98+
assert(storageAccessMap.empty() && "expected uninitialized results.");
99+
return false;
100+
}
101+
102+
/// Merge effects directly from \p RHS.
103+
bool mergeFrom(const FunctionAccessedStorage &RHS);
104+
105+
/// Merge the effects represented in calleeAccess into this
106+
/// FunctionAccessedStorage object. calleeAccess must correspond to at least
107+
/// one callee at the apply site `fullApply`. Merging drops any local effects,
108+
/// and translates parameter effects into effects on the caller-side
109+
/// arguments.
110+
///
111+
/// The full caller-side effects at a call site can be obtained with
112+
/// AccessedStorageAnalysis::getCallSiteEffects().
113+
bool mergeFromApply(const FunctionAccessedStorage &calleeAccess,
114+
FullApplySite fullApply);
115+
116+
/// Analyze the side-effects of a single SIL instruction \p I.
117+
/// Visited callees are added to \p BottomUpOrder until \p RecursionDepth
118+
/// reaches MaxRecursionDepth.
119+
void analyzeInstruction(SILInstruction *I);
120+
121+
/// Does any of the accesses represented by this FunctionAccessedStorage
122+
/// object conflict with the given access kind and storage.
123+
bool mayConflictWith(SILAccessKind otherAccessKind,
124+
const AccessedStorage &otherStorage);
125+
126+
void print(raw_ostream &os) const;
127+
void dump() const;
128+
129+
protected:
130+
bool updateUnidentifiedAccess(SILAccessKind accessKind);
131+
132+
bool mergeAccesses(
133+
const FunctionAccessedStorage &RHS,
134+
std::function<AccessedStorage(const AccessedStorage &)> transformStorage);
135+
136+
template <typename B> void visitBeginAccess(B *beginAccess);
137+
};
138+
139+
/// Summarizes the dynamic accesses performed within a function and its
140+
/// callees.
141+
///
142+
/// When summarizing multiple accesses, a Read access indicates that all
143+
/// accesses are read only. A Write access indicates potential reads and writes.
144+
///
145+
/// Unidentified accesses are not recorded individually. Rather, the function is
146+
/// marked as potentially executing unidentified reads or writes. An incomplete
147+
/// function, without a known callee set, is considered to have unidentified
148+
/// writes.
149+
class AccessedStorageAnalysis
150+
: public GenericFunctionEffectAnalysis<FunctionAccessedStorage> {
151+
public:
152+
AccessedStorageAnalysis()
153+
: GenericFunctionEffectAnalysis<FunctionAccessedStorage>(
154+
AnalysisKind::AccessedStorage) {}
155+
156+
static bool classof(const SILAnalysis *S) {
157+
return S->getKind() == AnalysisKind::AccessedStorage;
158+
}
159+
};
160+
161+
} // end namespace swift
162+
163+
#endif // SWIFT_SILOPTIMIZER_ANALYSIS_ACCESSED_STORAGE_ANALYSIS_H_

include/swift/SILOptimizer/Analysis/Analysis.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#endif
2525

2626
ANALYSIS(AccessSummary)
27+
ANALYSIS(AccessedStorage)
2728
ANALYSIS(Alias)
2829
ANALYSIS(BasicCallee)
2930
ANALYSIS(Caller)

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ PASS(AccessEnforcementSelection, "access-enforcement-selection",
5858
"Access Enforcement Selection")
5959
PASS(AccessSummaryDumper, "access-summary-dump",
6060
"Dump Address Parameter Access Summary")
61+
PASS(AccessedStorageDumper, "accessed-storage-dump",
62+
"Dump Accessed Storage Summary")
6163
PASS(AccessMarkerElimination, "access-marker-elim",
6264
"Access Marker Elimination.")
6365
PASS(AddressLowering, "address-lowering",

lib/SIL/MemAccessUtils.cpp

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,53 @@ const ValueDecl *AccessedStorage::getDecl(SILFunction *F) const {
104104
}
105105
}
106106

107-
// An address base is a block argument. Verify that it is actually a box
108-
// projected from a switch_enum.
107+
const char *AccessedStorage::getKindName(AccessedStorage::Kind k) {
108+
switch (k) {
109+
case Box:
110+
return "Box";
111+
case Stack:
112+
return "Stack";
113+
case Nested:
114+
return "Nested";
115+
case Unidentified:
116+
return "Unidentified";
117+
case Argument:
118+
return "Argument";
119+
case Global:
120+
return "Global";
121+
case Class:
122+
return "Class";
123+
}
124+
}
125+
126+
void AccessedStorage::print(raw_ostream &os) const {
127+
os << getKindName(kind) << " ";
128+
switch (kind) {
129+
case Box:
130+
case Stack:
131+
case Nested:
132+
case Unidentified:
133+
os << value;
134+
break;
135+
case Argument:
136+
os << "index: " << paramIndex << "\n";
137+
break;
138+
case Global:
139+
os << *global;
140+
break;
141+
case Class:
142+
os << objProj.getObject() << " ";
143+
objProj.getProjection().print(os, objProj.getObject()->getType());
144+
os << "\n";
145+
}
146+
}
147+
148+
void AccessedStorage::dump() const { print(llvm::dbgs()); }
149+
150+
// Given an address base is a block argument, verify that it is actually a box
151+
// projected from a switch_enum. This is a valid pattern at any SIL stage
152+
// resulting in a block-type phi. In later SIL stages, the optimizer may form
153+
// address-type phis, causing this assert if called on those cases.
109154
static void checkSwitchEnumBlockArg(SILPHIArgument *arg) {
110155
assert(!arg->getType().isAddress());
111156
SILBasicBlock *Pred = arg->getParent()->getSinglePredecessorBlock();
@@ -135,6 +180,9 @@ AccessedStorage swift::findAccessedStorage(SILValue sourceAddr) {
135180
// A block argument may be a box value projected out of
136181
// switch_enum. Address-type block arguments are not allowed.
137182
case ValueKind::SILPHIArgument:
183+
if (address->getType().isAddress())
184+
return AccessedStorage();
185+
138186
checkSwitchEnumBlockArg(cast<SILPHIArgument>(address));
139187
return AccessedStorage(address, AccessedStorage::Unidentified);
140188

@@ -279,7 +327,7 @@ bool swift::isPossibleFormalAccessBase(const AccessedStorage &storage,
279327
if (isScratchBuffer(storage.getValue()))
280328
return false;
281329
}
282-
// Additional checks that apply to anything that may fal through.
330+
// Additional checks that apply to anything that may fall through.
283331

284332
// Immutable values are only accessed for initialization.
285333
if (isLetAccess(storage, F))

0 commit comments

Comments
 (0)