Skip to content

Commit 5391bc6

Browse files
committed
[debug-info] Only hoist the first dbg inst associated with an async debug variable.
Otherwise, we may hoist a llvm.dbg.addr over another llvm.dbg.addr or llvm.dbg.value, changing observable debugging state. As an additional benefit, this finally lets me add straight line end to end lldb tests for async var and lets. This was the last thing stopping me from doing that.
1 parent a1c63d5 commit 5391bc6

File tree

4 files changed

+224
-1
lines changed

4 files changed

+224
-1
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SWIFT_SOURCES := main.swift
2+
SWIFTFLAGS_EXTRAS := -parse-as-library
3+
include Makefile.rules
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# TestSwiftMoveFunctionAsync.py
2+
#
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2014 - 2016 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+
Check that we properly show variables at various points of the CFG while
14+
stepping with the move function.
15+
"""
16+
import lldb
17+
from lldbsuite.test.lldbtest import *
18+
from lldbsuite.test.decorators import *
19+
import lldbsuite.test.lldbutil as lldbutil
20+
import os
21+
import sys
22+
import unittest2
23+
24+
def stderr_print(line):
25+
sys.stderr.write(line + "\n")
26+
27+
class TestSwiftMoveFunctionAsyncType(TestBase):
28+
29+
mydir = TestBase.compute_mydir(__file__)
30+
31+
@swiftTest
32+
def test_swift_move_function_async(self):
33+
"""Check that we properly show variables at various points of the CFG while
34+
stepping with the move function.
35+
"""
36+
self.build()
37+
38+
self.target, self.process, self.thread, self.bkpt = \
39+
lldbutil.run_to_source_breakpoint(
40+
self, 'Set breakpoint', lldb.SBFileSpec('main.swift'))
41+
42+
# We setup a single breakpoint in copyable var test so we can disable it
43+
# after we hit it.
44+
self.do_setup_breakpoints()
45+
46+
self.do_check_copyable_value_test()
47+
self.do_check_copyable_var_test()
48+
49+
def setUp(self):
50+
TestBase.setUp(self)
51+
self.main_source = "main.swift"
52+
self.main_source_spec = lldb.SBFileSpec(self.main_source)
53+
self.exec_name = "a.out"
54+
55+
def get_var(self, name):
56+
frame = self.thread.frames[0]
57+
return frame.FindVariable(name)
58+
59+
def do_setup_breakpoints(self):
60+
self.breakpoints = []
61+
pattern = 'Special breakpoint'
62+
brk = self.target.BreakpointCreateBySourceRegex(
63+
pattern, self.main_source_spec)
64+
self.assertGreater(brk.GetNumLocations(), 0, VALID_BREAKPOINT)
65+
self.breakpoints.append(brk)
66+
67+
def do_check_copyable_value_test(self):
68+
# We haven't defined varK yet.
69+
varK = self.get_var('k')
70+
self.assertEqual(varK.unsigned, 0, "varK initialized too early?!")
71+
72+
# Go to break point 2.1. k should be valid.
73+
self.process.Continue()
74+
varK = self.get_var('k')
75+
self.assertGreater(varK.unsigned, 0, "varK not initialized?!")
76+
77+
# Go to breakpoint 2.2. k should still be valid. And we should be on the
78+
# other side of the force split.
79+
self.process.Continue()
80+
varK = self.get_var('k')
81+
self.assertGreater(varK.unsigned, 0, "varK not initialized?!")
82+
83+
# Go to breakpoint 3. k should still be valid. We should be at the move
84+
# on the other side of the forceSplit.
85+
self.process.Continue()
86+
varK = self.get_var('k')
87+
self.assertGreater(varK.unsigned, 0, "varK not initialized?!")
88+
89+
# We are now at break point 4. We have moved k, it should be empty.
90+
self.process.Continue()
91+
varK = self.get_var('k')
92+
self.assertIsNone(varK.value, "K is live but was moved?!")
93+
94+
# Finally, we are on the other side of the final force split. Make sure
95+
# the value still isn't available.
96+
self.process.Continue()
97+
varK = self.get_var('k')
98+
self.assertIsNone(varK.value, "K is live but was moved?!")
99+
100+
# Run so we hit the next breakpoint to jump to the next test's
101+
# breakpoint.
102+
self.process.Continue()
103+
104+
def do_check_copyable_var_test(self):
105+
# We haven't defined varK yet.
106+
varK = self.get_var('k')
107+
self.assertEqual(varK.unsigned, 0, "varK initialized too early?!")
108+
109+
# Go to break point 2.1. k should be valid.
110+
self.process.Continue()
111+
varK = self.get_var('k')
112+
self.assertGreater(varK.unsigned, 0, "varK not initialized?!")
113+
114+
# Go to breakpoint 2.2. k should still be valid. And we should be on the
115+
# other side of the force split.
116+
self.process.Continue()
117+
varK = self.get_var('k')
118+
self.assertGreater(varK.unsigned, 0, "varK not initialized?!")
119+
120+
# Go to breakpoint 3. k should still be valid. We should be at the move
121+
# on the other side of the forceSplit.
122+
self.process.Continue()
123+
varK = self.get_var('k')
124+
self.assertGreater(varK.unsigned, 0, "varK not initialized?!")
125+
126+
# There is an instruction with the wrong debug location on Linux,
127+
# causing us to jump back to 'step backwards' to an earlier location
128+
# before we have run the move. So disable that breakpoint so when we
129+
# continue, we get to the appropriate location on Linux and other
130+
# platforms.
131+
#
132+
# TODO: Investigate why this is happening!
133+
self.runCmd('# Skipping bad loc by disabling earlier break point 6')
134+
self.runCmd('break dis 2')
135+
136+
# We are now at break point 4. We have moved k, it should be empty.
137+
self.process.Continue()
138+
varK = self.get_var('k')
139+
self.assertIsNone(varK.value, "K is live but was moved?!")
140+
141+
# Now, we are on the other side of the final force split. Make sure
142+
# the value still isn't available.
143+
self.process.Continue()
144+
self.runCmd('# On other side of force split')
145+
varK = self.get_var('k')
146+
self.assertIsNone(varK.value, "K is live but was moved?!")
147+
148+
# Finally, we have reinitialized k, look for k.
149+
self.process.Continue()
150+
self.runCmd('# After var reinit')
151+
varK = self.get_var('k')
152+
self.assertGreater(varK.unsigned, 0, "varK not initialized?!")
153+
154+
# Run so we hit the next breakpoint to jump to the next test's
155+
# breakpoint.
156+
self.runCmd('# At end of routine!')
157+
self.process.Continue()
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// main.swift
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2016 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+
public class Klass {
14+
public func doSomething() {}
15+
}
16+
17+
public func forceSplit() async {}
18+
19+
//////////////////
20+
// Simple Tests //
21+
//////////////////
22+
23+
public func copyableValueTest() async {
24+
print("stop here") // Set breakpoint
25+
let k = Klass()
26+
k.doSomething()
27+
await forceSplit() // Set breakpoint
28+
let m = _move(k) // Set breakpoint
29+
m.doSomething() // Set breakpoint
30+
await forceSplit()
31+
m.doSomething() // Set breakpoint
32+
}
33+
34+
public func copyableVarTest() async {
35+
print("stop here")
36+
var k = Klass() // Special breakpoint
37+
k.doSomething()
38+
await forceSplit() // Set breakpoint
39+
let m = _move(k) // Set breakpoint
40+
m.doSomething() // Set breakpoint
41+
await forceSplit()
42+
k = Klass() // Set breakpoint
43+
k.doSomething() // Set breakpoint
44+
print("stop here")
45+
}
46+
47+
//////////////////////////
48+
// Top Level Entrypoint //
49+
//////////////////////////
50+
51+
@main struct Main {
52+
static func main() async {
53+
await copyableValueTest()
54+
await copyableVarTest()
55+
}
56+
}

llvm/lib/CodeGen/LiveDebugValues/VarLocBasedImpl.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2197,12 +2197,19 @@ bool VarLocBasedLDV::ExtendRanges(MachineFunction &MF,
21972197
// function parameters in order to generate debug entry values for them.
21982198
SmallVector<MachineInstr *, 8> AsyncDbgValues;
21992199
MachineBasicBlock &First_MBB = *(MF.begin());
2200+
// Use a cache so that we only hoist the first swift async context debug inst
2201+
// for a specific DBG_VALUE. Otherwise, we may hoist inappropriately over a
2202+
// llvm.dbg.value.
2203+
SmallDenseSet<DebugVariable, 8> SeenDebugVars;
22002204
for (auto &MI : First_MBB) {
22012205
collectRegDefs(MI, DefinedRegs, TRI);
22022206
if (MI.isDebugValue()) {
22032207
// In Swift async functions entry values are preferred, since they
22042208
// can be evaluated in both live frames and virtual backtraces.
2205-
if (isSwiftAsyncContext(MI)) {
2209+
if (SeenDebugVars.insert(DebugVariable(MI.getDebugVariable(),
2210+
MI.getDebugExpression(),
2211+
MI.getDebugLoc()->getInlinedAt())).second &&
2212+
isSwiftAsyncContext(MI)) {
22062213
// If our instruction is not an entry value yet, make it an entry value.
22072214
if (!MI.getDebugExpression()->isEntryValue()) {
22082215
MI.getOperand(3).setMetadata(DIExpression::prepend(

0 commit comments

Comments
 (0)