Skip to content

Commit 68052a3

Browse files
authored
Merge pull request swiftlang#33135 from gottesmm/pr-bcb3de84050b91b600ff133d7d6902b97e6065af
[opt-remark] Add support for simple object projections.
2 parents 7ee6319 + 0bd6a58 commit 68052a3

File tree

4 files changed

+409
-98
lines changed

4 files changed

+409
-98
lines changed

include/swift/SIL/Projection.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ class Projection {
247247

248248
Projection &operator=(Projection &&P) = default;
249249

250-
bool isValid() const { return Value.isValid(); }
250+
bool isValid() const { return bool(*this); }
251+
operator bool() const { return Value.isValid(); }
251252

252253
/// Convenience method for getting the underlying index. Assumes that this
253254
/// projection is valid. Otherwise it asserts.

lib/SILOptimizer/Transforms/OptRemarkGenerator.cpp

Lines changed: 111 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
1010
//
1111
//===----------------------------------------------------------------------===//
12+
///
13+
/// \file
14+
///
15+
/// In this pass, we define the opt-remark-generator, a simple SILVisitor that
16+
/// attempts to infer opt-remarks for the user using heuristics.
17+
///
18+
//===----------------------------------------------------------------------===//
1219

1320
#define DEBUG_TYPE "sil-opt-remark-gen"
1421

@@ -33,32 +40,95 @@ using namespace swift;
3340

3441
namespace {
3542

36-
/// Given a value, call \p funcPassedInferredArgs for each associated
37-
/// ValueDecl that is associated with \p value. All created Arguments are
38-
/// passed the same StringRef. To stop iteration, return false in \p
39-
/// funcPassedInferedArgs.
40-
///
41-
/// NOTE: the function may be called multiple times if the optimizer joined
42-
/// two live ranges and thus when iterating over value's users we see multiple
43-
/// debug_value operations.
4443
struct ValueToDeclInferrer {
4544
using Argument = OptRemark::Argument;
4645
using ArgumentKeyKind = OptRemark::ArgumentKeyKind;
4746

47+
RCIdentityFunctionInfo &rcfi;
48+
SmallVector<std::pair<SILType, Projection>, 32> accessPath;
49+
50+
ValueToDeclInferrer(RCIdentityFunctionInfo &rcfi) : rcfi(rcfi) {}
51+
52+
/// Given a value, attempt to infer a conservative list of decls that the
53+
/// passed in value could be referring to. This is done just using heuristics
4854
bool infer(ArgumentKeyKind keyKind, SILValue value,
49-
function_ref<bool(Argument)> funcPassedInferedArgs);
55+
SmallVectorImpl<Argument> &resultingInferredDecls);
56+
57+
/// Print out a note to \p stream that beings at decl and then consumes the
58+
/// accessPath we computed for decl producing a segmented access path, e.x.:
59+
/// "of 'x.lhs.ivar'".
60+
void printNote(llvm::raw_string_ostream &stream, const ValueDecl *decl);
5061
};
5162

5263
} // anonymous namespace
5364

54-
static void printNote(llvm::raw_string_ostream &stream, const ValueDecl *decl) {
55-
stream << "of '" << decl->getBaseName() << "'";
65+
void ValueToDeclInferrer::printNote(llvm::raw_string_ostream &stream,
66+
const ValueDecl *decl) {
67+
stream << "of '" << decl->getBaseName();
68+
for (auto &pair : accessPath) {
69+
auto baseType = pair.first;
70+
auto &proj = pair.second;
71+
stream << ".";
72+
73+
// WARNING: This must be kept insync with isSupportedProjection!
74+
switch (proj.getKind()) {
75+
case ProjectionKind::Upcast:
76+
stream << "upcast<" << proj.getCastType(baseType) << ">";
77+
continue;
78+
case ProjectionKind::RefCast:
79+
stream << "refcast<" << proj.getCastType(baseType) << ">";
80+
continue;
81+
case ProjectionKind::BitwiseCast:
82+
stream << "bitwise_cast<" << proj.getCastType(baseType) << ">";
83+
continue;
84+
case ProjectionKind::Struct:
85+
stream << proj.getVarDecl(baseType)->getBaseName();
86+
continue;
87+
case ProjectionKind::Tuple:
88+
stream << proj.getIndex();
89+
continue;
90+
case ProjectionKind::Enum:
91+
stream << proj.getEnumElementDecl(baseType)->getBaseName();
92+
continue;
93+
// Object -> Address projections can never be looked through.
94+
case ProjectionKind::Class:
95+
case ProjectionKind::Box:
96+
case ProjectionKind::Index:
97+
case ProjectionKind::TailElems:
98+
llvm_unreachable(
99+
"Object -> Address projection should never be looked through!");
100+
}
101+
102+
llvm_unreachable("Covered switch is not covered?!");
103+
}
104+
105+
accessPath.clear();
106+
stream << "'";
107+
}
108+
109+
// WARNING: This must be kept insync with ValueToDeclInferrer::printNote(...).
110+
static SingleValueInstruction *isSupportedProjection(Projection p, SILValue v) {
111+
switch (p.getKind()) {
112+
case ProjectionKind::Upcast:
113+
case ProjectionKind::RefCast:
114+
case ProjectionKind::BitwiseCast:
115+
case ProjectionKind::Struct:
116+
case ProjectionKind::Tuple:
117+
case ProjectionKind::Enum:
118+
return cast<SingleValueInstruction>(v);
119+
// Object -> Address projections can never be looked through.
120+
case ProjectionKind::Class:
121+
case ProjectionKind::Box:
122+
case ProjectionKind::Index:
123+
case ProjectionKind::TailElems:
124+
return nullptr;
125+
}
126+
llvm_unreachable("Covered switch is not covered?!");
56127
}
57128

58129
bool ValueToDeclInferrer::infer(
59130
ArgumentKeyKind keyKind, SILValue value,
60-
function_ref<bool(Argument)> funcPassedInferedArgs) {
61-
131+
SmallVectorImpl<Argument> &resultingInferredDecls) {
62132
// This is a linear IR traversal using a 'falling while loop'. That means
63133
// every time through the loop we are trying to handle a case before we hit
64134
// the bottom of the while loop where we always return true (since we did not
@@ -73,8 +143,9 @@ bool ValueToDeclInferrer::infer(
73143
llvm::raw_string_ostream stream(msg);
74144
printNote(stream, decl);
75145
}
76-
return funcPassedInferedArgs(
146+
resultingInferredDecls.push_back(
77147
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
148+
return true;
78149
}
79150

80151
if (auto *ga = dyn_cast<GlobalAddrInst>(value))
@@ -84,13 +155,14 @@ bool ValueToDeclInferrer::infer(
84155
llvm::raw_string_ostream stream(msg);
85156
printNote(stream, decl);
86157
}
87-
if (!funcPassedInferedArgs(
88-
Argument({keyKind, "InferredValue"}, std::move(msg), decl)))
89-
return false;
158+
resultingInferredDecls.push_back(
159+
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
160+
return true;
90161
}
91162

92163
// Then visit our users and see if we can find a debug_value that provides
93164
// us with a decl we can use to construct an argument.
165+
bool foundDeclFromUse = false;
94166
for (auto *use : value->getUses()) {
95167
// Skip type dependent uses.
96168
if (use->isTypeDependent())
@@ -103,12 +175,14 @@ bool ValueToDeclInferrer::infer(
103175
llvm::raw_string_ostream stream(msg);
104176
printNote(stream, decl);
105177
}
106-
if (!funcPassedInferedArgs(
107-
Argument({keyKind, "InferredValue"}, std::move(msg), decl)))
108-
return false;
178+
resultingInferredDecls.push_back(
179+
Argument({keyKind, "InferredValue"}, std::move(msg), decl));
180+
foundDeclFromUse = true;
109181
}
110182
}
111183
}
184+
if (foundDeclFromUse)
185+
return true;
112186

113187
// At this point, we could not infer any argument. See if we can look
114188
// through loads.
@@ -121,6 +195,14 @@ bool ValueToDeclInferrer::infer(
121195
continue;
122196
}
123197

198+
if (auto proj = Projection(value)) {
199+
if (auto *projInst = isSupportedProjection(proj, value)) {
200+
value = projInst->getOperand(0);
201+
accessPath.emplace_back(value->getType(), proj);
202+
continue;
203+
}
204+
}
205+
124206
// If we reached this point, we finished falling through the loop and return
125207
// true.
126208
return true;
@@ -136,7 +218,6 @@ namespace {
136218
struct OptRemarkGeneratorInstructionVisitor
137219
: public SILInstructionVisitor<OptRemarkGeneratorInstructionVisitor> {
138220
SILModule &mod;
139-
RCIdentityFunctionInfo &rcfi;
140221
OptRemark::Emitter ORE;
141222

142223
/// A class that we use to infer the decl that is associated with a
@@ -145,7 +226,7 @@ struct OptRemarkGeneratorInstructionVisitor
145226

146227
OptRemarkGeneratorInstructionVisitor(SILFunction &fn,
147228
RCIdentityFunctionInfo &rcfi)
148-
: mod(fn.getModule()), rcfi(rcfi), ORE(DEBUG_TYPE, fn) {}
229+
: mod(fn.getModule()), ORE(DEBUG_TYPE, fn), valueToDeclInferrer(rcfi) {}
149230

150231
void visitStrongRetainInst(StrongRetainInst *sri);
151232
void visitStrongReleaseInst(StrongReleaseInst *sri);
@@ -160,13 +241,9 @@ void OptRemarkGeneratorInstructionVisitor::visitStrongRetainInst(
160241
StrongRetainInst *sri) {
161242
ORE.emit([&]() {
162243
using namespace OptRemark;
163-
SILValue root = rcfi.getRCIdentityRoot(sri->getOperand());
164244
SmallVector<Argument, 8> inferredArgs;
165-
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
166-
[&](Argument arg) {
167-
inferredArgs.push_back(arg);
168-
return true;
169-
});
245+
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
246+
sri->getOperand(), inferredArgs);
170247
(void)foundArgs;
171248

172249
// Retains begin a lifetime scope so we infer scan forward.
@@ -185,13 +262,9 @@ void OptRemarkGeneratorInstructionVisitor::visitStrongReleaseInst(
185262
ORE.emit([&]() {
186263
using namespace OptRemark;
187264
// Releases end a lifetime scope so we infer scan backward.
188-
SILValue root = rcfi.getRCIdentityRoot(sri->getOperand());
189265
SmallVector<Argument, 8> inferredArgs;
190-
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
191-
[&](Argument arg) {
192-
inferredArgs.push_back(arg);
193-
return true;
194-
});
266+
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
267+
sri->getOperand(), inferredArgs);
195268
(void)foundArgs;
196269

197270
auto remark = RemarkMissed("memory", *sri,
@@ -208,15 +281,10 @@ void OptRemarkGeneratorInstructionVisitor::visitRetainValueInst(
208281
RetainValueInst *rvi) {
209282
ORE.emit([&]() {
210283
using namespace OptRemark;
211-
SILValue root = rcfi.getRCIdentityRoot(rvi->getOperand());
212284
SmallVector<Argument, 8> inferredArgs;
213-
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
214-
[&](Argument arg) {
215-
inferredArgs.push_back(arg);
216-
return true;
217-
});
285+
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
286+
rvi->getOperand(), inferredArgs);
218287
(void)foundArgs;
219-
220288
// Retains begin a lifetime scope, so we infer scan forwards.
221289
auto remark = RemarkMissed("memory", *rvi,
222290
SourceLocInferenceBehavior::ForwardScanOnly)
@@ -232,13 +300,9 @@ void OptRemarkGeneratorInstructionVisitor::visitReleaseValueInst(
232300
ReleaseValueInst *rvi) {
233301
ORE.emit([&]() {
234302
using namespace OptRemark;
235-
SILValue root = rcfi.getRCIdentityRoot(rvi->getOperand());
236303
SmallVector<Argument, 8> inferredArgs;
237-
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note, root,
238-
[&](Argument arg) {
239-
inferredArgs.push_back(arg);
240-
return true;
241-
});
304+
bool foundArgs = valueToDeclInferrer.infer(ArgumentKeyKind::Note,
305+
rvi->getOperand(), inferredArgs);
242306
(void)foundArgs;
243307

244308
// Releases end a lifetime scope so we infer scan backward.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// RUN: %target-swiftc_driver -O -Rpass-missed=sil-opt-remark-gen -Xllvm -sil-disable-pass=FunctionSignatureOpts -emit-sil %s -o /dev/null -Xfrontend -verify
2+
3+
// RUN: %empty-directory(%t)
4+
// RUN: %target-swiftc_driver -wmo -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -emit-sil -save-optimization-record=yaml -save-optimization-record-path %t/note.yaml %s -o /dev/null && %FileCheck --input-file=%t/note.yaml %s
5+
6+
// This file is testing out the basic YAML functionality to make sure that it
7+
// works without burdening opt-remark-generator-yaml.swift with having to update all
8+
// of the yaml test cases everytime new code is added.
9+
10+
// CHECK: --- !Missed
11+
// CHECK-NEXT: Pass: sil-opt-remark-gen
12+
// CHECK-NEXT: Name: sil.memory
13+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}opt-remark-generator-yaml.swift',
14+
// CHECK-NEXT: Line: 63, Column: 5 }
15+
// CHECK-NEXT: Function: 'getGlobal()'
16+
// CHECK-NEXT: Args:
17+
// CHECK-NEXT: - String: retain
18+
// CHECK-NEXT: - InferredValue: 'of ''global'''
19+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}opt-remark-generator-yaml.swift',
20+
// CHECK-NEXT: Line: 59, Column: 12 }
21+
// CHECK-NEXT: ...
22+
// CHECK-NEXT: --- !Missed
23+
// CHECK-NEXT: Pass: sil-opt-remark-gen
24+
// CHECK-NEXT: Name: sil.memory
25+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}opt-remark-generator-yaml.swift',
26+
// CHECK-NEXT: Line: 71, Column: 5 }
27+
// CHECK-NEXT: Function: 'useGlobal()'
28+
// CHECK-NEXT: Args:
29+
// CHECK-NEXT: - String: retain
30+
// CHECK-NEXT: - InferredValue: 'of ''x'''
31+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}opt-remark-generator-yaml.swift',
32+
// CHECK-NEXT: Line: 68, Column: 9 }
33+
// CHECK-NEXT: ...
34+
// CHECK-NEXT: --- !Missed
35+
// CHECK-NEXT: Pass: sil-opt-remark-gen
36+
// CHECK-NEXT: Name: sil.memory
37+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}opt-remark-generator-yaml.swift',
38+
// CHECK-NEXT: Line: 71, Column: 12 }
39+
// CHECK-NEXT: Function: 'useGlobal()'
40+
// CHECK-NEXT: Args:
41+
// CHECK-NEXT: - String: release
42+
43+
// CHECK-NEXT: ...
44+
// CHECK-NEXT: --- !Missed
45+
// CHECK-NEXT: Pass: sil-opt-remark-gen
46+
// CHECK-NEXT: Name: sil.memory
47+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}opt-remark-generator-yaml.swift',
48+
// CHECK-NEXT: Line: 71, Column: 12 }
49+
// CHECK-NEXT: Function: 'useGlobal()'
50+
// CHECK-NEXT: Args:
51+
// CHECK-NEXT: - String: release
52+
// CHECK-NEXT: - InferredValue: 'of ''x'''
53+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}opt-remark-generator-yaml.swift',
54+
// CHECK-NEXT: Line: 68, Column: 9 }
55+
// CHECK-NEXT: ...
56+
57+
public class Klass {}
58+
59+
public var global = Klass()
60+
61+
@inline(never)
62+
public func getGlobal() -> Klass {
63+
return global // expected-remark @:5 {{retain}}
64+
// expected-note @-5:12 {{of 'global'}}
65+
}
66+
67+
public func useGlobal() {
68+
let x = getGlobal()
69+
// Make sure that the retain msg is at the beginning of the print and the
70+
// releases are the end of the print.
71+
print(x) // expected-remark @:5 {{retain}}
72+
// expected-note @-4:9 {{of 'x'}}
73+
// expected-remark @-2:12 {{release}}
74+
// expected-remark @-3:12 {{release}}
75+
// expected-note @-7:9 {{of 'x'}}
76+
}

0 commit comments

Comments
 (0)