Skip to content

Commit 96097b0

Browse files
committed
[opt-remark] Add @_semantics("optremark{.$SIL_PASS_NAME}") that force opt remarks on functions.
More specifically, if one wants to force emit /all/ opt-remarks on a function, mark it with: ``` @_semantics("optremark") ``` If one wants to emit opt-remarks only for a specific SIL pass (like lets say sil-opt-remark-gen), one can write: ``` @_semantics("optremark.sil-opt-remark-gen") ``` I made the pattern matching strict so if you just put in a '.' or add additional suffixes, it will not pattern match. I think that this is sufficient for a prototyping tool. This is useful if one wants to play around with opt-remarks when optimizing code in Xcode or any IDE that can use serialized diagnostics.
1 parent 2b467ad commit 96097b0

File tree

7 files changed

+158
-19
lines changed

7 files changed

+158
-19
lines changed

include/swift/AST/SemanticAttrs.def

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,16 @@ SEMANTICS_ATTR(CONVERT_TO_OBJECTIVE_C, "convertToObjectiveC")
9393

9494
SEMANTICS_ATTR(KEYPATH_KVC_KEY_PATH_STRING, "keypath.kvcKeyPathString")
9595

96+
/// The prefix used to force opt-remarks to be emitted in a specific function.
97+
///
98+
/// If used just by itself "optremark", it is assumed that /all/ opt remarks
99+
/// should be emitted. Otherwise, one can add a suffix after a '.' that
100+
/// specifies a pass to emit opt-remarks from. So for instance to get just
101+
/// information from 'sil-opt-remark-gen', one would write:
102+
/// "optremark.sil-opt-remark-gen". One can add as many as one wishes. Keep in
103+
/// mind that if the function itself is inlined, one will lose the optremark so
104+
/// consider inlining where to put these.
105+
SEMANTICS_ATTR(FORCE_EMIT_OPT_REMARK_PREFIX, "optremark")
106+
96107
#undef SEMANTICS_ATTR
97108

include/swift/SIL/OptimizationRemark.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#ifndef SWIFT_SIL_OPTIMIZATIONREMARKEMITTER_H
2020
#define SWIFT_SIL_OPTIMIZATIONREMARKEMITTER_H
2121

22+
#include "swift/AST/SemanticAttrs.h"
2223
#include "swift/Basic/SourceLoc.h"
2324
#include "swift/Demangling/Demangler.h"
2425
#include "swift/SIL/SILArgument.h"

lib/SIL/Utils/OptimizationRemark.cpp

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
#include "swift/SIL/OptimizationRemark.h"
2020
#include "swift/AST/DiagnosticEngine.h"
2121
#include "swift/AST/DiagnosticsSIL.h"
22+
#include "swift/AST/SemanticAttrs.h"
2223
#include "swift/Demangling/Demangler.h"
2324
#include "swift/SIL/DebugUtils.h"
2425
#include "swift/SIL/InstructionUtils.h"
2526
#include "swift/SIL/MemAccessUtils.h"
27+
#include "swift/SIL/Projection.h"
2628
#include "swift/SIL/SILArgument.h"
2729
#include "swift/SIL/SILInstruction.h"
2830
#include "swift/SIL/SILRemarkStreamer.h"
@@ -108,16 +110,39 @@ std::string Remark<DerivedT>::getDebugMsg() const {
108110
return stream.str();
109111
}
110112

113+
static bool hasForceEmitSemanticAttr(SILFunction &fn, StringRef passName) {
114+
return llvm::any_of(fn.getSemanticsAttrs(), [&](const std::string &str) {
115+
auto ref = StringRef(str);
116+
117+
// First try to chomp the prefix.
118+
if (!ref.consume_front(semantics::FORCE_EMIT_OPT_REMARK_PREFIX))
119+
return false;
120+
121+
// Then see if we only have the prefix. Then always return true the user
122+
// wants /all/ remarks.
123+
if (ref.empty())
124+
return true;
125+
126+
// Otherwise, lets try to chomp the '.' and then the passName.
127+
if (!ref.consume_front(".") || !ref.consume_front(passName))
128+
return false;
129+
130+
return ref.empty();
131+
});
132+
}
133+
111134
Emitter::Emitter(StringRef passName, SILFunction &fn)
112135
: fn(fn), passName(passName),
113136
passedEnabled(
114-
fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern &&
115-
fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern->match(
116-
passName)),
137+
hasForceEmitSemanticAttr(fn, passName) ||
138+
(fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern &&
139+
fn.getASTContext().LangOpts.OptimizationRemarkPassedPattern->match(
140+
passName))),
117141
missedEnabled(
118-
fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern &&
119-
fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern->match(
120-
passName)) {}
142+
hasForceEmitSemanticAttr(fn, passName) ||
143+
(fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern &&
144+
fn.getASTContext().LangOpts.OptimizationRemarkMissedPattern->match(
145+
passName))) {}
121146

122147
/// The user has passed us an instruction that for some reason has a source loc
123148
/// that can not be used. Search down the current block for an instruction with
@@ -216,7 +241,8 @@ static void emitRemark(SILFunction &fn, const Remark<RemarkT> &remark,
216241
// If diagnostics are enabled, first emit the main diagnostic and then loop
217242
// through our arguments and allow the arguments to add additional diagnostics
218243
// if they want.
219-
if (!diagEnabled)
244+
if (!diagEnabled && !fn.hasSemanticsAttrThatStartsWith(
245+
semantics::FORCE_EMIT_OPT_REMARK_PREFIX))
220246
return;
221247

222248
auto &de = module.getASTContext().Diags;

lib/SILOptimizer/Transforms/OptRemarkGenerator.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212

1313
#define DEBUG_TYPE "sil-opt-remark-gen"
1414

15+
#include "swift/AST/SemanticAttrs.h"
1516
#include "swift/SIL/MemAccessUtils.h"
1617
#include "swift/SIL/OptimizationRemark.h"
18+
#include "swift/SIL/Projection.h"
1719
#include "swift/SIL/SILFunction.h"
1820
#include "swift/SIL/SILInstruction.h"
1921
#include "swift/SIL/SILModule.h"
@@ -266,7 +268,9 @@ class OptRemarkGenerator : public SILFunctionTransform {
266268

267269
return bool(langOpts.OptimizationRemarkMissedPattern) ||
268270
bool(langOpts.OptimizationRemarkPassedPattern) ||
269-
fn->getModule().getSILRemarkStreamer();
271+
fn->getModule().getSILRemarkStreamer() ||
272+
fn->hasSemanticsAttrThatStartsWith(
273+
semantics::FORCE_EMIT_OPT_REMARK_PREFIX);
270274
}
271275

272276
/// The entry point to the transformation.

lib/SILOptimizer/Transforms/PerformanceInliner.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,13 @@ bool SILPerformanceInliner::isProfitableToInline(
298298
// It is always OK to inline a simple call.
299299
// TODO: May be consider also the size of the callee?
300300
if (isPureCall(AI, SEA)) {
301+
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
302+
using namespace OptRemark;
303+
return RemarkPassed("Inline", *AI.getInstruction())
304+
<< "Pure call. Always profitable to inline "
305+
<< NV("Callee", Callee);
306+
});
307+
301308
LLVM_DEBUG(dumpCaller(AI.getFunction());
302309
llvm::dbgs() << " pure-call decision " << Callee->getName()
303310
<< '\n');
@@ -487,9 +494,24 @@ bool SILPerformanceInliner::isProfitableToInline(
487494
auto *bb = AI.getInstruction()->getParent();
488495
auto bbIt = BBToWeightMap.find(bb);
489496
if (bbIt != BBToWeightMap.end()) {
490-
return profileBasedDecision(AI, Benefit, Callee, CalleeCost,
491-
NumCallerBlocks, bbIt);
497+
if (profileBasedDecision(AI, Benefit, Callee, CalleeCost, NumCallerBlocks,
498+
bbIt)) {
499+
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
500+
using namespace OptRemark;
501+
return RemarkPassed("Inline", *AI.getInstruction())
502+
<< "Profitable due to provided profile";
503+
});
504+
return true;
505+
}
506+
507+
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
508+
using namespace OptRemark;
509+
return RemarkMissed("Inline", *AI.getInstruction())
510+
<< "Not profitable due to provided profile";
511+
});
512+
return false;
492513
}
514+
493515
if (isClassMethodAtOsize && Benefit > OSizeClassMethodBenefit) {
494516
Benefit = OSizeClassMethodBenefit;
495517
}
@@ -498,7 +520,7 @@ bool SILPerformanceInliner::isProfitableToInline(
498520
if (CalleeCost > Benefit) {
499521
OptRemark::Emitter::emitOrDebug(DEBUG_TYPE, &ORE, [&]() {
500522
using namespace OptRemark;
501-
return RemarkMissed("NoInlinedCost", *AI.getInstruction())
523+
return RemarkMissed("Inline", *AI.getInstruction())
502524
<< "Not profitable to inline function " << NV("Callee", Callee)
503525
<< " (cost = " << NV("Cost", CalleeCost)
504526
<< ", benefit = " << NV("Benefit", Benefit) << ")";

test/SILOptimizer/inliner_coldblocks.sil

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ bb0:
6565

6666
bb1:
6767
%f = function_ref @callee : $@convention(thin) () -> ()
68+
// REMARKS_PASSED: {{.*}}inliner_coldblocks.sil:[[# @LINE + 1]]:8: remark: Pure call. Always profitable to inline "callee"
6869
%a = apply %f() : $@convention(thin) () -> ()
6970
br bb2
7071

@@ -193,13 +194,13 @@ bb0:
193194
%c30 = builtin "assert_configuration"() : $Builtin.Int32
194195

195196
%f = function_ref @update_global: $@convention(thin) () -> ()
196-
// REMARKS_PASSED: inliner_coldblocks.sil:223:3: remark: "update_global" inlined into "regular_large_callee" (cost = {{.*}}, benefit = {{.*}})
197+
// REMARKS_PASSED: inliner_coldblocks.sil:[[# @LINE + 27]]:3: remark: "update_global" inlined into "regular_large_callee" (cost = {{.*}}, benefit = {{.*}})
197198
// YAML: --- !Passed
198199
// YAML-NEXT: Pass: sil-inliner
199-
// YAML-NEXT: Name: sil.Inlined
200+
// YAML-NEXT: Name: sil.Inline
200201
// YAML-NEXT: DebugLoc:
201202
// YAML: File: {{.*}}inliner_coldblocks.sil
202-
// YAML: Line: 223
203+
// YAML: Line: [[#@LINE + 21]]
203204
// YAML: Column: 3
204205
// YAML-NEXT: Function: regular_large_callee
205206
// YAML-NEXT: Args:
@@ -212,7 +213,7 @@ bb0:
212213
// YAML-NEXT: - Caller: '"regular_large_callee"'
213214
// YAML-NEXT: DebugLoc:
214215
// YAML: File: {{.*}}inliner_coldblocks.sil
215-
// YAML: Line: 162
216+
// YAML: Line: [[# @LINE - 53]]
216217
// YAML: Column: 6
217218
// YAML-NEXT: - String: ' (cost = '
218219
// YAML-NEXT: - Cost: '{{.*}}'
@@ -233,21 +234,21 @@ bb0:
233234
sil @dont_inline_regular_large_callee : $@convention(thin) () -> () {
234235
bb0:
235236
%f = function_ref @regular_large_callee : $@convention(thin) () -> ()
236-
// REMARKS_MISSED: inliner_coldblocks.sil:258:8: remark: Not profitable to inline function "regular_large_callee" (cost = {{.*}}, benefit = {{.*}})
237+
// REMARKS_MISSED: inliner_coldblocks.sil:[[# @LINE + 22]]:8: remark: Not profitable to inline function "regular_large_callee" (cost = {{.*}}, benefit = {{.*}})
237238
// YAML: --- !Missed
238239
// YAML-NEXT: Pass: sil-inliner
239-
// YAML-NEXT: Name: sil.NoInlinedCost
240+
// YAML-NEXT: Name: sil.Inline
240241
// YAML-NEXT: DebugLoc:
241242
// YAML: File: {{.*}}inliner_coldblocks.sil
242-
// YAML: Line: 258
243+
// YAML: Line: [[# @LINE + 16]]
243244
// YAML: Column: 8
244245
// YAML-NEXT: Function: dont_inline_regular_large_callee
245246
// YAML-NEXT: Args:
246247
// YAML-NEXT: - String: 'Not profitable to inline function '
247248
// YAML-NEXT: - Callee: '"regular_large_callee"'
248249
// YAML-NEXT: DebugLoc:
249250
// YAML: File: {{.*}}inliner_coldblocks.sil
250-
// YAML: Line: 162
251+
// YAML: Line: [[# @LINE - 88]]
251252
// YAML: Column: 6
252253
// YAML-NEXT: - String: ' (cost = '
253254
// YAML-NEXT: - Cost: '{{.*}}'
@@ -304,6 +305,7 @@ bb0:
304305
sil @inline_large_callee_on_fast_path : $@convention(thin) () -> () {
305306
bb0:
306307
%f = function_ref @large_callee_on_fast_path : $@convention(thin) () -> ()
308+
// REMARKS_PASSED: inliner_coldblocks.sil:[[# @LINE + 1]]:8: remark: Pure call. Always profitable to inline "large_callee_on_fast_path"
307309
%a = apply %f() : $@convention(thin) () -> ()
308310
%r = tuple ()
309311
return %r : $()
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// RUN: %target-swift-frontend -emit-sil %s -verify -Osize
2+
//
3+
// NOTE: We only emit opt-remarks with -Osize,-O today! -O does drop way more
4+
// stuff though, so we test with -Osize.
5+
6+
public class Klass {}
7+
8+
public var mySingleton = Klass()
9+
10+
@_semantics("optremark")
11+
@inline(never)
12+
public func forceOptRemark() -> Klass {
13+
return mySingleton // expected-remark {{retain}}
14+
// expected-note @-6 {{of 'mySingleton'}}
15+
}
16+
17+
@_semantics("optremark.sil-opt-remark-gen")
18+
@inline(never)
19+
public func forceOptRemark2() -> Klass {
20+
return mySingleton // expected-remark {{retain}}
21+
// expected-note @-13 {{of 'mySingleton'}}
22+
}
23+
24+
@_semantics("optremark.fail")
25+
@inline(never)
26+
public func failMatch() -> Klass {
27+
return mySingleton
28+
}
29+
30+
@_semantics("optremark")
31+
public func allocateInlineCallee() -> Klass {
32+
return Klass() // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
33+
}
34+
35+
@_semantics("optremark.sil-inliner")
36+
public func allocateInlineCallee2() -> Klass {
37+
return Klass() // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
38+
}
39+
40+
// This makes sure we don't emit any remarks if we do not have semantics.
41+
public func allocateInlineCallee3() -> Klass {
42+
return Klass()
43+
}
44+
45+
@_semantics("optremark.sil-inliner")
46+
@_semantics("optremark.sil-opt-remark-gen")
47+
public func mix1() -> (Klass, Klass) {
48+
return (mySingleton, Klass()) // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
49+
// expected-remark @-1:5 {{retain}}
50+
// expected-note @-42:12 {{of 'mySingleton'}}
51+
}
52+
53+
@_semantics("optremark.sil-inliner")
54+
public func mix2() -> (Klass, Klass) {
55+
return (mySingleton, Klass()) // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
56+
}
57+
58+
@_semantics("optremark.sil-opt-remark-gen")
59+
public func mix3() -> (Klass, Klass) {
60+
return (mySingleton, Klass()) // expected-remark @:5 {{retain}}
61+
// expected-note @-53:12 {{of 'mySingleton'}}
62+
}
63+
64+
@_semantics("optremark")
65+
public func mix4() -> (Klass, Klass) {
66+
return (mySingleton, Klass()) // expected-remark {{Pure call. Always profitable to inline "main.Klass.__allocating_init()"}}
67+
// expected-remark @-1:5 {{retain}}
68+
// expected-note @-60:12 {{of 'mySingleton'}}
69+
}
70+
71+
public func mix5() -> (Klass, Klass) {
72+
return (mySingleton, Klass())
73+
}

0 commit comments

Comments
 (0)