Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LLDB] FindLibCppStdFunctionCallableInfo improvements #111892

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

mentlerd
Copy link

Work in progress attempt at fixing #111291. Looking for maintainer feedback/direction from @Michael137

Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together! Looks like non-trivial amount of investigation.

On the first pass, I think this seems like a reasonable (and more maintainable) alternative to what we're currently doing.

Using the TypeSystem to fish out the various std::function details aligns with what we also do for the other libc++ data formatters.

Ultimately it would be nice to achieve the step-through-to-callable in a more compiler/thread-plan oriented way. But that's out of scope of this in my opinion, given we already have FindLibCppStdFunctionCallableInfo.

A high-level comment: could we split up FindLibCppStdFunctionCallableInfo? Exactly how I haven't fully thought through. But possibly a function for each of the callable cases?

I'll do a second pass of this early next week

// pointers taking up two pointers of space. Perhaps that's why it is not legal
// to read their underlying value raw?

printf("Curious!");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, yes, it's represented as a pointer and possible offset: https://itanium-cxx-abi.github.io/cxx-abi/abi.html#member-function-pointers

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the ABI agnostic way of extracting the underlying function pointer? Or is it fine to assume that the target is using the Itanium ABI?

@@ -191,14 +191,6 @@ llvm::Error ItaniumABILanguageRuntime::TypeHasVTable(CompilerType type) {
type = pointee_type;
}

// Make sure this is a class or a struct first by checking the type class
// bitfield that gets returned.
if ((type.GetTypeClass() & (eTypeClassStruct | eTypeClassClass)) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate on the removal here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might have overgeneralized here a bit - TypeSystemClang::IsPolymorphicClass already checks these conditions, and seems to handle situations better where CompilerType is a typedef to a struct/class instead of being one itself.

Comment on lines 277 to 287
// class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)> {
// __compressed_pair<_Fp, _Ap> __f_;
// }
//
// class __compressed_pair : __compressed_pair_elem<_T1, 0>,
// __compressed_pair_elem<_T2, 1> {
// }
//
// struct __compressed_pair_elem {
// _Tp __value_;
// }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, this is not the compressed pair representation of libc++ anymore. Nothing really that would affect the code here, but the comment could use an update.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am developing this using the SDK shipped with Xcode 16 - that's where I mostly intend to use the improvements too. Perhaps it isn't worth putting library code excerpts into comments if they are doomed to get out of sync eventually?

Comment on lines 296 to 301
if (callable_type.IsRecordType() && callable_type.GetNumFields() == 0) {
// callable_type is an empty class, and has been optimized away! Serve a dummy
callable = valobj_sp->CreateValueObjectFromAddress("__value_",
pair->GetLoadAddress(),
pair->GetExecutionContextRef(),
callable_type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean optimized away here? What situation do we land here in? I assume this is not optimized code you're talking about. Why do 0 fields indicate such situation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what you were seeing actually is that libc++ removed the __compressed_pair structure. So __f_ gets you exactly the callable, not a wrapper template anymore. See e.g., what we did for the other data-formatters to support the new layout: #96538

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code snippets above are from the SDK shipped with Xcode 16. There __compressed_pair_elem has a specialization for storing classes which are completely empty which is an empty class.

In that case empty base class elision kicks in, and leaves me with a __compressed_pair with no base classes and no fields.

Eventually I would like to make a synthetic children provider for std::function which would show the wrapped object. I figured even if it is empty it would be worthy to show a dummy of it so the UI can immediately show the underlying C++ type as well.


As far as I am aware Apple's libc++ implementation is slightly different than the one on this repo. What is the policy for such situations? Are you OK with me also supporting their layout or is that something more fitting of contributing to https://github.com/swiftlang/llvm-project?

@mentlerd
Copy link
Author

A high-level comment: could we split up FindLibCppStdFunctionCallableInfo? Exactly how I haven't fully thought through. But possibly a function for each of the callable cases?

Sure! I think a natural split could be to make a version which returns a (possibly dummy) ValueObjectSP of wrapped callable in combination with a CompilerType of the function pointer type describing the kind of function this std::function implements.

Other code then can either proceed to extracting the load address from the ValueObjectSP that we might land in when the std::function is called, or to just serve the ValueObjectSP as synthetic children.


One open question that I have been thinking about is how to handle callable types with multiple operator()(...) function overloads. My thought process so far is that trying to figure out which overload std::function would end up calling is futile effort, instead we could try giving the thread plan all potential addresses.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants