-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
[smart_holder] fix smart_holder regressions with multiple inheritance #3635
Conversation
2f5ac26
to
3c287cf
Compare
Thanks a lot Dustin! It looks complicated, I'll need to carve out some time to dig into this. Do you see a way to reduce the new test, based on your analysis? That would make it easier for me to get my head around the problem. But I'll look into this regardless, asap. |
Looking at the rest results, it seems that you only need to keep When the issue is finally resolved, I think it would be good to keep the whole test. |
I believe I have a fix for the issue, using my original intuition stated above. This creates a std::vector of implicit casters that is appended to during the recursive type caster walk, and once it's time to do the actual cast it calls them in order. I don't really like adding the std::vector to the type caster, but it's simplest? However, it's getting allocated every time we create a type caster, so it's probably a bad idea. |
5c10569
to
d9ad43c
Compare
Awesome, thanks! I can only quickly look right now (late night here). It looks good at first glance. I don't think the overhead matters much (we could run ubench/holder_comparison.cpp to get basic timings), and maybe we can find tricks or compromises to minimize the overhead. I'll play with this as soon as I get a chance; it's at the top of the pile. |
67b55bc
to
a4755b7
Compare
@@ -251,7 +253,7 @@ class modified_type_caster_generic_load_impl { | |||
const std::type_info *cpptype = nullptr; | |||
void *unowned_void_ptr_from_direct_conversion = nullptr; | |||
const std::type_info *loaded_v_h_cpptype = nullptr; | |||
void *(*implicit_cast)(void *) = nullptr; | |||
std::vector<void *(*)(void *)> implicit_casts; | |||
value_and_holder loaded_v_h; | |||
bool reinterpret_cast_deemed_ok = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the safety guard value change due to this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the safety guard value change due to this change?
I'm thinking not. The safety guard was meant to be a very targeted sanity check, not a version check.
I've ~neglected versioning of the smart_holder internals because Google-internally we're basically building everything from sources, for each test or executable independently & hermetically. But for more safety externally, It's probably a good idea to give smart_holder internal versioning a little more thought; in a separate PR.
I'll rerun the ubench timings asap. Not sure if I get a large-enough time window over the weekend, but Monday almost certainly.
Hi Dustin, some observations and questions:
|
tests/test_multiple_inheritance.cpp
Outdated
.def_readwrite("e", &MVE::e); | ||
// TODO: py::multiple_inheritance is required here, but pybind11 should | ||
// be able to detect this by looking at MVE which is already bound... | ||
py::class_<MVF, MVE>(m, "MVF", py::multiple_inheritance()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, thinking about this. Is this indicating a more subtle bug with the smart holder? Why does the type_record only register a single base in rec.bases?
pybind11/include/pybind11/pybind11.h
Line 1206 in d434b5f
if (rec.bases.size() > 1 || rec.multiple_inheritance) { |
pybind11/include/pybind11/pybind11.h
Line 1210 in d434b5f
else if (rec.bases.size() == 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It records that it has a single base because MVF only has a single immediate base. To get the correct behavior it would need to examine all of its bases in turn for the multiple inheritance flag.
Actually, if it only examined its immediate bases for the flag, that would fix it, because the flag would be propagated through all derived classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That does fix it, I'll open another PR (#3650) to have a discussion about it.
Thanks Ralf, I'll split the PR as suggested later today/tonight. |
Perfect. The global testing came back unusually noisy, but after scrolling up and down through the failures for a while I'm thinking this PR didn't cause any failures. (I'll probably retry after we're through the splitting steps, hoping for cleaner results.) |
bb06cf5
to
9e6ce49
Compare
I'm not sure how to best handle the situation that the tests are on master already. There must be a way, but how? My usual procedure is:
But if I do that now the smart_holder branch will have many broken tests. @virtuald would it work if you did this?
Then me doing this?
This wouldn't squash your commits, but I think that's better than us having to coordinate. |
Oh ... face palm.
|
The original pybind11 holder supported multiple inheritance by recursively creating type casters until it finds one for the source type, then converting each value in turn to the next type via typeinfo->implicit_cast The smart_holder only stored the last implicit_cast, which was incorrect. This commit changes it to create a list of implicit_cast functions that are appended to during the recursive type caster creation, and when the time comes to cast to the destination type, it calls all of them in the correct order.
9e6ce49
to
a6c47cc
Compare
Just saw your note. I removed the tests. |
As soon as I see that the CI is happy I'll merge. The one failure at this moment is a known flake, good to ignore. |
Thanks a lot Dustin! Merging now. |
Description
The
smart_holder
does not handle multiple inheritance correctly, and this test demonstrates that. The tests work fine with normal holders.Investigation so far
I believe I've identified the source of the problem, but I don't have any good ideas for a fix at the present moment (@rwgk if you have ideas but not time to implement them, let me know and I can give it a shot).
My gdb investigation was done using this python code:
Lots of stuff happens, and at some point
modified_type_caster_generic_load_impl::load_impl
gets called (smart_holder_type_casters.h:162
). This eventually callstry_implicit_casts
, which recursively tries to convert to the correct type.If you compare to the original type caster, the modified smart holder caster does basically the same logic, with a key difference:
This is bad because at some point
smart_holder_type_caster_load::convert_type
on line 525 calls the implicit_castfunction from the
modified_type_caster_generic_load_impl
.The implicit_cast function is a lambda from
pybind11.h:1489
If I'm interpreting my GDB correctly, Base is
MVB
and type isMVC
when it's called in this case.Which is not what it should be doing. I think (right? since src isn't a MVC, it's an MVF?).
What it probably should be doing is recursively calling the implicit cast function on the value, converting MVF to MVE, MVE to MVD0 .. etc, just like the original caster did. Or maybe it should also be storing the converted value? It's not 100% clear to me why it's different here, but I'm guessing it has something to do with ownership.