-
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. |
…major and/or influential contributors to smart_holder branch * pybind#2904 by @rhaschke was merged on Mar 16, 2021 * pybind#3012 by @rhaschke was merged on May 28, 2021 * pybind#3039 by @jakobandersen was merged on Jun 29, 2021 * pybind#3048 by @Skylion007 was merged on Jun 18, 2021 * pybind#3588 by @virtuald was merged on Jan 3, 2022 * pybind#3633 by @wangxf123456 was merged on Jan 25, 2022 * pybind#3635 by @virtuald was merged on Jan 26, 2022 * pybind#3645 by @wangxf123456 was merged on Jan 25, 2022 * pybind#3796 by @wangxf123456 was merged on Mar 10, 2022 * pybind#3807 by @wangxf123456 was merged on Mar 18, 2022 * pybind#3838 by @wangxf123456 was merged on Apr 15, 2022 * pybind#3929 by @tomba was merged on May 7, 2022 * pybind#4031 by @wangxf123456 was merged on Jun 27, 2022 * pybind#4343 by @wangxf123456 was merged on Nov 18, 2022 * pybind#4381 by @wangxf123456 was merged on Dec 5, 2022 * pybind#4539 by @wangxf123456 was merged on Feb 28, 2023 * pybind#4609 by @wangxf123456 was merged on Apr 6, 2023 * pybind#4775 by @wangxf123456 was merged on Aug 3, 2023 * pybind#4921 by @iwanders was merged on Nov 7, 2023 * pybind#4924 by @iwanders was merged on Nov 6, 2023 * pybind#5401 by @msimacek was merged on Oct 8, 2024 Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com> Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com> Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com> Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk> Co-authored-by: Michael Šimáček <michael.simacek@oracle.com> Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com> Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi> Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com>
* Pure `git merge --squash smart_holder` (no manual interventions). * Remove ubench/ directory. * Remove include/pybind11/smart_holder.h * [ci skip] smart_ptrs.rst updates [WIP/unfinished] * [ci skip] smart_ptrs.rst updates continued; also updating classes.rst, advanced/classes.rst * Remove README_smart_holder.rst * Restore original README.rst from master * [ci skip] Minimal change to README.rst, to leave a hint that this is pybind11v3 * [ci skip] Work in ChatGPT suggestions. * Change macro name to PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE * Add a note pointing to the holder reinterpret_cast. * Incorporate suggestion by @virtuald: #5542 (comment) * Systematically change most py::class_ to py::classh under docs/ * Remove references to README_smart_holder.rst This should have been part of commit eb550d0. * [ci skip] Fix minor oversight (``class_`` -> ``py::class_``) noticed by chance. * [ci skip] Resolve suggestion by @virtuald #5542 (comment) * [ci skip] Apply suggestions by @timohl (thanks!) * #5542 (comment) * #5542 (comment) * #5542 (comment) * Replace `classh : class_` inhertance with `using`, as suggested by @henryiii #5542 (comment) * Revert "Systematically change most py::class_ to py::classh under docs/" This reverts commit ac9d31e. * docs: focus on py::smart_holder instead of py::classh Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * Restore minor general fixes that got lost when ac9d31e was reverted. * Remove `- smart_holder` from list of branches in all .github/workflows * Extend classh note to explain whitespace noise motivation. * Suggest `py::smart_holder` for "most situations for safety" * Add back PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT This define was * introduced with #5286 * removed with #5531 It is has been in use here: * https://github.com/pybind/pybind11_protobuf/blob/f02a2b7653bc50eb5119d125842a3870db95d251/pybind11_protobuf/native_proto_caster.h#L89-L101 Currently pybind11 unit tests for the two holder caster backwards compatibility traits * `copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled` * `move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled` are missing. * Add py::trampoline_self_life_support to all trampoline examples under docs/. Address suggestion by @timohl: * #5542 (comment) Add to the "please think twice" note: the overhead for safety is likely in the noise. Also fix a two-fold inconsistency introduced by revert-commit 1e646c9: 1. py::trampoline_self_life_support is mentioned in a note, but is missing in the example right before. 2. The section starting with To enable safely passing a ``std::unique_ptr`` to a trampoline object between is obsolete. * Fix whitespace accident (indentation) introduced with 1e646c9 Apparently the mis-indentation was introduced when resolving merge conflicts for what became 1e646c9 * WHITESPACE CHANGES ONLY in README.rst (list of people that made significant contributions) * Add Ethan Steinberg to list of people that made significant contributions (for completeness, unrelated to smart_holder work). * [ci skip] Add to list of people that made significant contributions: major and/or influential contributors to smart_holder branch * #2904 by @rhaschke was merged on Mar 16, 2021 * #3012 by @rhaschke was merged on May 28, 2021 * #3039 by @jakobandersen was merged on Jun 29, 2021 * #3048 by @Skylion007 was merged on Jun 18, 2021 * #3588 by @virtuald was merged on Jan 3, 2022 * #3633 by @wangxf123456 was merged on Jan 25, 2022 * #3635 by @virtuald was merged on Jan 26, 2022 * #3645 by @wangxf123456 was merged on Jan 25, 2022 * #3796 by @wangxf123456 was merged on Mar 10, 2022 * #3807 by @wangxf123456 was merged on Mar 18, 2022 * #3838 by @wangxf123456 was merged on Apr 15, 2022 * #3929 by @tomba was merged on May 7, 2022 * #4031 by @wangxf123456 was merged on Jun 27, 2022 * #4343 by @wangxf123456 was merged on Nov 18, 2022 * #4381 by @wangxf123456 was merged on Dec 5, 2022 * #4539 by @wangxf123456 was merged on Feb 28, 2023 * #4609 by @wangxf123456 was merged on Apr 6, 2023 * #4775 by @wangxf123456 was merged on Aug 3, 2023 * #4921 by @iwanders was merged on Nov 7, 2023 * #4924 by @iwanders was merged on Nov 6, 2023 * #5401 by @msimacek was merged on Oct 8, 2024 Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com> Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com> Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com> Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk> Co-authored-by: Michael Šimáček <michael.simacek@oracle.com> Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com> Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi> Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com> --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com> Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com> Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com> Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk> Co-authored-by: Michael Šimáček <michael.simacek@oracle.com> Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com> Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi> Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com>
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.