feat(build): support nanobind 3, widening the bound to <4 - #87
Merged
Merged
Conversation
Closes #85. The <3 cap added in #84 when nanobind 3.0.0 broke every wheel build was a hold, not a fix. This is the port, and it keeps BOTH majors working rather than trading one for the other: 1374 passed, 3 skipped under each of nanobind 2.15.0 and 3.0.0. nanobind 3 removed the free nb::detail::keep_alive(nurse, patient) and routes the operation through a backend slot needing the extension's context pointer. That pointer IS available to extension code -- NB_CTX is only unavailable inside nanobind's own build, under NB_BUILD -- so the port is a respelling, not a redesign. mtl5_ndarray.cpp spells it once in keep_view_alive(), selecting on NB_VERSION_MAJOR. The obvious simplification is wrong, and now has a test saying so. Replacing these calls with the declarative nb::keep_alive<0, 1> annotation compiles and passes the old suite, but the annotation applies to EVERY return of a .def, while __getitem__, reshape and ravel each return a view on one path and a copy (or a scalar) on another. Pinning a large source array to an independent copy of itself is a memory-retention bug, so the pin stays per-path. Two lifetime-test gaps closed, both of which a broken keep-alive would have survived: - test_a_view_of_an_OWNING_array_keeps_it_alive. The existing lifetime test derives its views from asarray(), where the view also carries the NumPy owner object -- so the buffer survives on that reference alone and the keep-alive is never actually load-bearing. An array from copy() owns its memory with no second owner, which is where dropping the parent is a real use-after-free. Covers all three manual sites. - test_a_copy_does_not_pin_its_source. Asserts the aliasing/copying split directly via refcount: a view increfs its parent (2 -> 3), a copy does not (2 -> 2). This is the test that fails if someone folds the manual calls into the annotation. python/CMakeLists.txt's FetchContent fallback moves v2.15.0 -> v3.0.0 to stay in step with what the bound resolves; #84's lesson was that letting those two drift is how a source change passes locally and fails every CI job. The upper bound stays: nanobind 3.0.0 broke every build the day it released precisely because there was none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
On-demand reviews are free for the next 24 days. After that, they cost $0.25 per reviewed file. Or wait 23 minutes for your next included review. View limit detailsLimit details: You’ve used the included review currently available. Your 74 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #85.
The
<3cap added in #84 — when nanobind 3.0.0 released and broke every wheel build — was a hold, not a fix. This is the port, and it keeps both majors working rather than trading one for the other.Both are clean configure → full build → link → full suite. The link matters: when #85 was filed, the build had never got past the failing object, so link and runtime compatibility were explicitly unverified. They are now.
The change
nanobind 3 removed the free
nb::detail::keep_alive(nurse, patient)and routes the operation through a backend slot that must be handed the extension's context pointer. That pointer is available to extension code —NB_CTXis only unavailable inside nanobind's own build, underNB_BUILD— so this is a respelling, not a redesign.mtl5_ndarray.cppnow spells it once, inkeep_view_alive(), selecting onNB_VERSION_MAJOR. That's why the bound moves to<4instead of jumping to>=3.The obvious simplification is wrong
Worth stating plainly, because the compiler suggests it and a reviewer will think it:
That compiles, and it passes the suite as it existed. It is still wrong. The annotation applies to every return of a
.def, and all three sites return a view on one path and a copy — or, for a fully-integer index, a scalar — on another:__getitem__reshaperavelPinning a large source array to an independent copy of itself is a memory-retention bug. The pin has to stay per-path, so the manual call stays.
Two lifetime-test gaps, closed
Both would have survived a broken keep-alive, which is why the port needed them:
test_a_view_of_an_OWNING_array_keeps_it_alive— the existing lifetime test derives its views fromasarray(), where the view also carries the NumPy owner object. The buffer survives on that reference alone, so the keep-alive is never load-bearing there. An array fromcopy()owns its memory with no second owner — that is the configuration where dropping the parent is a genuine use-after-free. Covers all three manual sites.test_a_copy_does_not_pin_its_source— asserts the aliasing/copying split directly, via refcount:This is the test that fails if someone later folds the manual calls into the annotation.
Version alignment
python/CMakeLists.txt's FetchContent fallback movesv2.15.0→v3.0.0to stay in step with what the bound resolves. #84's lesson was that letting those two drift is exactly how a source change passes locally and fails every CI job.The upper bound stays. nanobind 3.0.0 broke every build the day it released precisely because there wasn't one.
🤖 Generated with Claude Code