Skip to content

Emit direct pybind callable pointers with full-signature casts - #197

Open
dellaert wants to merge 3 commits into
masterfrom
codex/pybind-direct-function-pointers
Open

Emit direct pybind callable pointers with full-signature casts#197
dellaert wants to merge 3 commits into
masterfrom
codex/pybind-direct-function-pointers

Conversation

@dellaert

Copy link
Copy Markdown
Member

Summary

  • replace unnecessary forwarding lambdas with direct C++ callable pointers
  • emit explicit full-signature casts for ordinary instance, const, static, and free functions
  • add @pybind_lambda as an explicit escape hatch for wrapper signatures that intentionally differ from C++ declarations
  • preserve automatic adapter lambdas, policies, defaults, docstrings, and template behavior
  • document the migration workflow and add a repository-local annotation skill

Motivation

Bare callable addresses and py::overload_cast cannot select an exact declaration when a C++ header contains overloads omitted from the wrapper interface. Full-signature casts retain genuine function/member-function pointers while reliably selecting the interface-declared signature. Intentional wrapper/C++ signature mismatches continue to use @pybind_lambda.

Validation

Previously run on this branch before push:

  • conda run -n py312 python -m pytest tests/test_pybind_wrapper.py — 18 passed
  • conda run -n py312 python -m pytest tests — 123 passed
  • repository-local skill validation — passed
  • git diff --check — passed

No tests or builds were run while creating this PR.

Copy link
Copy Markdown
Member Author

Downstream GTSAM audit

I audited this wrapper against a full GTSAM migration, regenerating the same 21 modules from develop and from the cleanup branch.

Metric develop Cleanup branch
Generated lambda expressions 6,070 1,637
Exact full-signature casts 0 3,817
Explicitly audited adapters 611 initially 27 remaining
Eigen::Ref occurrences in .i 168 0
OptionalJacobian occurrences 0 399

That is a 73.0% reduction in generated lambdas and a 95.6% reduction in declarations requiring explicit adapter annotations. The migration also consolidated reduced-arity/defaulted families such as ISAM2::update, FixedLagSmoother::update, LinearContainerFactor, and graph elimination APIs, while moving inherited methods onto their actual wrapped bases.

The remaining 27 adapters are accounted for: 14 function-template calls, four ref-qualified members, six real Python/STL conversions, and three inheritance/overload-registration blockers.

Validation on the downstream branch: wrapper suite 125 passed, stable and unstable Python extensions built, 61 focused Python tests passed, and both SmartProjection C++ test targets passed.

The GTSAM integration also contains a small follow-up beyond this PR for type-qualifying {} defaults and substituting scoped template types; those were needed by exact downstream signatures.

Copy link
Copy Markdown
Member Author

Downstream compile-time and binary-size audit

I built the audited GTSAM develop commit (80a7073dbb) and cleanup branch (e2471b50d8) in clean, identical Release configurations: Clang 22.1.7, Python 3.12, Ninja -j6, ccache disabled, stable+unstable Python enabled.

For the isolated wrapper rebuild, I touched the same 20 generated wrapper translation units in each already-built tree, then recompiled and relinked both Python modules:

Measurement develop Cleanup branch Change
Wrapper rebuild wall time 238.84 s 182.84 s −23.4%
Aggregate Ninja TU compile time 1,023.64 s 823.07 s −19.6%
Longest wrapper TU 193.33 s 144.42 s −25.3%

Stripped Python module sizes:

Module develop Cleanup branch Change
Stable gtsam 30.40 MiB 29.78 MiB −2.05%
gtsam_unstable 3.48 MiB 2.38 MiB −31.54%
Combined 33.88 MiB 32.16 MiB −5.07%

The clean end-to-end stable+unstable build was 683.39 s vs 406.94 s (−40.5%), but that number also includes native GTSAM differences, so the wrapper-only result above is the more relevant comparison. Both resulting module pairs imported successfully.

Caveat: this is an end-to-end downstream migration result, combining direct callable pointers with the accompanying exact-signature/interface consolidation. It does not attribute the whole gain to pointer emission alone. The unstable size reduction is also strongly affected by removal of the deprecated PoseRTV wrapper.

@dellaert
dellaert marked this pull request as ready for review August 15, 2026 16:24
@ProfFan

ProfFan commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

I don't really like the @pybind_lambda thingy (I have a local branch with codex that also have this kind of setup). I need to spend some time looking at wrap and think...

@dellaert

Copy link
Copy Markdown
Member Author

@ProfFan I'm not in love with it either, but I spent a lot of time in tokens to reduce the number of lambdas needed to a very small number, like 27 or so. Some of that work is simply making the wrapper better. So it's not lost work, per se. But this solution definitely does work.

@dellaert

Copy link
Copy Markdown
Member Author

Explicitly audited adapters | 611 initially ==> 27 remaining

@ProfFan

ProfFan commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Let me also spin some experiments

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