1 | //===-- Coroutines.cpp ----------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "Coroutines.h" |
10 | |
11 | #include "Plugins/TypeSystem/Clang/TypeSystemClang.h" |
12 | #include "lldb/Symbol/Function.h" |
13 | #include "lldb/Symbol/VariableList.h" |
14 | #include "lldb/Utility/LLDBLog.h" |
15 | #include "lldb/Utility/Log.h" |
16 | |
17 | using namespace lldb; |
18 | using namespace lldb_private; |
19 | using namespace lldb_private::formatters; |
20 | |
21 | static lldb::addr_t GetCoroFramePtrFromHandle(ValueObjectSP valobj_sp) { |
22 | if (!valobj_sp) |
23 | return LLDB_INVALID_ADDRESS; |
24 | |
25 | // We expect a single pointer in the `coroutine_handle` class. |
26 | // We don't care about its name. |
27 | if (valobj_sp->GetNumChildrenIgnoringErrors() != 1) |
28 | return LLDB_INVALID_ADDRESS; |
29 | ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(idx: 0)); |
30 | if (!ptr_sp) |
31 | return LLDB_INVALID_ADDRESS; |
32 | if (!ptr_sp->GetCompilerType().IsPointerType()) |
33 | return LLDB_INVALID_ADDRESS; |
34 | |
35 | AddressType addr_type; |
36 | lldb::addr_t frame_ptr_addr = ptr_sp->GetPointerValue(address_type: &addr_type); |
37 | if (!frame_ptr_addr || frame_ptr_addr == LLDB_INVALID_ADDRESS) |
38 | return LLDB_INVALID_ADDRESS; |
39 | lldbassert(addr_type == AddressType::eAddressTypeLoad); |
40 | if (addr_type != AddressType::eAddressTypeLoad) |
41 | return LLDB_INVALID_ADDRESS; |
42 | |
43 | return frame_ptr_addr; |
44 | } |
45 | |
46 | static Function *(lldb::TargetSP target_sp, |
47 | lldb::addr_t frame_ptr_addr) { |
48 | lldb::ProcessSP process_sp = target_sp->GetProcessSP(); |
49 | auto ptr_size = process_sp->GetAddressByteSize(); |
50 | |
51 | Status error; |
52 | auto destroy_func_ptr_addr = frame_ptr_addr + ptr_size; |
53 | lldb::addr_t destroy_func_addr = |
54 | process_sp->ReadPointerFromMemory(vm_addr: destroy_func_ptr_addr, error); |
55 | if (error.Fail()) |
56 | return nullptr; |
57 | |
58 | Address destroy_func_address; |
59 | if (!target_sp->ResolveLoadAddress(load_addr: destroy_func_addr, so_addr&: destroy_func_address)) |
60 | return nullptr; |
61 | |
62 | return destroy_func_address.CalculateSymbolContextFunction(); |
63 | } |
64 | |
65 | static CompilerType InferPromiseType(Function &destroy_func) { |
66 | Block &block = destroy_func.GetBlock(can_create: true); |
67 | auto variable_list = block.GetBlockVariableList(can_create: true); |
68 | |
69 | // clang generates an artificial `__promise` variable inside the |
70 | // `destroy` function. Look for it. |
71 | auto promise_var = variable_list->FindVariable(name: ConstString("__promise" )); |
72 | if (!promise_var) |
73 | return {}; |
74 | if (!promise_var->IsArtificial()) |
75 | return {}; |
76 | |
77 | Type *promise_type = promise_var->GetType(); |
78 | if (!promise_type) |
79 | return {}; |
80 | return promise_type->GetForwardCompilerType(); |
81 | } |
82 | |
83 | bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider( |
84 | ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) { |
85 | lldb::addr_t frame_ptr_addr = |
86 | GetCoroFramePtrFromHandle(valobj_sp: valobj.GetNonSyntheticValue()); |
87 | if (frame_ptr_addr == LLDB_INVALID_ADDRESS) |
88 | return false; |
89 | |
90 | if (frame_ptr_addr == 0) { |
91 | stream << "nullptr" ; |
92 | } else { |
93 | stream.Printf(format: "coro frame = 0x%" PRIx64, frame_ptr_addr); |
94 | } |
95 | |
96 | return true; |
97 | } |
98 | |
99 | lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: |
100 | StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp) |
101 | : SyntheticChildrenFrontEnd(*valobj_sp) { |
102 | if (valobj_sp) |
103 | Update(); |
104 | } |
105 | |
106 | lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: |
107 | ~StdlibCoroutineHandleSyntheticFrontEnd() = default; |
108 | |
109 | llvm::Expected<uint32_t> lldb_private::formatters:: |
110 | StdlibCoroutineHandleSyntheticFrontEnd::CalculateNumChildren() { |
111 | if (!m_resume_ptr_sp || !m_destroy_ptr_sp) |
112 | return 0; |
113 | |
114 | return m_promise_ptr_sp ? 3 : 2; |
115 | } |
116 | |
117 | lldb::ValueObjectSP lldb_private::formatters:: |
118 | 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(); |
128 | } |
129 | |
130 | lldb::ChildCacheState |
131 | lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::Update() { |
132 | m_resume_ptr_sp.reset(); |
133 | m_destroy_ptr_sp.reset(); |
134 | m_promise_ptr_sp.reset(); |
135 | |
136 | ValueObjectSP valobj_sp = m_backend.GetNonSyntheticValue(); |
137 | if (!valobj_sp) |
138 | return lldb::ChildCacheState::eRefetch; |
139 | |
140 | lldb::addr_t frame_ptr_addr = GetCoroFramePtrFromHandle(valobj_sp); |
141 | if (frame_ptr_addr == 0 || frame_ptr_addr == LLDB_INVALID_ADDRESS) |
142 | return lldb::ChildCacheState::eRefetch; |
143 | |
144 | auto ts = valobj_sp->GetCompilerType().GetTypeSystem(); |
145 | auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>(); |
146 | if (!ast_ctx) |
147 | return lldb::ChildCacheState::eRefetch; |
148 | |
149 | // Create the `resume` and `destroy` children. |
150 | lldb::TargetSP target_sp = m_backend.GetTargetSP(); |
151 | auto &exe_ctx = m_backend.GetExecutionContextRef(); |
152 | lldb::ProcessSP process_sp = target_sp->GetProcessSP(); |
153 | auto ptr_size = process_sp->GetAddressByteSize(); |
154 | CompilerType void_type = ast_ctx->GetBasicType(type: lldb::eBasicTypeVoid); |
155 | CompilerType coro_func_type = ast_ctx->CreateFunctionType( |
156 | /*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1, |
157 | /*is_variadic=*/false, /*qualifiers=*/type_quals: 0); |
158 | CompilerType coro_func_ptr_type = coro_func_type.GetPointerType(); |
159 | m_resume_ptr_sp = CreateValueObjectFromAddress( |
160 | name: "resume" , address: frame_ptr_addr + 0 * ptr_size, exe_ctx, type: coro_func_ptr_type); |
161 | lldbassert(m_resume_ptr_sp); |
162 | m_destroy_ptr_sp = CreateValueObjectFromAddress( |
163 | name: "destroy" , address: frame_ptr_addr + 1 * ptr_size, exe_ctx, type: coro_func_ptr_type); |
164 | lldbassert(m_destroy_ptr_sp); |
165 | |
166 | // Get the `promise_type` from the template argument |
167 | CompilerType promise_type( |
168 | valobj_sp->GetCompilerType().GetTypeTemplateArgument(idx: 0)); |
169 | if (!promise_type) |
170 | return lldb::ChildCacheState::eRefetch; |
171 | |
172 | // Try to infer the promise_type if it was type-erased |
173 | if (promise_type.IsVoidType()) { |
174 | if (Function *destroy_func = |
175 | ExtractDestroyFunction(target_sp, frame_ptr_addr)) { |
176 | if (CompilerType inferred_type = InferPromiseType(destroy_func&: *destroy_func)) { |
177 | promise_type = inferred_type; |
178 | } |
179 | } |
180 | } |
181 | |
182 | // If we don't know the promise type, we don't display the `promise` member. |
183 | // `CreateValueObjectFromAddress` below would fail for `void` types. |
184 | if (promise_type.IsVoidType()) { |
185 | return lldb::ChildCacheState::eRefetch; |
186 | } |
187 | |
188 | // Add the `promise` member. We intentionally add `promise` as a pointer type |
189 | // instead of a value type, and don't automatically dereference this pointer. |
190 | // We do so to avoid potential very deep recursion in case there is a cycle |
191 | // formed between `std::coroutine_handle`s and their promises. |
192 | lldb::ValueObjectSP promise = CreateValueObjectFromAddress( |
193 | name: "promise" , address: frame_ptr_addr + 2 * ptr_size, exe_ctx, type: promise_type); |
194 | Status error; |
195 | lldb::ValueObjectSP promisePtr = promise->AddressOf(error); |
196 | if (error.Success()) |
197 | m_promise_ptr_sp = promisePtr->Clone(new_name: ConstString("promise" )); |
198 | |
199 | return lldb::ChildCacheState::eRefetch; |
200 | } |
201 | |
202 | bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd:: |
203 | MightHaveChildren() { |
204 | return true; |
205 | } |
206 | |
207 | size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName( |
208 | ConstString name) { |
209 | if (!m_resume_ptr_sp || !m_destroy_ptr_sp) |
210 | return UINT32_MAX; |
211 | |
212 | if (name == ConstString("resume" )) |
213 | return 0; |
214 | if (name == ConstString("destroy" )) |
215 | return 1; |
216 | if (name == ConstString("promise_ptr" ) && m_promise_ptr_sp) |
217 | return 2; |
218 | |
219 | return UINT32_MAX; |
220 | } |
221 | |
222 | SyntheticChildrenFrontEnd * |
223 | lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator( |
224 | CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) { |
225 | return (valobj_sp ? new StdlibCoroutineHandleSyntheticFrontEnd(valobj_sp) |
226 | : nullptr); |
227 | } |
228 | |