Skip to content

Commit 5825f5e

Browse files
Merge pull request #9912 from felipepiovezan/felipe/unwind_by_asm_plans
[lldb][swift] Prefer assembly unwind plans for async registers
2 parents 306fe85 + 486a032 commit 5825f5e

File tree

4 files changed

+172
-1
lines changed

4 files changed

+172
-1
lines changed

lldb/source/Plugins/LanguageRuntime/Swift/SwiftLanguageRuntime.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2206,6 +2206,21 @@ static llvm::Expected<addr_t> GetCFA(Process &process, RegisterContext &regctx,
22062206
cfa_loc.GetValueType());
22072207
}
22082208

2209+
static UnwindPlanSP GetUnwindPlanForAsyncRegister(FuncUnwinders &unwinders,
2210+
Target &target,
2211+
Thread &thread) {
2212+
// We cannot trust compiler emitted unwind plans, as they respect the
2213+
// swifttail calling convention, which assumes the async register is _not_
2214+
// restored and therefore it is not tracked by compiler plans. If LLDB uses
2215+
// those plans, it may take "no info" to mean "register not clobbered". For
2216+
// those reasons, always favour the assembly plan first, it will try to track
2217+
// the async register by assuming the usual arm calling conventions.
2218+
if (UnwindPlanSP asm_plan = unwinders.GetAssemblyUnwindPlan(target, thread))
2219+
return asm_plan;
2220+
// In the unlikely case the assembly plan is not available, try all others.
2221+
return unwinders.GetUnwindPlanAtNonCallSite(target, thread);
2222+
}
2223+
22092224
/// Attempts to use UnwindPlans that inspect assembly to recover the entry value
22102225
/// of the async context register. This is a simplified version of the methods
22112226
/// in RegisterContextUnwind, since plumbing access to those here would be
@@ -2223,7 +2238,7 @@ static llvm::Expected<addr_t> ReadAsyncContextRegisterFromUnwind(
22232238

22242239
Target &target = process.GetTarget();
22252240
UnwindPlanSP unwind_plan =
2226-
unwinders->GetUnwindPlanAtNonCallSite(target, regctx.GetThread());
2241+
GetUnwindPlanForAsyncRegister(*unwinders, target, regctx.GetThread());
22272242
if (!unwind_plan)
22282243
return llvm::createStringError(
22292244
"SwiftLanguageRuntime: Failed to find non call site unwind plan at "
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 -Xfrontend -Xllvm -Xfrontend --emit-dwarf-unwind=always
3+
include Makefile.rules
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import lldb
2+
from lldbsuite.test.decorators import *
3+
import lldbsuite.test.lldbtest as lldbtest
4+
import lldbsuite.test.lldbutil as lldbutil
5+
6+
7+
# This test is a best effort attempt at creating a program with high register
8+
# pressure while also making the linker avoid compact unwind.
9+
class TestCase(lldbtest.TestBase):
10+
11+
mydir = lldbtest.TestBase.compute_mydir(__file__)
12+
13+
def set_breakpoints_all_instructions(self, target):
14+
funclet_name = "$s1a12ASYNC___1___SiyYaFTY0_"
15+
breakpoints = set()
16+
17+
sym_ctx_list = target.FindFunctions(funclet_name)
18+
self.assertEqual(
19+
sym_ctx_list.GetSize(),
20+
1,
21+
f"failed to get symbol context for {funclet_name}",
22+
)
23+
function = sym_ctx_list[0].function
24+
25+
instructions = list(function.GetInstructions(target))
26+
self.assertGreater(len(instructions), 0)
27+
for instruction in instructions:
28+
bp = target.BreakpointCreateBySBAddress(instruction.GetAddress())
29+
self.assertTrue(
30+
bp.IsValid(), f"failed to set bp inside funclet {funclet_name}"
31+
)
32+
breakpoints.add(bp.GetID())
33+
return breakpoints
34+
35+
def check_unwind_ok(self, thread):
36+
# Check that we see the virtual backtrace:
37+
expected_funcnames = [
38+
"ASYNC___1___",
39+
"ASYNC___2___",
40+
"ASYNC___3___",
41+
"ASYNC___4___",
42+
"ASYNC___5___",
43+
]
44+
frames = thread.frames
45+
self.assertGreater(
46+
len(frames), len(expected_funcnames), f"Invalid backtrace for {frames}"
47+
)
48+
actual_funcnames = [
49+
frame.GetFunctionName() for frame in frames[: len(expected_funcnames)]
50+
]
51+
for expected_name, actual_name in zip(expected_funcnames, actual_funcnames):
52+
self.assertIn(expected_name, actual_name, f"Unexpected backtrace: {frames}")
53+
54+
@swiftTest
55+
@skipIf(oslist=["windows", "linux"])
56+
def test(self):
57+
"""Test that the debugger can unwind at all instructions of all funclets"""
58+
self.build()
59+
60+
source_file = lldb.SBFileSpec("main.swift")
61+
target, process, _, bp = lldbutil.run_to_source_breakpoint(
62+
self, "BREAK HERE", source_file
63+
)
64+
target.DeleteAllBreakpoints()
65+
66+
breakpoints = self.set_breakpoints_all_instructions(target)
67+
num_breakpoints = len(breakpoints)
68+
69+
# Reach most breakpoints and ensure we can unwind in that position.
70+
while True:
71+
process.Continue()
72+
if process.GetState() == lldb.eStateExited:
73+
break
74+
thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonBreakpoint)
75+
self.assertTrue(thread.IsValid())
76+
bpid = thread.GetStopReasonDataAtIndex(0)
77+
breakpoints.remove(bpid)
78+
target.FindBreakpointByID(bpid).SetEnabled(False)
79+
80+
self.check_unwind_ok(thread)
81+
82+
# We will never hit all breakpoints we set, because of things like
83+
# overflow handling or other unreachable traps. However, it's good to
84+
# have some sanity check that we have hit at least a decent chunk of
85+
# them.
86+
breakpoints_not_hit = len(breakpoints)
87+
self.assertLess(breakpoints_not_hit / num_breakpoints, 0.25)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
func work(_ objects: Any...) {
2+
for object in objects {
3+
print("Processing object of type: \(type(of: object))")
4+
}
5+
}
6+
7+
func use(_ x: Int, _ y: Int) -> Int {
8+
return x &* y &+ x &- y
9+
}
10+
11+
var arr: [Int] = []
12+
13+
func ASYNC___1___() async -> Int {
14+
var a1 = 1, a2 = 2, a3 = 3, a4 = 4, a5 = 5
15+
print("BREAK HERE")
16+
var a6 = 6, a7 = 7, a8 = 8, a9 = 9, a10 = 10
17+
var a11 = 11, a12 = 12, a13 = 13, a14 = 14, a15 = 15
18+
var a16 = 16, a17 = 17, a18 = 18, a19 = 19, a20 = 20
19+
var a21 = 21, a22 = 22, a23 = 23, a24 = 24, a25 = 25
20+
var a26 = 26, a27 = 27, a28 = 28, a29 = 29, a30 = 30
21+
a1 = use(a1, a2)
22+
a3 = use(a3, a4)
23+
a5 = use(a5, a6)
24+
a7 = use(a7, a8)
25+
a9 = use(a9, a10)
26+
a11 = use(a11, a12)
27+
a13 = use(a13, a14)
28+
a15 = use(a15, a16)
29+
a17 = use(a17, a18)
30+
a19 = use(a19, a20)
31+
a21 = use(a21, a22)
32+
a23 = use(a23, a24)
33+
a25 = use(a25, a26)
34+
a27 = use(a27, a28)
35+
a29 = use(a29, a30)
36+
work(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24,a25, a26,a27, a28, a29, a30)
37+
arr = [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24,a25, a26,a27, a28, a29, a30]
38+
return a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8 + a9 + a10 + a11 + a12 + a13 + a14 + a15 + a16 + a17 + a18 + a19
39+
}
40+
41+
func ASYNC___2___() async -> Int {
42+
let result = await ASYNC___1___()
43+
return result
44+
}
45+
46+
func ASYNC___3___() async -> Int {
47+
let result = await ASYNC___2___()
48+
return result
49+
}
50+
51+
func ASYNC___4___() async -> Int {
52+
let result = await ASYNC___3___()
53+
return result
54+
}
55+
56+
func ASYNC___5___() async -> Int {
57+
let result = await ASYNC___4___()
58+
return result
59+
}
60+
61+
@main struct Main {
62+
static func main() async {
63+
let result = await ASYNC___5___()
64+
print(result)
65+
}
66+
}

0 commit comments

Comments
 (0)