Skip to content

Commit 804bcac

Browse files
committed
[assembly-vision] Change release to do backwards then forwards when inferring source locs.
TLDR: I fixed a whole in the assembly-vision opt-remark pass where we were not emitting a remark for end of scope instructions at the beginning of blocks. Now all of these instructions (strong_release, end_access) should always reliably have a remark emitted for them. ---- I think that this is a pragmatic first solution to the problem of strong_release, release_value being the first instruction of a block. For those who are unaware, this issue is that for a long time we have searched backwards first for "end of scope" like instructions. This then allows us to identify the "end of scope" instruction as happening at the end of the previous statement which is where the developer thinks it should be: ``` var global: Klass func bar() -> @owned Klass { global } func foo() { // We want the remark for the bar() // expected-remark {{retain}} } ``` This makes sense since we want to show end of scope instructions as being applied to the earlier code whose scope it is ending. We can be clear that it is at the end of the statement by placing the carrot on the end of statement SourceLoc so there isn't any confusion upon whether or not That generally has delivered nice looking results, but what if our release is the first instruction in the block? In that case, we do not have any instruction that we can immediately use, so traditionally we just gave up and didn't emit anything. This is not an acceptable solution! We should be able to emit something for every retain/release in the program if we want users to be able to rely upon this! Thus we need to be able to get source location information from somewhere around First before we begin, my approach here is informed by my seeing over time that the optimizer does a pretty good job of not breaking SourceLoc info for terminators. With that in mind, there are two possible approaches here: using the terminator from the previous block and searching forward at worst taking the SourceLoc of the current block's terminator (or earlier if we find a good SourceLoc). I wasn't sure what the correct thing to do was at the time so I didn't fix the issue. After some thought, I realized that the correct solution is to if we fail a backwards search, search forwards. The reason why is that since our remarks runs late in the optimization pipeline, there is a very high likelihood that if we aren't folded into our previous block that there is a true need in the program for conditional control flow here. We want to avoid placing the release out of such pieces of code since it is misleading to the user: ``` In this example there is a release inside the case for .x but none for .y. In that case it is possible that we get a release for .f since payload is passed in at +1 at SILGen time. In such a case, using the terminator of the previous block would mean that we would have the release be marked as on payload instead of inside the case of .x. By using the terminator of the releases block, we can switch payload { case let .x(f): ... case let .y: ... } ``` So using the terminator from the previous block would be misleading to the user. Instead it is better to pick a location after the release that way we know at least the instruction we are inferring from must in some sense be With this fix, we should not emit locations for all retains, releases. We may not identify a good source loc for all of them, but we will identify them. optimization pipeline, if our block was not folded into the previous block there is a very high liklihood that there is some sort of conditional control flow that is truly necessary in the program. If we this generally implies that there is a real side effect in the program that is requiring conditional code execution (since the optimizer would have folded it). The reason why is that we are at least going to hit a terminator or a side-effect having instruction that generally have debug info preserved by the optimizer.
1 parent 87cde88 commit 804bcac

File tree

7 files changed

+103
-9
lines changed

7 files changed

+103
-9
lines changed

benchmark/single-source/ChaCha.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ func checkResult(_ plaintext: [UInt8]) {
361361
}
362362

363363
@inline(never)
364+
@_assemblyVision
364365
public func run_ChaCha(_ N: Int) {
365366
let key = Array(repeating: UInt8(1), count: 32)
366367
let nonce = Array(repeating: UInt8(2), count: 12)

include/swift/SIL/OptimizationRemark.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ enum class SourceLocInferenceBehavior : unsigned {
153153
BackwardsThenForwards = BackwardScan | ForwardScan2nd,
154154
ForwardScanAlwaysInfer = ForwardScan | AlwaysInfer,
155155
BackwardScanAlwaysInfer = BackwardScan | AlwaysInfer,
156+
BackwardThenForwardAlwaysInfer = BackwardScan | ForwardScan2nd | AlwaysInfer,
156157
};
157158

158159
inline SourceLocInferenceBehavior operator&(SourceLocInferenceBehavior lhs,

lib/SIL/Utils/OptimizationRemark.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,19 @@ static SourceLoc getLocForPresentation(SILLocation loc,
161161
llvm_unreachable("covered switch");
162162
}
163163

164+
static bool instHasInferrableLoc(SILInstruction &inst) {
165+
if (isa<DeallocStackInst>(inst) || isa<StrongRetainInst>(inst) ||
166+
isa<StrongReleaseInst>(inst) || isa<RetainValueInst>(inst) ||
167+
isa<ReleaseValueInst>(inst) || isa<EndAccessInst>(inst))
168+
return false;
169+
170+
// Ignore empty tuples
171+
if (auto *tup = dyn_cast<TupleInst>(&inst))
172+
return tup->getNumOperands() != 0;
173+
174+
return true;
175+
}
176+
164177
/// The user has passed us an instruction that for some reason has a source loc
165178
/// that can not be used. Search down the current block for an instruction with
166179
/// a valid source loc and use that instead.
@@ -169,11 +182,16 @@ inferOptRemarkSearchForwards(SILInstruction &i,
169182
SourceLocPresentationKind presentationKind) {
170183
for (auto &inst :
171184
llvm::make_range(std::next(i.getIterator()), i.getParent()->end())) {
185+
if (!instHasInferrableLoc(inst))
186+
continue;
187+
// Skip instructions without a loc we care about since we move it around.
172188
auto newLoc = getLocForPresentation(inst.getLoc(), presentationKind);
173189
if (auto inlinedLoc = inst.getDebugScope()->getOutermostInlineLocation())
174190
newLoc = getLocForPresentation(inlinedLoc, presentationKind);
175-
if (newLoc.isValid())
191+
if (newLoc.isValid()) {
192+
LLVM_DEBUG(llvm::dbgs() << "Inferring loc from " << inst);
176193
return newLoc;
194+
}
177195
}
178196

179197
return SourceLoc();
@@ -188,11 +206,15 @@ inferOptRemarkSearchBackwards(SILInstruction &i,
188206
SourceLocPresentationKind presentationKind) {
189207
for (auto &inst : llvm::make_range(std::next(i.getReverseIterator()),
190208
i.getParent()->rend())) {
209+
if (!instHasInferrableLoc(inst))
210+
continue;
191211
auto loc = inst.getLoc();
192212
if (auto inlinedLoc = inst.getDebugScope()->getOutermostInlineLocation())
193213
loc = inlinedLoc;
194-
if (auto result = getLocForPresentation(loc, presentationKind))
214+
if (auto result = getLocForPresentation(loc, presentationKind)) {
215+
LLVM_DEBUG(llvm::dbgs() << "Inferring loc from " << inst);
195216
return result;
217+
}
196218
}
197219

198220
return SourceLoc();

lib/SILOptimizer/Transforms/AssemblyVisionRemarkGenerator.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,8 @@ void AssemblyVisionRemarkGeneratorInstructionVisitor::visitEndAccessInst(
604604
// Use the actual source loc of the begin_access if it works. Otherwise,
605605
// scan backwards.
606606
auto remark =
607-
RemarkMissed("memory", *eai, SourceLocInferenceBehavior::BackwardScan,
607+
RemarkMissed("memory", *eai,
608+
SourceLocInferenceBehavior::BackwardThenForwardAlwaysInfer,
608609
SourceLocPresentationKind::EndRange)
609610
<< "end exclusive access to value of type '"
610611
<< NV("ValueType", eai->getOperand()->getType()) << "'";
@@ -649,7 +650,7 @@ void AssemblyVisionRemarkGeneratorInstructionVisitor::visitStrongReleaseInst(
649650

650651
auto remark =
651652
RemarkMissed("memory", *sri,
652-
SourceLocInferenceBehavior::BackwardScanAlwaysInfer,
653+
SourceLocInferenceBehavior::BackwardThenForwardAlwaysInfer,
653654
SourceLocPresentationKind::EndRange)
654655
<< "release of type '" << NV("ValueType", sri->getOperand()->getType())
655656
<< "'";
@@ -693,7 +694,7 @@ void AssemblyVisionRemarkGeneratorInstructionVisitor::visitReleaseValueInst(
693694
// Releases end a lifetime scope so we infer scan backward.
694695
auto remark =
695696
RemarkMissed("memory", *rvi,
696-
SourceLocInferenceBehavior::BackwardScanAlwaysInfer)
697+
SourceLocInferenceBehavior::BackwardThenForwardAlwaysInfer)
697698
<< "release of type '" << NV("ValueType", rvi->getOperand()->getType())
698699
<< "'";
699700
for (auto arg : inferredArgs) {

test/SILOptimizer/assemblyvision_remark/basic.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public func getGlobal() -> Klass {
1515
// expected-note @-5:12 {{of 'global'}}
1616
// expected-remark @-2:12 {{begin exclusive access to value of type 'Klass'}}
1717
// expected-note @-7:12 {{of 'global'}}
18+
// expected-remark @-4:12 {{end exclusive access to value of type 'Klass'}}
19+
// expected-note @-9:12 {{of 'global'}}
1820
}
1921

2022
// Make sure that the retain msg is at the beginning of the print and the

test/SILOptimizer/assemblyvision_remark/basic_yaml.swift

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public var global = Klass() // expected-remark {{heap allocated ref of type 'Kla
2828
// CHECK-NEXT: Pass: sil-assembly-vision-remark-gen
2929
// CHECK-NEXT: Name: sil.memory
3030
// CHECK-NEXT: DebugLoc: { File: '{{.*}}basic_yaml.swift',
31-
// CHECK-NEXT: Line: [[# @LINE + 27 ]], Column: 12 }
31+
// CHECK-NEXT: Line: [[# @LINE + 42 ]], Column: 12 }
3232
// CHECK-NEXT: Function: 'getGlobal()'
3333
// CHECK-NEXT: Args:
3434
// CHECK-NEXT: - String: 'begin exclusive access to value of type '''
@@ -42,6 +42,21 @@ public var global = Klass() // expected-remark {{heap allocated ref of type 'Kla
4242
// CHECK: --- !Missed
4343
// CHECK-NEXT: Pass: sil-assembly-vision-remark-gen
4444
// CHECK-NEXT: Name: sil.memory
45+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}basic_yaml.swift',
46+
// CHECK-NEXT: Line: [[# @LINE + 27 ]], Column: 12 }
47+
// CHECK-NEXT: Function: 'getGlobal()'
48+
// CHECK-NEXT: Args:
49+
// CHECK-NEXT: - String: 'end exclusive access to value of type '''
50+
// CHECK-NEXT: - ValueType: Klass
51+
// CHECK-NEXT: - String: ''''
52+
// CHECK-NEXT: - InferredValue: 'of ''global'''
53+
// CHECK-NEXT: DebugLoc: { File: '{{.*}}basic_yaml.swift',
54+
// CHECK-NEXT: Line: [[# @LINE - 29 ]], Column: 12 }
55+
// CHECK-NEXT: ...
56+
//
57+
// CHECK: --- !Missed
58+
// CHECK-NEXT: Pass: sil-assembly-vision-remark-gen
59+
// CHECK-NEXT: Name: sil.memory
4560
// CHECK-NEXT: DebugLoc: { File: '{{.*}}basic_yaml.swift',
4661
// CHECK-NEXT: Line: [[# @LINE + 12]], Column: 5 }
4762
// CHECK-NEXT: Function: 'getGlobal()'
@@ -51,14 +66,17 @@ public var global = Klass() // expected-remark {{heap allocated ref of type 'Kla
5166
// CHECK-NEXT: - String: ''''
5267
// CHECK-NEXT: - InferredValue: 'of ''global'''
5368
// CHECK-NEXT: DebugLoc: { File: '{{.*}}basic_yaml.swift',
54-
// CHECK-NEXT: Line: [[# @LINE - 29 ]], Column: 12 }
69+
// CHECK-NEXT: Line: [[# @LINE - 44 ]], Column: 12 }
5570
// CHECK-NEXT: ...
5671
@inline(never)
5772
public func getGlobal() -> Klass {
5873
return global // expected-remark @:5 {{retain of type 'Klass'}}
59-
// expected-note @-34:12 {{of 'global'}}
74+
// expected-note @-49:12 {{of 'global'}}
6075
// expected-remark @-2 {{begin exclusive access to value of type 'Klass'}}
61-
// expected-note @-36:12 {{of 'global'}}
76+
// expected-note @-51:12 {{of 'global'}}
77+
// NOTE: We really want the end access at :18, not :12. TODO Fix this!
78+
// expected-remark @-5 {{end exclusive access to value of type 'Klass'}}
79+
// expected-note @-54:12 {{of 'global'}}
6280
}
6381

6482
// CHECK: --- !Missed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %target-swiftc_driver -Osize -emit-sil %s -o /dev/null -Xfrontend -verify
2+
// REQUIRES: optimized_stdlib,swift_stdlib_no_asserts
3+
4+
// An extraction from the benchmark ChaCha20 that we were not ignoring
5+
// dealloc_stack and other end scope instructions.
6+
7+
enum ChaCha20 { }
8+
9+
extension ChaCha20 {
10+
@inline(never)
11+
public static func encrypt<Key: Collection, Nonce: Collection, Bytes: MutableCollection>(bytes: inout Bytes, key: Key, nonce: Nonce, initialCounter: UInt32 = 0) where Bytes.Element == UInt8, Key.Element == UInt8, Nonce.Element == UInt8 {
12+
print("I am lost...")
13+
}
14+
}
15+
16+
@inline(never)
17+
func checkResult(_ plaintext: [UInt8]) {
18+
precondition(plaintext.first! == 6 && plaintext.last! == 254)
19+
var hash: UInt64 = 0
20+
for byte in plaintext {
21+
// rotate
22+
hash = (hash &<< 8) | (hash &>> (64 - 8))
23+
hash ^= UInt64(byte)
24+
}
25+
precondition(hash == 0xa1bcdb217d8d14e4)
26+
}
27+
28+
@_semantics("optremark.sil-assembly-vision-remark-gen")
29+
public func run_ChaCha(_ N: Int) {
30+
let key = Array(repeating: UInt8(1), count: 32)
31+
let nonce = Array(repeating: UInt8(2), count: 12)
32+
33+
var checkedtext = Array(repeating: UInt8(0), count: 1024)
34+
ChaCha20.encrypt(bytes: &checkedtext, key: key, nonce: nonce)
35+
checkResult(checkedtext) // expected-remark {{release of type '}}
36+
// expected-note @-3 {{of 'checkedtext}}
37+
38+
var plaintext = Array(repeating: UInt8(0), count: 30720)
39+
for _ in 1...N {
40+
ChaCha20.encrypt(bytes: &plaintext, key: key, nonce: nonce)
41+
print(plaintext.first!) // expected-remark @:11 {{heap allocated ref of type '}}
42+
// expected-remark @-1:27 {{release of type '}}
43+
}
44+
} // expected-remark {{release of type '}}
45+
// expected-note @-7 {{of 'plaintext}}
46+
// expected-remark @-2 {{release of type '}}
47+
// expected-note @-16 {{of 'nonce}}
48+
// expected-remark @-4 {{release of type '}}
49+
// expected-note @-19 {{of 'key}}

0 commit comments

Comments
 (0)