Skip to content

Commit 4132dcd

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 73c4929 commit 4132dcd

File tree

5 files changed

+95
-99
lines changed

5 files changed

+95
-99
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: 2 additions & 2 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) {
141+
CompilerType type, bool do_deref) {
142142
ValueObjectSP valobj_sp(
143-
ValueObject::CreateValueObjectFromAddress(name, address, exe_ctx, type));
143+
ValueObject::CreateValueObjectFromAddress(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: 62 additions & 76 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;
@@ -62,19 +60,20 @@ static Function *ExtractDestroyFunction(lldb::TargetSP target_sp,
6260
return destroy_func_address.CalculateSymbolContextFunction();
6361
}
6462

65-
static CompilerType InferPromiseType(Function &destroy_func) {
63+
// clang generates aritifical `__promise` and `__coro_frame` variables inside
64+
// the destroy function. Look for those variables and extract their type.
65+
static CompilerType InferArtificialCoroType(Function &destroy_func,
66+
ConstString var_name) {
6667
Block &block = destroy_func.GetBlock(true);
6768
auto variable_list = block.GetBlockVariableList(true);
6869

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

77-
Type *promise_type = promise_var->GetType();
76+
Type *promise_type = var->GetType();
7877
if (!promise_type)
7978
return {};
8079
return promise_type->GetForwardCompilerType();
@@ -108,30 +107,17 @@ lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
108107

109108
llvm::Expected<uint32_t> lldb_private::formatters::
110109
StdlibCoroutineHandleSyntheticFrontEnd::CalculateNumChildren() {
111-
if (!m_resume_ptr_sp || !m_destroy_ptr_sp)
112-
return 0;
113-
114-
return m_promise_ptr_sp ? 3 : 2;
110+
return m_children.size();
115111
}
116112

117113
lldb::ValueObjectSP lldb_private::formatters::
118114
StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(uint32_t idx) {
119-
switch (idx) {
120-
case 0:
121-
return m_resume_ptr_sp;
122-
case 1:
123-
return m_destroy_ptr_sp;
124-
case 2:
125-
return m_promise_ptr_sp;
126-
}
127-
return lldb::ValueObjectSP();
115+
return idx < m_children.size() ? m_children[idx] : lldb::ValueObjectSP();
128116
}
129117

130118
lldb::ChildCacheState
131119
lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::Update() {
132-
m_resume_ptr_sp.reset();
133-
m_destroy_ptr_sp.reset();
134-
m_promise_ptr_sp.reset();
120+
m_children.clear();
135121

136122
ValueObjectSP valobj_sp = m_backend.GetNonSyntheticValue();
137123
if (!valobj_sp)
@@ -141,76 +127,76 @@ lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::Update() {
141127
if (frame_ptr_addr == 0 || frame_ptr_addr == LLDB_INVALID_ADDRESS)
142128
return lldb::ChildCacheState::eRefetch;
143129

144-
auto ast_ctx = valobj_sp->GetCompilerType().GetTypeSystem<TypeSystemClang>();
145-
if (!ast_ctx)
146-
return lldb::ChildCacheState::eRefetch;
147-
148-
// Create the `resume` and `destroy` children.
149130
lldb::TargetSP target_sp = m_backend.GetTargetSP();
150131
auto &exe_ctx = m_backend.GetExecutionContextRef();
151132
lldb::ProcessSP process_sp = target_sp->GetProcessSP();
152133
auto ptr_size = process_sp->GetAddressByteSize();
153-
CompilerType void_type = ast_ctx->GetBasicType(lldb::eBasicTypeVoid);
154-
CompilerType coro_func_type = ast_ctx->CreateFunctionType(
155-
/*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1,
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)
134+
auto ast_ctx = valobj_sp->GetCompilerType().GetTypeSystem<TypeSystemClang>();
135+
if (!ast_ctx)
169136
return lldb::ChildCacheState::eRefetch;
170137

171-
// Try to infer the promise_type if it was type-erased
138+
// Determine the coroutine frame type and the promise type. Fall back
139+
// to `void`, since even the pointer itself might be useful, even if the
140+
// type inference failed.
141+
Function *destroy_func = ExtractDestroyFunction(target_sp, frame_ptr_addr);
142+
CompilerType void_type = ast_ctx->GetBasicType(lldb::eBasicTypeVoid);
143+
CompilerType promise_type;
144+
if (CompilerType template_argt =
145+
valobj_sp->GetCompilerType().GetTypeTemplateArgument(0))
146+
promise_type = std::move(template_argt);
172147
if (promise_type.IsVoidType()) {
173-
if (Function *destroy_func =
174-
ExtractDestroyFunction(target_sp, frame_ptr_addr)) {
175-
if (CompilerType inferred_type = InferPromiseType(*destroy_func)) {
148+
// Try to infer the promise_type if it was type-erased
149+
if (destroy_func) {
150+
if (CompilerType inferred_type = InferArtificialCoroType(
151+
*destroy_func, ConstString("__promise"))) {
176152
promise_type = inferred_type;
177153
}
178154
}
179155
}
156+
CompilerType coro_frame_type =
157+
InferArtificialCoroType(*destroy_func, ConstString("__coro_frame"));
158+
if (!coro_frame_type)
159+
coro_frame_type = void_type;
180160

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"));
161+
// Create the `resume` and `destroy` children.
162+
CompilerType coro_func_type = ast_ctx->CreateFunctionType(
163+
/*result_type=*/void_type, /*args=*/&coro_frame_type, /*num_args=*/1,
164+
/*is_variadic=*/false, /*qualifiers=*/0);
165+
CompilerType coro_func_ptr_type = coro_func_type.GetPointerType();
166+
ValueObjectSP resume_ptr_sp = CreateValueObjectFromAddress(
167+
"resume", frame_ptr_addr + 0 * ptr_size, exe_ctx, coro_func_ptr_type);
168+
lldbassert(resume_ptr_sp);
169+
m_children.push_back(std::move(resume_ptr_sp));
170+
ValueObjectSP destroy_ptr_sp = CreateValueObjectFromAddress(
171+
"destroy", frame_ptr_addr + 1 * ptr_size, exe_ctx, coro_func_ptr_type);
172+
lldbassert(destroy_ptr_sp);
173+
m_children.push_back(std::move(destroy_ptr_sp));
174+
175+
// Add promise and coro_frame
176+
// Add the `promise` and `coro_frame` member. We intentionally add them as
177+
// pointer types instead of a value type, and don't automatically dereference
178+
// those pointers. We do so to avoid potential very deep recursion in case
179+
// there is a cycle formed between `std::coroutine_handle`s and their
180+
// promises.
181+
ValueObjectSP promise_ptr_sp = CreateValueObjectFromAddress(
182+
"promise", frame_ptr_addr + 2 * ptr_size, exe_ctx,
183+
promise_type.GetPointerType(), /*do_deref=*/false);
184+
m_children.push_back(std::move(promise_ptr_sp));
185+
ValueObjectSP coroframe_ptr_sp = CreateValueObjectFromAddress(
186+
"coro_frame", frame_ptr_addr, exe_ctx, coro_frame_type);
187+
m_children.push_back(std::move(coroframe_ptr_sp));
197188

198189
return lldb::ChildCacheState::eRefetch;
199190
}
200191

201192
llvm::Expected<size_t>
202193
StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName(
203194
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;
195+
for (size_t i = 0, limit = m_children.size(); i < limit; ++i) {
196+
if (m_children[i]->GetName() == name) {
197+
return i;
198+
}
199+
}
214200

215201
return llvm::createStringError("Type has no child named '%s'",
216202
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)