Traversing member types of a type

I’m trying to write formatters for Clang, and I ran into an issue that SBType doesn’t provide API to traverse member types of a type. This is driven by llvm::PointerIntPairInfo, where I’d like to reuse bit masks computed at compile time (e.g. PointerIntMask), in order to avoid duplication of logic in formatter implementation.

I don’t mind preparing the patches, but I’d like code owners to give me a green light on the direction, and also provide me with clear guidance on how user-facing APIs should look like, in order to avoid major redesign later. As someone new to LLDB codebase, I wonder, for instance, if SBTypeList kind of APIs is what LLDB does nowadays, or GetNumberOfX() + GetXAtIndex() is still preferred.

In a bigger picture, I’d also like to know how important LLDB users among Clang contributors are for LLDB.

Not sure I understand the question.

One way I could interpret it is, how welcome are changes to lldb that support scenarios that only appear when debugging clang, or programs built with clang. I think the answer would be, very welcome. Given that I assume the vast majority of toolchains that use lldb also use clang.

If that change was incredibly specific to clang’s behaviour then there would be some debate whether it’s appropriate to change lldb or fix clang. For example I have come across workarounds for older clangs that produced incomplete debug information, where it was presumably too late to fix those. But if clang is following all the relevant standards, let’s support it in lldb.

The other way to interpret it might be - should lldb be the best way to debug clang? Putting aside personal taste in tools, on a technical level, yes it should. If you can’t use llvm’s debugger to debug its flagship project, that’s a bad look.

@mib / @JDevlieghere as the (currently proposed) owners of Python and Lua API to answer the API design question.

Sorry for not making it clear, but I was interested in both ways, so thank you for your answer.

What I had in my mind are scenarios like this one, when I can imagine LLDB position be “we intentionally don’t wast time and memory on parsing compile-time constants from debug info, because we don’t deem them useful”, while it makes Clang (and LLVM) formatters to be more brittle and harder to implement.

What I don’t imply is to force you to go out of your way for the sake of just Clang and LLVM, because I’d expect those cases to warrant a fix in Clang or LLVM codebases themselves. I also don’t imply supporting debug info coming from an older Clang. Workflow on my mind is the following: get latest release or nightly build of Clang and LLDB, build Clang with that (possibly with -glldb), and debug with LLDB. I believe this workflow should provide an excellent debugging experience. Today it’s very much bare bone, because of bit-packed pointers.

I think both approaches are fine: If the container you’re trying to implement can do special processing on its element (i.e. SBBreakpointList::FindBreakpointByID) then it makes sense to introduce a new SB container type. However, if you just want to pull discret data from another SB type, implementing a GetNumOfX + GetXAtIndex sounds easier. For the latter, you could also return an SBStructuredData for instance, and make your API more generic rather than tying it to a specific use case.

Keep in mind that LLDB’s SB API is both API and ABI stable, so you need to be careful adding new methods and classes to it, since we can’t remove them or change their definition them after the fact.

I’d be happy to take a look at your patch once it goes for review :slight_smile:

I have started with ⚙ D156774 [lldb] Parse enums while parsing a type

I’m confused…

lldb currently has SBType::GetNumberOfFields/SBType::GetFieldAtIndex If you are inspecting a C-struct that should be all you need to iterate over the fields of a type. The GetFieldAtIndex API returns the field as an SBTypeMember, which holds the name, bit & byte offset & SBType of the field. So then you use the SBType and the above API’s to descend into the fields.

If you are in C++ you also need to traverse the base classes, for which you would use SBType::GetNumberOf{Direct,Virtual}BaseClasses and SBType::Get{Direct,Virtual}BaseClassAtIndex. There are also API’s to get member functions, though I don’t think you would need that for data formatters.

What is missing?

I also don’t imply supporting debug info coming from an older Clang

We do try to support this. See LLDB Matrix [Jenkins]

I believe this workflow should provide an excellent debugging experience. Today it’s very much bare bone, because of bit-packed pointers.

Would definitely be great to get formatters for PointerIntPair to work. We recently removed them because the tools to implement them were lacking: ⚙ D155219 [llvm][utils] Disable lldb formatters for PointerIntPair and PointerUnion.

You probably are already aware, but in the meantime, if you’re debugging clang you should be able to run expr your_pointer.getInt() or expr your_pointer.getPointer()

Member types. Off the top of my head: type aliases, enums, nested classes, nested class templates.
As I pointed out in the original post, llvm::PointerIntPairInfo uses enums for named compile-time bitmasks computed at compile-time. Given an SBType for PointerIntPairInfo, I don’t see a way to get to MaskAndShiftConstants enum, and values inside. (I’ll share my thoughts on expression evaluator in the next post.)

Thank you very much, because I missed that previous implementation attempt! Even though it looks nice, using expression evaluator in a formatter doesn’t lead to responsive user experience, as far as I saw in my own attempts to use it. That’s why I’m looking into improving type introspection facilities, so that formatters can get the data required, and do a bit of compute themselves.

There’s SBType::GetEnumMembers to get enumeration members, represented by the SBTypeEnumMemberList. Did that not work for your use case?

Not sure what you want with type aliases. You can look them up with SBTarget::FindType and then use SBType::GetTypedefType to get the referenced type. There isn’t a Target GetTypes API, so if you want to do things like “find me all the typedef’s in some context” you’ll have to iterate over modules calling GetTypes with the type class of eTypeClassTypedef and then filtering after the fact for “in this class” or whatever.

We don’t have a “list all the nested types in a class” API, though again you could build it out of GetTypes. I wouldn’t do that as a type system API, however since it’s really the same query for “types in namespaces” or any other scope.

I can see that would be useful if you are trying to write some kind of type dumper using lldb. But I can’t see how that’s necessary for writing formatters. You aren’t generally doing “list all the nested types till I find the one that looks right” you already know the one you are looking for, and in that case FindTypes will find the SBType by fully scoped name for you.

If you want to build some nicer queries on top of FindTypes, that would be a good addition, particularly for writing type dumpers. The simplest API would be to extend SBTarget::FindTypes that took a context (namespace, class, function, etc) and a TypeClass. I think that would allow you to find all the various nested entities if you know their context. There no natural ordering for the results of this sort of query, so returning them in an SBTypeList would be fine.

I ran into a similar issue while trying to implement a synthetic children provider for absl::flat_hash_map.

To do so I would need to obtain a typedef from a policy template argument. Obtaining the policy SBType is quite straightforward, but I don’t see how I could get to the policy’s typedefs:

template<> struct FlatHashMapPolicy<int, int> {
    typedef int key_type;
    typedef std::pair<int, int> init_type;
    typedef absl::container_internal::map_slot_policy<int, int>::slot_type slot_type;
    static size_t space_used(const absl::container_internal::FlatHashMapPolicy<int, int>::slot_type *);
    static std::pair<const int, int> &element(absl::container_internal::FlatHashMapPolicy<int, int>::slot_type *);
    static int &value(std::pair<const int, int> *);
    static const int &value(const std::pair<const int, int> *);
}

(This is the result of printing the SBType in the REPL, so the information appears to be readily available)

I tried following the path of just enumerating all typedefs within the defining module, but I don’t see a way to correlate typedefs found this way to a particular SBType that declared them.

1 Like

To do so I would need to obtain a typedef from a policy template argument. Obtaining the policy SBType is quite straightforward, but I don’t see how I could get to the policy’s typedefs:

Does FindDirectNestedType('key_type') not work for you? That API was added to SBType as a result of this thread a few months back: [lldb] Add SBType::FindDirectNestedType() function by Endilll · Pull Request #68705 · llvm/llvm-project · GitHub

If that doesn’t work, could you provide the error you’re seeing?

This looks precisely what I am looking for. Thanks! Is the function so new that it has not made it’s way into a release, thus it isn’t part of the Python reference either?

Unfortunately for my usecase this won’t work until the code is upstreamed into LLDB bundled in the Xcode toolchain.

For the time being I have managed to obtain the type in question by finding a member function which returns a pointer to slot_type, so the immediate problem was solved in a way that is compatible with the IDE :slight_smile:

I’ll try to test the new API sometime with a custom lldb build to see whether I could get rid of the workaround.