xref: /llvm-project/lldb/source/Plugins/Language/CPlusPlus/Coroutines.cpp (revision f6d4e687172ab14728c569a0c1d3eb1c76021250)
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/ExpressionParser/Clang/ClangASTImporter.h"
12 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
13 #include "lldb/Symbol/Function.h"
14 #include "lldb/Symbol/VariableList.h"
15 
16 using namespace lldb;
17 using namespace lldb_private;
18 using namespace lldb_private::formatters;
19 
20 static ValueObjectSP GetCoroFramePtrFromHandle(ValueObject &valobj) {
21   ValueObjectSP valobj_sp(valobj.GetNonSyntheticValue());
22   if (!valobj_sp)
23     return nullptr;
24 
25   // We expect a single pointer in the `coroutine_handle` class.
26   // We don't care about its name.
27   if (valobj_sp->GetNumChildren() != 1)
28     return nullptr;
29   ValueObjectSP ptr_sp(valobj_sp->GetChildAtIndex(0, true));
30   if (!ptr_sp)
31     return nullptr;
32   if (!ptr_sp->GetCompilerType().IsPointerType())
33     return nullptr;
34 
35   return ptr_sp;
36 }
37 
38 static Function *ExtractFunction(ValueObjectSP &frame_ptr_sp, int offset) {
39   lldb::TargetSP target_sp = frame_ptr_sp->GetTargetSP();
40   lldb::ProcessSP process_sp = frame_ptr_sp->GetProcessSP();
41   auto ptr_size = process_sp->GetAddressByteSize();
42 
43   AddressType addr_type;
44   lldb::addr_t frame_ptr_addr = frame_ptr_sp->GetPointerValue(&addr_type);
45   if (!frame_ptr_addr || frame_ptr_addr == LLDB_INVALID_ADDRESS)
46     return nullptr;
47   lldbassert(addr_type == AddressType::eAddressTypeLoad);
48 
49   Status error;
50   auto func_ptr_addr = frame_ptr_addr + offset * ptr_size;
51   lldb::addr_t func_addr =
52       process_sp->ReadPointerFromMemory(func_ptr_addr, error);
53   if (error.Fail())
54     return nullptr;
55 
56   Address func_address;
57   if (!target_sp->ResolveLoadAddress(func_addr, func_address))
58     return nullptr;
59 
60   return func_address.CalculateSymbolContextFunction();
61 }
62 
63 static Function *ExtractResumeFunction(ValueObjectSP &frame_ptr_sp) {
64   return ExtractFunction(frame_ptr_sp, 0);
65 }
66 
67 static Function *ExtractDestroyFunction(ValueObjectSP &frame_ptr_sp) {
68   return ExtractFunction(frame_ptr_sp, 1);
69 }
70 
71 static bool IsNoopCoroFunction(Function *f) {
72   if (!f)
73     return false;
74 
75   // clang's `__builtin_coro_noop` gets lowered to
76   // `_NoopCoro_ResumeDestroy`. This is used by libc++
77   // on clang.
78   auto mangledName = f->GetMangled().GetMangledName();
79   if (mangledName == "__NoopCoro_ResumeDestroy")
80     return true;
81 
82   // libc++ uses the following name as a fallback on
83   // compilers without `__builtin_coro_noop`.
84   auto name = f->GetNameNoArguments();
85   static RegularExpression libcxxRegex(
86       "^std::coroutine_handle<std::noop_coroutine_promise>::"
87       "__noop_coroutine_frame_ty_::__dummy_resume_destroy_func$");
88   lldbassert(libcxxRegex.IsValid());
89   if (libcxxRegex.Execute(name.GetStringRef()))
90     return true;
91   static RegularExpression libcxxRegexAbiNS(
92       "^std::__[[:alnum:]]+::coroutine_handle<std::__[[:alnum:]]+::"
93       "noop_coroutine_promise>::__noop_coroutine_frame_ty_::"
94       "__dummy_resume_destroy_func$");
95   lldbassert(libcxxRegexAbiNS.IsValid());
96   if (libcxxRegexAbiNS.Execute(name.GetStringRef()))
97     return true;
98 
99   // libstdc++ uses the following name on both gcc and clang.
100   static RegularExpression libstdcppRegex(
101       "^std::__[[:alnum:]]+::coroutine_handle<std::__[[:alnum:]]+::"
102       "noop_coroutine_promise>::__frame::__dummy_resume_destroy$");
103   lldbassert(libstdcppRegex.IsValid());
104   if (libstdcppRegex.Execute(name.GetStringRef()))
105     return true;
106 
107   return false;
108 }
109 
110 static CompilerType InferPromiseType(Function &destroy_func) {
111   Block &block = destroy_func.GetBlock(true);
112   auto variable_list = block.GetBlockVariableList(true);
113 
114   // clang generates an artificial `__promise` variable inside the
115   // `destroy` function. Look for it.
116   auto promise_var = variable_list->FindVariable(ConstString("__promise"));
117   if (!promise_var)
118     return {};
119   if (!promise_var->IsArtificial())
120     return {};
121 
122   Type *promise_type = promise_var->GetType();
123   if (!promise_type)
124     return {};
125   return promise_type->GetForwardCompilerType();
126 }
127 
128 static CompilerType GetCoroutineFrameType(TypeSystemClang &ast_ctx,
129                                           CompilerType promise_type) {
130   CompilerType void_type = ast_ctx.GetBasicType(lldb::eBasicTypeVoid);
131   CompilerType coro_func_type = ast_ctx.CreateFunctionType(
132       /*result_type=*/void_type, /*args=*/&void_type, /*num_args=*/1,
133       /*is_variadic=*/false, /*qualifiers=*/0);
134   CompilerType coro_abi_type;
135   if (promise_type.IsVoidType()) {
136     coro_abi_type = ast_ctx.CreateStructForIdentifier(
137         ConstString(), {{"resume", coro_func_type.GetPointerType()},
138                         {"destroy", coro_func_type.GetPointerType()}});
139   } else {
140     coro_abi_type = ast_ctx.CreateStructForIdentifier(
141         ConstString(), {{"resume", coro_func_type.GetPointerType()},
142                         {"destroy", coro_func_type.GetPointerType()},
143                         {"promise", promise_type}});
144   }
145   return coro_abi_type;
146 }
147 
148 bool lldb_private::formatters::StdlibCoroutineHandleSummaryProvider(
149     ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
150   ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(valobj));
151   if (!ptr_sp)
152     return false;
153 
154   if (!ptr_sp->GetValueAsUnsigned(0)) {
155     stream << "nullptr";
156     return true;
157   }
158   if (IsNoopCoroFunction(ExtractResumeFunction(ptr_sp)) &&
159       IsNoopCoroFunction(ExtractDestroyFunction(ptr_sp))) {
160     stream << "noop_coroutine";
161     return true;
162   }
163 
164   stream.Printf("coro frame = 0x%" PRIx64, ptr_sp->GetValueAsUnsigned(0));
165   return true;
166 }
167 
168 lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
169     StdlibCoroutineHandleSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
170     : SyntheticChildrenFrontEnd(*valobj_sp),
171       m_ast_importer(std::make_unique<ClangASTImporter>()) {
172   if (valobj_sp)
173     Update();
174 }
175 
176 lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
177     ~StdlibCoroutineHandleSyntheticFrontEnd() = default;
178 
179 size_t lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
180     CalculateNumChildren() {
181   if (!m_frame_ptr_sp)
182     return 0;
183 
184   return m_frame_ptr_sp->GetNumChildren();
185 }
186 
187 lldb::ValueObjectSP lldb_private::formatters::
188     StdlibCoroutineHandleSyntheticFrontEnd::GetChildAtIndex(size_t idx) {
189   if (!m_frame_ptr_sp)
190     return lldb::ValueObjectSP();
191 
192   return m_frame_ptr_sp->GetChildAtIndex(idx, true);
193 }
194 
195 bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
196     Update() {
197   m_frame_ptr_sp.reset();
198 
199   ValueObjectSP valobj_sp = m_backend.GetSP();
200   if (!valobj_sp)
201     return false;
202 
203   ValueObjectSP ptr_sp(GetCoroFramePtrFromHandle(m_backend));
204   if (!ptr_sp)
205     return false;
206 
207   Function *resume_func = ExtractResumeFunction(ptr_sp);
208   Function *destroy_func = ExtractDestroyFunction(ptr_sp);
209 
210   if (IsNoopCoroFunction(resume_func) && IsNoopCoroFunction(destroy_func)) {
211     // For `std::noop_coroutine()`, we don't want to display any child nodes.
212     return false;
213   }
214 
215   // Get the `promise_type` from the template argument
216   CompilerType promise_type(
217       valobj_sp->GetCompilerType().GetTypeTemplateArgument(0));
218   if (!promise_type)
219     return false;
220 
221   // Try to infer the promise_type if it was type-erased
222   auto ts = valobj_sp->GetCompilerType().GetTypeSystem();
223   auto ast_ctx = ts.dyn_cast_or_null<TypeSystemClang>();
224   if (!ast_ctx)
225     return false;
226   if (promise_type.IsVoidType() && destroy_func) {
227     if (CompilerType inferred_type = InferPromiseType(*destroy_func)) {
228       // Copy the type over to the correct `TypeSystemClang` instance
229       promise_type = m_ast_importer->CopyType(*ast_ctx, inferred_type);
230     }
231   }
232 
233   // Build the coroutine frame type
234   CompilerType coro_frame_type = GetCoroutineFrameType(*ast_ctx, promise_type);
235 
236   m_frame_ptr_sp = ptr_sp->Cast(coro_frame_type.GetPointerType());
237 
238   return false;
239 }
240 
241 bool lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEnd::
242     MightHaveChildren() {
243   return true;
244 }
245 
246 size_t StdlibCoroutineHandleSyntheticFrontEnd::GetIndexOfChildWithName(
247     ConstString name) {
248   if (!m_frame_ptr_sp)
249     return UINT32_MAX;
250 
251   return m_frame_ptr_sp->GetIndexOfChildWithName(name);
252 }
253 
254 SyntheticChildrenFrontEnd *
255 lldb_private::formatters::StdlibCoroutineHandleSyntheticFrontEndCreator(
256     CXXSyntheticChildren *, lldb::ValueObjectSP valobj_sp) {
257   return (valobj_sp ? new StdlibCoroutineHandleSyntheticFrontEnd(valobj_sp)
258                     : nullptr);
259 }
260