> On Mar 16, 2016, at 3:34 PM, Zachary Turner <[email protected]> wrote:
>
> Hi Greg,
>
> could you clarify the difference between the functions ParseTypes,
SymbolFile::ParseTypes() is only used for debugging, don't worry about this one
unless you need a way to say "parse all types in the supplied context". It can
help you debug things. It is only called by Module::ParseAllDebugSymbols() and
this is only used for debugging and it isn't exposed anywhere or used by
anyone. Just used for initial implementation. Again, see
Module::ParseAllDebugSymbols() for all the details, but not one calls
Module::ParseAllDebugSymbols().
> FindTypes
FindType is for finding types by name:
virtual uint32_t
FindTypes (const SymbolContext& sc,
const ConstString &name,
const CompilerDeclContext *parent_decl_ctx,
bool append,
uint32_t max_matches,
llvm::DenseSet<lldb_private::SymbolFile *> &searched_symbol_files,
TypeMap& types);
The symbol context can limit the scope of your search if needed. Why? Because
you might be stopped inside a "test" module that has 100 compile units, and you
are stopped in compile unit "foo.cpp" in function "bar()". The symbol context
can specify a scope in which to search. If a function or block is specified,
you should find the any types in the the block, if you don't find one, proceed
to the parent block. Keep going up the blocks, then search the class if "bar()"
is a method, then search the compile unit for any types that match, and then
fall back to the module. It is usually easiest to just lookup the type and come
up with N results, and pare down the results after you find them as most times
you will only find one type so there is no need.
If "parent_decl_ctx" is not NULL, you take all results that made it through the
"sc" filter and then filter out any results that are not in this
"parent_decl_ctx". The expression parser often says "I am looking for
"basic_string" in the parent_decl_ctx that represents "namespace std".
You always check to see if "this" is in the searched_symbol_files set and only
search if you aren't. This is for module debugging where DWARF files refer to
types from other DWARF files.
And there is one that is specific to module debugging:
virtual size_t FindTypes (const std::vector<CompilerContext>
&context, bool append, TypeMap& types);
You don't need to implement this one unless you have one PDB file that refers
to a type in another...
> ResolveTypeUID
Often types you might have a variable whose type id "typedef FooType ...;".
Because of this, you can give your lldb_private::Variable a lldb_private::Type
that says "I am a typedef named 'FooType' to the type with UID 0x1234. We don't
need to resolve the type just yet and if no one needs to explore the type, we
don't need to resolve the type right away. If anyone does ask for the
CompilerType from a lldb_private::Type, we can then do a
"m_symfile->ResolveTypeUID(m_encoding_uid);". So it allows us to just say "your
type is a type from a symbol file with a user ID of 0x1234" and we can resolve
that lazily if any only if we ever need to. Another instance where this is
useful is when you create a lldb_private::Function:
Function (CompileUnit *comp_unit,
lldb::user_id_t func_uid,
lldb::user_id_t func_type_uid,
const Mangled &mangled,
Type * func_type,
const AddressRange& range);
Note that you don't need to parse the function type up front, you can just make
a function with:
SymbolFilePDB::ParseFunction(...)
{
lldb::user_id_t func_uid = 0x1234;
Mangled mangled("_Z3foo", true);
AddressRange func_range = ...;
Function *func = new Function(m_cu, func_uid, func_uid, mangled, nullptr,
func_range);
}
Later if anyone calls:
Type* Function::GetType();
You can see if can lazily resolve its function type using ResolveTypeUID:
Type*
Function::GetType()
{
if (m_type == nullptr)
{
SymbolContext sc;
CalculateSymbolContext (&sc);
if (!sc.module_sp)
return nullptr;
SymbolVendor *sym_vendor = sc.module_sp->GetSymbolVendor();
if (sym_vendor == nullptr)
return nullptr;
SymbolFile *sym_file = sym_vendor->GetSymbolFile();
if (sym_file == nullptr)
return nullptr;
m_type = sym_file->ResolveTypeUID(m_type_uid);
}
return m_type;
}
> , and CompleteType
virtual bool SymbolFile::CompleteType (CompilerType &compiler_type);
This allows you to say "I have a class A type that is very complex and until
someone needs to know the details about this class I will have the type
represented as 'class A;'. Once someone needs to know the details they can
lazily complete the type. This is very useful when creating types in
clang::ASTContext objects because we can make a forward declaration that knows
how to complete itself when you hook into the clang::ExternalASTSource. This is
some of the factoring you have already been doing. If you always just want make
all of your types completely when you hand them out, then you don't need to do
this. But since clang types already have all the hooks needed to lazily
complete types, we take advantage of this. This way you can hand an expression
a "class A;" as the type for a variable, and if clang ever needs to know about
the details inside of A, the clang::ExternalASTSource hooks will allow the type
to lazily complete itself _only_ if ever needed. So if you have an expression
like "A* a = GetA(); printf("%p\n", a)", clang never need to know what is
inside of A so it never asks us to complete it. But if you do "A* a = ...;
a->DoSomething()", clang will ask us to expand the type. Another example is
imagine you have a local variable in your frame whose type is "A* a;". If we
display the type in the debugger's variable view, we know this is a pointer and
unless someone clicks on the disclosure triangle to expand the type, we don't
need to know what is inside of "A". Variable display is done by using
CompilerType methods like:
CompilerType t;
const bool omit_empty_base_classes = true;
uint32_t num_children = t.GetNumChildren (omit_empty_base_classes);
for (i in num_children)
{
CompilerType child_type = t.GetChildCompilerTypeAtIndex (exe_ctx, i, ...);
}
When we call CompilerType::GetNumChildren(), we call into
ClangASTContext::GetNumChildren(...), which knows we have clang types and it
also knows that if the type isn't complete and it can complete itself, that we
can complete the type:
uint32_t
ClangASTContext::GetNumChildren (lldb::opaque_compiler_type_t type, bool
omit_empty_base_classes)
{
uint32_t num_children = 0;
clang::QualType qual_type(GetQualType(type));
const clang::Type::TypeClass type_class = qual_type->getTypeClass();
switch (type_class)
{
case clang::Type::Record:
if (GetCompleteQualType (getASTContext(), qual_type))
{
Note that we are smart and only try to complete types that we know can be
complete, like Record types for clang. See the function GetCompleteQualType().
So the key is here: both LLDB code and the actual clang compiler itself as it
is compiling expressions has the ability to deal with incomplete types and
complete them only when necessary.
> from the SymbolFile plugin?
Let me know if you have any questions.
Greg
_______________________________________________
lldb-dev mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev