Skip to content

Commit c58c369

Browse files
authored
[lldb] Defer checking CFA memory region (swiftlang#7877)
To support stepping in Swift async code, LLDB uses the memory region (ie stack vs heap) of the Canonical Frame Address (CFA) to identify when an async function calls a synchronous function. But this has resulted in a performance regression, as determining the memory region of the CFA is not free, and eagerly performing this query for every `StackID`, particularly during backtracing/unwinding – when stepping is not yet occurring, is wasteful. This change proposes a solution that defers the memory region query until required to perform stepping logic. The logic that checks heaps vs stacks was moved into `ThreadPlan`, the only place that currently needs to make that determination. Performing this check in `ThreadPlan` provides access to the process, allowing stack/heap determination to happen as needed. Previously, the stack/heap determination was made eagerly when the `StackID` was constructed. rdar://117505613
1 parent afcd9c2 commit c58c369

9 files changed

+72
-47
lines changed

lldb/include/lldb/Target/StackID.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class StackID {
3636
void Clear() {
3737
m_pc = LLDB_INVALID_ADDRESS;
3838
m_cfa = LLDB_INVALID_ADDRESS;
39-
m_cfa_on_stack = true;
39+
m_cfa_on_stack = eLazyBoolCalculate;
4040
m_symbol_scope = nullptr;
4141
}
4242

@@ -57,15 +57,18 @@ class StackID {
5757
return *this;
5858
}
5959

60-
bool IsCFAOnStack() const { return m_cfa_on_stack; }
60+
/// Check if the CFA is on the stack, or elsewhere in the process, such as on
61+
/// the heap.
62+
bool IsCFAOnStack(Process &process) const;
63+
64+
/// Determine if the first StackID is "younger" than the second.
65+
static bool IsYounger(const StackID &lhs, const StackID &rhs,
66+
Process &process);
6167

6268
protected:
6369
friend class StackFrame;
6470

65-
explicit StackID(lldb::addr_t pc, lldb::addr_t cfa, lldb::ThreadSP thread_sp)
66-
: m_pc(pc), m_cfa(cfa), m_cfa_on_stack(IsStackAddress(cfa, thread_sp)) {}
67-
68-
bool IsStackAddress(lldb::addr_t addr, lldb::ThreadSP thread_sp) const;
71+
explicit StackID(lldb::addr_t pc, lldb::addr_t cfa) : m_pc(pc), m_cfa(cfa) {}
6972

7073
void SetPC(lldb::addr_t pc) { m_pc = pc; }
7174

@@ -84,7 +87,7 @@ class StackID {
8487
// below)
8588
// True if the CFA is an address on the stack, false if it's an address
8689
// elsewhere (ie heap).
87-
bool m_cfa_on_stack = true;
90+
mutable LazyBool m_cfa_on_stack = eLazyBoolCalculate;
8891
SymbolContextScope *m_symbol_scope =
8992
nullptr; // If nullptr, there is no block or symbol for this frame.
9093
// If not nullptr, this will either be the scope for the
@@ -98,9 +101,6 @@ class StackID {
98101
bool operator==(const StackID &lhs, const StackID &rhs);
99102
bool operator!=(const StackID &lhs, const StackID &rhs);
100103

101-
// frame_id_1 < frame_id_2 means "frame_id_1 is YOUNGER than frame_id_2"
102-
bool operator<(const StackID &lhs, const StackID &rhs);
103-
104104
} // namespace lldb_private
105105

106106
#endif // LLDB_TARGET_STACKID_H

lldb/include/lldb/Target/ThreadPlan.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,28 @@ class ThreadPlan : public std::enable_shared_from_this<ThreadPlan>,
555555

556556
bool IsUsuallyUnexplainedStopReason(lldb::StopReason);
557557

558+
/// Determine if the first StackID is younger than the second.
559+
///
560+
/// A callee is considered younger than its caller, and is also younger than
561+
/// all ancestor frames leading up to the caller. Consider this stack:
562+
///
563+
/// +------------------+
564+
/// | Fa |
565+
/// +------------------+
566+
/// | Fb |
567+
/// +------------------+
568+
/// | ... |
569+
/// +------------------+
570+
/// | Fy |
571+
/// +------------------+
572+
/// | Fz |
573+
/// +------------------+
574+
///
575+
/// In this case Fz is younger than each of Fy, ..., Fb, and Fa. Fy is not
576+
/// younger than Fz, but is younger than all frames above it in the stack,
577+
/// including Fa and Fb.
578+
bool IsYounger(const StackID &lhs, const StackID &rhs) const;
579+
558580
Status m_status;
559581
Process &m_process;
560582
lldb::tid_t m_tid;

lldb/source/Target/StackFrame.cpp

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx,
5757
const SymbolContext *sc_ptr)
5858
: m_thread_wp(thread_sp), m_frame_index(frame_idx),
5959
m_concrete_frame_index(unwind_frame_index), m_reg_context_sp(),
60-
m_id(pc, cfa, thread_sp), m_frame_code_addr(pc), m_sc(), m_flags(),
61-
m_frame_base(), m_frame_base_error(), m_cfa_is_valid(cfa_is_valid),
60+
m_id(pc, cfa), m_frame_code_addr(pc), m_sc(), m_flags(), m_frame_base(),
61+
m_frame_base_error(), m_cfa_is_valid(cfa_is_valid),
6262
m_stack_frame_kind(kind),
6363
m_behaves_like_zeroth_frame(behaves_like_zeroth_frame),
6464
m_variable_list_sp(), m_variable_list_value_objects(),
@@ -83,10 +83,9 @@ StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx,
8383
const SymbolContext *sc_ptr)
8484
: m_thread_wp(thread_sp), m_frame_index(frame_idx),
8585
m_concrete_frame_index(unwind_frame_index),
86-
m_reg_context_sp(reg_context_sp), m_id(pc, cfa, thread_sp),
87-
m_frame_code_addr(pc), m_sc(), m_flags(), m_frame_base(),
88-
m_frame_base_error(), m_cfa_is_valid(true),
89-
m_stack_frame_kind(StackFrame::Kind::Regular),
86+
m_reg_context_sp(reg_context_sp), m_id(pc, cfa), m_frame_code_addr(pc),
87+
m_sc(), m_flags(), m_frame_base(), m_frame_base_error(),
88+
m_cfa_is_valid(true), m_stack_frame_kind(StackFrame::Kind::Regular),
9089
m_behaves_like_zeroth_frame(behaves_like_zeroth_frame),
9190
m_variable_list_sp(), m_variable_list_value_objects(),
9291
m_recognized_frame_sp(), m_disassembly(), m_mutex() {
@@ -110,8 +109,7 @@ StackFrame::StackFrame(const ThreadSP &thread_sp, user_id_t frame_idx,
110109
: m_thread_wp(thread_sp), m_frame_index(frame_idx),
111110
m_concrete_frame_index(unwind_frame_index),
112111
m_reg_context_sp(reg_context_sp),
113-
m_id(pc_addr.GetLoadAddress(thread_sp->CalculateTarget().get()), cfa,
114-
thread_sp),
112+
m_id(pc_addr.GetLoadAddress(thread_sp->CalculateTarget().get()), cfa),
115113
m_frame_code_addr(pc_addr), m_sc(), m_flags(), m_frame_base(),
116114
m_frame_base_error(), m_cfa_is_valid(true),
117115
m_stack_frame_kind(StackFrame::Kind::Regular),

lldb/source/Target/StackID.cpp

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@
1717

1818
using namespace lldb_private;
1919

20-
bool StackID::IsStackAddress(lldb::addr_t addr,
21-
lldb::ThreadSP thread_sp) const {
22-
if (addr == LLDB_INVALID_ADDRESS)
23-
return false;
24-
25-
if (thread_sp)
26-
if (auto process_sp = thread_sp->GetProcess()) {
20+
bool StackID::IsCFAOnStack(Process &process) const {
21+
if (m_cfa_on_stack == eLazyBoolCalculate) {
22+
m_cfa_on_stack = eLazyBoolNo;
23+
if (m_cfa != LLDB_INVALID_ADDRESS) {
2724
MemoryRegionInfo mem_info;
28-
if (process_sp->GetMemoryRegionInfo(addr, mem_info).Success())
29-
return mem_info.IsStackMemory() == MemoryRegionInfo::eYes;
25+
if (process.GetMemoryRegionInfo(m_cfa, mem_info).Success())
26+
if (mem_info.IsStackMemory() == MemoryRegionInfo::eYes)
27+
m_cfa_on_stack = eLazyBoolYes;
3028
}
31-
return true; // assumed
29+
}
30+
return m_cfa_on_stack == eLazyBoolYes;
3231
}
3332

3433
void StackID::Dump(Stream *s) {
@@ -74,19 +73,20 @@ bool lldb_private::operator!=(const StackID &lhs, const StackID &rhs) {
7473
return lhs_scope != rhs_scope;
7574
}
7675

77-
bool lldb_private::operator<(const StackID &lhs, const StackID &rhs) {
78-
const lldb::addr_t lhs_cfa = lhs.GetCallFrameAddress();
79-
const lldb::addr_t rhs_cfa = rhs.GetCallFrameAddress();
80-
76+
bool StackID::IsYounger(const StackID &lhs, const StackID &rhs,
77+
Process &process) {
8178
// FIXME: rdar://76119439
82-
// Heuristic: At the boundary between an async parent frame calling a regular
83-
// child frame, the CFA of the parent async function is a heap addresses, and
84-
// the CFA of concrete child function is a stack addresses. Therefore, if lhs
85-
// is on stack, and rhs is not, lhs is considered less than rhs (regardless of
86-
// address values).
87-
if (lhs.IsCFAOnStack() && !rhs.IsCFAOnStack())
79+
// At the boundary between an async parent frame calling a regular child
80+
// frame, the CFA of the parent async function is a heap addresses, and the
81+
// CFA of concrete child function is a stack address. Therefore, if lhs is
82+
// on stack, and rhs is not, lhs is considered less than rhs, independent of
83+
// address values.
84+
if (lhs.IsCFAOnStack(process) && !rhs.IsCFAOnStack(process))
8885
return true;
8986

87+
const lldb::addr_t lhs_cfa = lhs.GetCallFrameAddress();
88+
const lldb::addr_t rhs_cfa = rhs.GetCallFrameAddress();
89+
9090
// FIXME: We are assuming that the stacks grow downward in memory. That's not
9191
// necessary, but true on
9292
// all the machines we care about at present. If this changes, we'll have to

lldb/source/Target/ThreadPlan.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ bool ThreadPlan::IsUsuallyUnexplainedStopReason(lldb::StopReason reason) {
180180
}
181181
}
182182

183+
bool ThreadPlan::IsYounger(const StackID &lhs, const StackID &rhs) const {
184+
return StackID::IsYounger(lhs, rhs, m_process);
185+
}
186+
183187
// ThreadPlanNull
184188

185189
ThreadPlanNull::ThreadPlanNull(Thread &thread)

lldb/source/Target/ThreadPlanStepInstruction.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ bool ThreadPlanStepInstruction::IsPlanStale() {
110110
SetPlanComplete();
111111
}
112112
return (thread.GetRegisterContext()->GetPC(0) != m_instruction_addr);
113-
} else if (cur_frame_id < m_stack_id) {
113+
} else if (IsYounger(cur_frame_id, m_stack_id)) {
114114
// If the current frame is younger than the start frame and we are stepping
115115
// over, then we need to continue, but if we are doing just one step, we're
116116
// done.
@@ -140,7 +140,8 @@ bool ThreadPlanStepInstruction::ShouldStop(Event *event_ptr) {
140140

141141
StackID cur_frame_zero_id = cur_frame_sp->GetStackID();
142142

143-
if (cur_frame_zero_id == m_stack_id || m_stack_id < cur_frame_zero_id) {
143+
if (cur_frame_zero_id == m_stack_id ||
144+
IsYounger(m_stack_id, cur_frame_zero_id)) {
144145
if (thread.GetRegisterContext()->GetPC(0) != m_instruction_addr) {
145146
if (--m_iteration_count <= 0) {
146147
SetPlanComplete();

lldb/source/Target/ThreadPlanStepOut.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,12 +334,12 @@ bool ThreadPlanStepOut::DoPlanExplainsStop(Event *event_ptr) {
334334

335335
if (m_step_out_to_id == frame_zero_id)
336336
done = true;
337-
else if (m_step_out_to_id < frame_zero_id) {
337+
else if (IsYounger(m_step_out_to_id, frame_zero_id)) {
338338
// Either we stepped past the breakpoint, or the stack ID calculation
339339
// was incorrect and we should probably stop.
340340
done = true;
341341
} else {
342-
done = (m_immediate_step_from_id < frame_zero_id);
342+
done = IsYounger(m_immediate_step_from_id, frame_zero_id);
343343
}
344344

345345
if (done) {
@@ -398,7 +398,7 @@ bool ThreadPlanStepOut::ShouldStop(Event *event_ptr) {
398398

399399
if (!done) {
400400
StackID frame_zero_id = GetThread().GetStackFrameAtIndex(0)->GetStackID();
401-
done = !(frame_zero_id < m_step_out_to_id);
401+
done = !IsYounger(frame_zero_id, m_step_out_to_id);
402402
}
403403

404404
// The normal step out computations think we are done, so all we need to do
@@ -608,5 +608,5 @@ bool ThreadPlanStepOut::IsPlanStale() {
608608
// then there's something for us to do. Otherwise, we're stale.
609609

610610
StackID frame_zero_id = GetThread().GetStackFrameAtIndex(0)->GetStackID();
611-
return !(frame_zero_id < m_step_out_to_id);
611+
return !IsYounger(frame_zero_id, m_step_out_to_id);
612612
}

lldb/source/Target/ThreadPlanStepRange.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ lldb::FrameComparison ThreadPlanStepRange::CompareCurrentFrameToStartFrame() {
220220

221221
if (cur_frame_id == m_stack_id) {
222222
frame_order = eFrameCompareEqual;
223-
} else if (cur_frame_id < m_stack_id) {
223+
} else if (IsYounger(cur_frame_id, m_stack_id)) {
224224
frame_order = eFrameCompareYounger;
225225
} else {
226226
StackFrameSP cur_parent_frame = thread.GetStackFrameAtIndex(1);

lldb/source/Target/ThreadPlanStepUntil.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ void ThreadPlanStepUntil::AnalyzeStop() {
174174
bool done;
175175
StackID cur_frame_zero_id;
176176

177-
done = (m_stack_id < cur_frame_zero_id);
177+
done = IsYounger(m_stack_id, cur_frame_zero_id);
178178

179179
if (done) {
180180
m_stepped_out = true;
@@ -200,7 +200,7 @@ void ThreadPlanStepUntil::AnalyzeStop() {
200200

201201
if (frame_zero_id == m_stack_id)
202202
done = true;
203-
else if (frame_zero_id < m_stack_id)
203+
else if (IsYounger(frame_zero_id, m_stack_id))
204204
done = false;
205205
else {
206206
StackFrameSP older_frame_sp = thread.GetStackFrameAtIndex(1);

0 commit comments

Comments
 (0)