Skip to content

Commit 4a46ead

Browse files
authored
[lldb] Show coro_frame in std::coroutine_handle pretty printer (#141516)
This commit adjusts the pretty printer for `std::coroutine_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 coroutine handles have the same frame / promise pointers.
1 parent 953a778 commit 4a46ead

File tree

5 files changed

+101
-102
lines changed

5 files changed

+101
-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: 67 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,77 @@ 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_arg =
147+
valobj_sp->GetCompilerType().GetTypeTemplateArgument(0))
148+
promise_type = std::move(template_arg);
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 =
153+
InferArtificialCoroType(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+
assert(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+
assert(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 (const auto &[idx, child_sp] : llvm::enumerate(m_children)) {
200+
if (child_sp->GetName() == name)
201+
return idx;
202+
}
214203

215204
return llvm::createStringError("Type has no child named '%s'",
216205
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
@@ -46,11 +46,17 @@ def do_test(self, stdlib_type):
4646
ValueCheck(name="current_value", value="-1"),
4747
],
4848
),
49+
# We don not check any members inside the `coro_frame`,
50+
# as its contents are highly compiler-specific.
51+
ValueCheck(name="coro_frame"),
4952
],
5053
)
54+
55+
# For a type-erased `coroutine_handle<>`, we can still devirtualize
56+
# the promise call and display the correctly typed promise. This
57+
# currently only works in clang, because gcc is not adding the
58+
# artificial `__promise` variable to the destroy function.
5159
if is_clang:
52-
# For a type-erased `coroutine_handle<>`, we can still devirtualize
53-
# the promise call and display the correctly typed promise.
5460
self.expect_expr(
5561
"type_erased_hdl",
5662
result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
@@ -63,23 +69,26 @@ def do_test(self, stdlib_type):
6369
ValueCheck(name="current_value", value="-1"),
6470
],
6571
),
72+
ValueCheck(name="coro_frame"),
6673
],
6774
)
68-
# For an incorrectly typed `coroutine_handle`, we use the user-supplied
69-
# incorrect type instead of inferring the correct type. Strictly speaking,
70-
# incorrectly typed coroutine handles are undefined behavior. However,
71-
# it provides probably a better debugging experience if we display the
72-
# promise as seen by the program instead of fixing this bug based on
73-
# the available debug info.
74-
self.expect_expr(
75-
"incorrectly_typed_hdl",
76-
result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
77-
result_children=[
78-
ValueCheck(name="resume", summary=test_generator_func_ptr_re),
79-
ValueCheck(name="destroy", summary=test_generator_func_ptr_re),
80-
ValueCheck(name="promise", dereference=ValueCheck(value="-1")),
81-
],
82-
)
75+
76+
# For an incorrectly typed `coroutine_handle`, we use the user-supplied
77+
# incorrect type instead of inferring the correct type. Strictly speaking,
78+
# incorrectly typed coroutine handles are undefined behavior. However,
79+
# it provides probably a better debugging experience if we display the
80+
# promise as seen by the program instead of fixing this bug based on
81+
# the available debug info.
82+
self.expect_expr(
83+
"incorrectly_typed_hdl",
84+
result_summary=re.compile("^coro frame = 0x[0-9a-f]*$"),
85+
result_children=[
86+
ValueCheck(name="resume", summary=test_generator_func_ptr_re),
87+
ValueCheck(name="destroy", summary=test_generator_func_ptr_re),
88+
ValueCheck(name="promise", dereference=ValueCheck(value="-1")),
89+
ValueCheck(name="coro_frame"),
90+
],
91+
)
8392

8493
process = self.process()
8594

@@ -110,6 +119,7 @@ def do_test(self, stdlib_type):
110119
ValueCheck(name="current_value", value="42"),
111120
],
112121
),
122+
ValueCheck(name="coro_frame"),
113123
],
114124
)
115125

@@ -133,6 +143,7 @@ def do_test(self, stdlib_type):
133143
ValueCheck(name="current_value", value="42"),
134144
],
135145
),
146+
ValueCheck(name="coro_frame"),
136147
],
137148
)
138149
if is_clang:
@@ -150,6 +161,7 @@ def do_test(self, stdlib_type):
150161
ValueCheck(name="current_value", value="42"),
151162
],
152163
),
164+
ValueCheck(name="coro_frame"),
153165
],
154166
)
155167

0 commit comments

Comments
 (0)