Skip to content

Commit 82aa5d1

Browse files
committed
[lldb] Show coro_frame in std::coroutine_handle pretty printer
This commit adjusts the pretty printer for `std::corotoutine_handle` based on recent personal experiences with debugging C++20 coroutines: 1. It adds the `coro_frame` member. This member exposes the complete coroutine frame contents, including the suspension point id and all internal variables which the compiler decided to persist into the coroutine frame. While this data is highly compiler-specific, inspecting it can help identify the internal state of suspended coroutines. 2. It includes the `promise` and `coro_frame` members, even if devirtualization failed and we could not infer the promise type / the coro_frame type. Having them available as `void*` pointers can still be useful to identify, e.g., which two coroutines have the same frame / promise pointers.
1 parent 0c77468 commit 82aa5d1

File tree

5 files changed

+102
-102
lines changed

5 files changed

+102
-102
lines changed

lldb/include/lldb/DataFormatters/TypeSynthetic.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class SyntheticChildrenFrontEnd {
9292
lldb::ValueObjectSP
9393
CreateValueObjectFromAddress(llvm::StringRef name, uint64_t address,
9494
const ExecutionContext &exe_ctx,
95-
CompilerType type);
95+
CompilerType type, bool do_deref = true);
9696

9797
lldb::ValueObjectSP CreateValueObjectFromData(llvm::StringRef name,
9898
const DataExtractor &data,

lldb/source/DataFormatters/TypeSynthetic.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ lldb::ValueObjectSP SyntheticChildrenFrontEnd::CreateValueObjectFromExpression(
138138

139139
lldb::ValueObjectSP SyntheticChildrenFrontEnd::CreateValueObjectFromAddress(
140140
llvm::StringRef name, uint64_t address, const ExecutionContext &exe_ctx,
141-
CompilerType type) {
142-
ValueObjectSP valobj_sp(
143-
ValueObject::CreateValueObjectFromAddress(name, address, exe_ctx, type));
141+
CompilerType type, bool do_deref) {
142+
ValueObjectSP valobj_sp(ValueObject::CreateValueObjectFromAddress(
143+
name, address, exe_ctx, type, do_deref));
144144
if (valobj_sp)
145145
valobj_sp->SetSyntheticChildrenGenerated(true);
146146
return valobj_sp;

lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp

Lines changed: 68 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
1212
#include "lldb/Symbol/Function.h"
1313
#include "lldb/Symbol/VariableList.h"
14-
#include "lldb/Utility/LLDBLog.h"
15-
#include "lldb/Utility/Log.h"
1614

1715
using namespace lldb;
1816
using namespace lldb_private;
@@ -61,19 +59,23 @@ static Function *ExtractDestroyFunction(lldb::TargetSP target_sp,
6159
return destroy_func_address.CalculateSymbolContextFunction();
6260
}
6361

64-
static CompilerType InferPromiseType(Function &destroy_func) {
65-
Block &block = destroy_func.GetBlock(true);
62+
// clang generates aritifical `__promise` and `__coro_frame` variables inside
63+
// the destroy function. Look for those variables and extract their type.
64+
static CompilerType InferArtificialCoroType(Function *destroy_func,
65+
ConstString var_name) {
66+
if (!destroy_func)
67+
return {};
68+
69+
Block &block = destroy_func->GetBlock(true);
6670
auto variable_list = block.GetBlockVariableList(true);
6771

68-
// clang generates an artificial `__promise` variable inside the
69-
// `destroy` function. Look for it.
70-
auto promise_var = variable_list->FindVariable(ConstString("__promise"));
71-
if (!promise_var)
72+
auto var = variable_list->FindVariable(var_name);
73+
if (!var)
7274
return {};
73-
if (!promise_var->IsArtificial())
75+
if (!var->IsArtificial())
7476
return {};
7577

76-
Type *promise_type = promise_var->GetType();
78+
Type *promise_type = var->GetType();
7779
if (!promise_type)
7880
return {};
7981
return promise_type->GetForwardCompilerType();
@@ -107,30 +109,17 @@ lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
107109

108110
llvm::Expected<uint32_t> lldb_private::formatters::
109111
StdlibCoroutineHandleSyntheticFrontEnd::CalculateNumChildren() {
110-
if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
111-
return 0;
112-
113-
return m_promise_ptr_sp ? 3 : 2;
112+
return m_children.size();
114113
}
115114

116115
lldb::ValueObjectSP lldb_private::formatters::
117116
StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) {
118-
switch (idx) {
119-
case 0:
120-
return m_resume_ptr_sp;
121-
case 1:
122-
return m_destroy_ptr_sp;
123-
case 2:
124-
return m_promise_ptr_sp;
125-
}
126-
return lldb::ValueObjectSP();
117+
return idx < m_children.size() ? m_children[idx] : lldb::ValueObjectSP();
127118
}
128119

129120
lldb::ChildCacheState
130121
lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::Update() {
131-
m_resume_ptr_sp.reset();
132-
m_destroy_ptr_sp.reset();
133-
m_promise_ptr_sp.reset();
122+
m_children.clear();
134123

135124
ValueObjectSP valobj_sp = m_backend.GetNonSyntheticValue();
136125
if (!valobj_sp)
@@ -140,77 +129,78 @@ lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::Update() {
140129
if (frame_ptr_addr == 0 || frame_ptr_addr == LLDB_INVALID_ADDRESS)
141130
return lldb::ChildCacheState::eRefetch;
142131

143-
auto ast_ctx = valobj_sp->GetCompilerType().GetTypeSystem<TypeSystemClang>();
144-
if (!ast_ctx)
145-
return lldb::ChildCacheState::eRefetch;
146-
147-
// Create the `resume` and `destroy` children.
148132
lldb::TargetSP target_sp = m_backend.GetTargetSP();
149133
auto &exe_ctx = m_backend.GetExecutionContextRef();
150134
lldb::ProcessSP process_sp = target_sp->GetProcessSP();
151135
auto ptr_size = process_sp->GetAddressByteSize();
152-
CompilerType void_type = ast_ctx->GetBasicType(lldb::eBasicTypeVoid);
153-
std::array<CompilerType, 1> args{void_type};
154-
CompilerType coro_func_type = ast_ctx->CreateFunctionType(
155-
/*result_type=*/void_type, args,
156-
/*is_variadic=*/false, /*qualifiers=*/0);
157-
CompilerType coro_func_ptr_type = coro_func_type.GetPointerType();
158-
m_resume_ptr_sp = CreateValueObjectFromAddress(
159-
"resume", frame_ptr_addr + 0 * ptr_size, exe_ctx, coro_func_ptr_type);
160-
lldbassert(m_resume_ptr_sp);
161-
m_destroy_ptr_sp = CreateValueObjectFromAddress(
162-
"destroy", frame_ptr_addr + 1 * ptr_size, exe_ctx, coro_func_ptr_type);
163-
lldbassert(m_destroy_ptr_sp);
164-
165-
// Get the `promise_type` from the template argument
166-
CompilerType promise_type(
167-
valobj_sp->GetCompilerType().GetTypeTemplateArgument(0));
168-
if (!promise_type)
136+
auto ast_ctx = valobj_sp->GetCompilerType().GetTypeSystem<TypeSystemClang>();
137+
if (!ast_ctx)
169138
return lldb::ChildCacheState::eRefetch;
170139

171-
// Try to infer the promise_type if it was type-erased
140+
// Determine the coroutine frame type and the promise type. Fall back
141+
// to `void`, since even the pointer itself might be useful, even if the
142+
// type inference failed.
143+
Function *destroy_func = ExtractDestroyFunction(target_sp, frame_ptr_addr);
144+
CompilerType void_type = ast_ctx->GetBasicType(lldb::eBasicTypeVoid);
145+
CompilerType promise_type;
146+
if (CompilerType template_argt =
147+
valobj_sp->GetCompilerType().GetTypeTemplateArgument(0))
148+
promise_type = std::move(template_argt);
172149
if (promise_type.IsVoidType()) {
173-
if (Function *destroy_func =
174-
ExtractDestroyFunction(target_sp, frame_ptr_addr)) {
175-
if (CompilerType inferred_type = InferPromiseType(*destroy_func)) {
150+
// Try to infer the promise_type if it was type-erased
151+
if (destroy_func) {
152+
if (CompilerType inferred_type = InferArtificialCoroType(
153+
destroy_func, ConstString("__promise"))) {
176154
promise_type = inferred_type;
177155
}
178156
}
179157
}
158+
CompilerType coro_frame_type =
159+
InferArtificialCoroType(destroy_func, ConstString("__coro_frame"));
160+
if (!coro_frame_type)
161+
coro_frame_type = void_type;
180162

181-
// If we don't know the promise type, we don't display the `promise` member.
182-
// `CreateValueObjectFromAddress` below would fail for `void` types.
183-
if (promise_type.IsVoidType()) {
184-
return lldb::ChildCacheState::eRefetch;
185-
}
186-
187-
// Add the `promise` member. We intentionally add `promise` as a pointer type
188-
// instead of a value type, and don't automatically dereference this pointer.
189-
// We do so to avoid potential very deep recursion in case there is a cycle
190-
// formed between `std::coroutine_handle`s and their promises.
191-
lldb::ValueObjectSP promise = CreateValueObjectFromAddress(
192-
"promise", frame_ptr_addr + 2 * ptr_size, exe_ctx, promise_type);
193-
Status error;
194-
lldb::ValueObjectSP promisePtr = promise->AddressOf(error);
195-
if (error.Success())
196-
m_promise_ptr_sp = promisePtr->Clone(ConstString("promise"));
163+
// Create the `resume` and `destroy` children.
164+
std::array<CompilerType, 1> args{coro_frame_type};
165+
CompilerType coro_func_type = ast_ctx->CreateFunctionType(
166+
/*result_type=*/void_type, args,
167+
/*is_variadic=*/false, /*qualifiers=*/0);
168+
CompilerType coro_func_ptr_type = coro_func_type.GetPointerType();
169+
ValueObjectSP resume_ptr_sp = CreateValueObjectFromAddress(
170+
"resume", frame_ptr_addr + 0 * ptr_size, exe_ctx, coro_func_ptr_type);
171+
lldbassert(resume_ptr_sp);
172+
m_children.push_back(std::move(resume_ptr_sp));
173+
ValueObjectSP destroy_ptr_sp = CreateValueObjectFromAddress(
174+
"destroy", frame_ptr_addr + 1 * ptr_size, exe_ctx, coro_func_ptr_type);
175+
lldbassert(destroy_ptr_sp);
176+
m_children.push_back(std::move(destroy_ptr_sp));
177+
178+
// Add promise and coro_frame
179+
// Add the `promise` and `coro_frame` member. We intentionally add them as
180+
// pointer types instead of a value type, and don't automatically dereference
181+
// those pointers. We do so to avoid potential very deep recursion in case
182+
// there is a cycle formed between `std::coroutine_handle`s and their
183+
// promises.
184+
ValueObjectSP promise_ptr_sp = CreateValueObjectFromAddress(
185+
"promise", frame_ptr_addr + 2 * ptr_size, exe_ctx,
186+
promise_type.GetPointerType(), /*do_deref=*/false);
187+
m_children.push_back(std::move(promise_ptr_sp));
188+
ValueObjectSP coroframe_ptr_sp = CreateValueObjectFromAddress(
189+
"coro_frame", frame_ptr_addr, exe_ctx, coro_frame_type.GetPointerType(),
190+
/*do_deref=*/false);
191+
m_children.push_back(std::move(coroframe_ptr_sp));
197192

198193
return lldb::ChildCacheState::eRefetch;
199194
}
200195

201196
llvm::Expected<size_t>
202197
StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName(
203198
ConstString name) {
204-
if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
205-
return llvm::createStringError("Type has no child named '%s'",
206-
name.AsCString());
207-
208-
if (name == ConstString("resume"))
209-
return 0;
210-
if (name == ConstString("destroy"))
211-
return 1;
212-
if (name == ConstString("promise_ptr") && m_promise_ptr_sp)
213-
return 2;
199+
for (size_t i = 0, limit = m_children.size(); i < limit; ++i) {
200+
if (m_children[i]->GetName() == name) {
201+
return i;
202+
}
203+
}
214204

215205
return llvm::createStringError("Type has no child named '%s'",
216206
name.AsCString());

lldb/source/Plugins/Language/CPlusPlus/Coroutines.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ class StdlibCoroutineHandleSyntheticFrontEnd
4343
llvm::Expected<size_t> GetIndexOfChildWithName(ConstString name) override;
4444

4545
private:
46-
lldb::ValueObjectSP m_resume_ptr_sp;
47-
lldb::ValueObjectSP m_destroy_ptr_sp;
48-
lldb::ValueObjectSP m_promise_ptr_sp;
46+
std::vector<lldb::ValueObjectSP> m_children;
4947
};
5048

5149
SyntheticChildrenFrontEnd *

lldb/test/API/functionalities/data-formatter/data-formatter-stl/generic/coroutine_handle/TestCoroutineHandle.py

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,17 @@ def do_test(self, stdlib_type):
4343
ValueCheck(name="current_value", value="-1"),
4444
],
4545
),
46+
# We don not check any members inside the `coro_frame`,
47+
# as its contents are highly compiler-specific.
48+
ValueCheck(name="coro_frame"),
4649
],
4750
)
51+
52+
# For a type-erased `coroutine_handle<>`, we can still devirtualize
53+
# the promise call and display the correctly typed promise. This
54+
# currently only works in clang, because gcc is not adding the
55+
# artificial `__promise` variable to the destroy function.
4856
if is_clang:
49-
# For a type-erased `coroutine_handle<>`, we can still devirtualize
50-
# the promise call and display the correctly typed promise.
5157
self.expect_expr(
5258
"type_erased_hdl",
5359
result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
@@ -60,23 +66,26 @@ def do_test(self, stdlib_type):
6066
ValueCheck(name="current_value", value="-1"),
6167
],
6268
),
69+
ValueCheck(name="coro_frame"),
6370
],
6471
)
65-
# For an incorrectly typed `coroutine_handle`, we use the user-supplied
66-
# incorrect type instead of inferring the correct type. Strictly speaking,
67-
# incorrectly typed coroutine handles are undefined behavior. However,
68-
# it provides probably a better debugging experience if we display the
69-
# promise as seen by the program instead of fixing this bug based on
70-
# the available debug info.
71-
self.expect_expr(
72-
"incorrectly_typed_hdl",
73-
result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
74-
result_children=[
75-
ValueCheck(name="resume", summary=test_generator_func_ptr_re),
76-
ValueCheck(name="destroy", summary=test_generator_func_ptr_re),
77-
ValueCheck(name="promise", dereference=ValueCheck(value="-1")),
78-
],
79-
)
72+
73+
# For an incorrectly typed `coroutine_handle`, we use the user-supplied
74+
# incorrect type instead of inferring the correct type. Strictly speaking,
75+
# incorrectly typed coroutine handles are undefined behavior. However,
76+
# it provides probably a better debugging experience if we display the
77+
# promise as seen by the program instead of fixing this bug based on
78+
# the available debug info.
79+
self.expect_expr(
80+
"incorrectly_typed_hdl",
81+
result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
82+
result_children=[
83+
ValueCheck(name="resume", summary=test_generator_func_ptr_re),
84+
ValueCheck(name="destroy", summary=test_generator_func_ptr_re),
85+
ValueCheck(name="promise", dereference=ValueCheck(value="-1")),
86+
ValueCheck(name="coro_frame"),
87+
],
88+
)
8089

8190
process = self.process()
8291

@@ -107,6 +116,7 @@ def do_test(self, stdlib_type):
107116
ValueCheck(name="current_value", value="42"),
108117
],
109118
),
119+
ValueCheck(name="coro_frame"),
110120
],
111121
)
112122

@@ -130,6 +140,7 @@ def do_test(self, stdlib_type):
130140
ValueCheck(name="current_value", value="42"),
131141
],
132142
),
143+
ValueCheck(name="coro_frame"),
133144
],
134145
)
135146
if is_clang:
@@ -147,6 +158,7 @@ def do_test(self, stdlib_type):
147158
ValueCheck(name="current_value", value="42"),
148159
],
149160
),
161+
ValueCheck(name="coro_frame"),
150162
],
151163
)
152164

0 commit comments

Comments
 (0)