Skip to content

COMP: Make ITKVtkGlue wrapping abi3-compatible - #6715

Open
hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:abi3-vtkglue
Open

COMP: Make ITKVtkGlue wrapping abi3-compatible#6715
hjmjohnson wants to merge 1 commit into
InsightSoftwareConsortium:mainfrom
hjmjohnson:abi3-vtkglue

Conversation

@hjmjohnson

@hjmjohnson hjmjohnson commented Jul 27, 2026

Copy link
Copy Markdown
Member

Makes ITKVtkGlue's Python wrapping stable-ABI clean, so it can be built with ITK_USE_PYTHON_LIMITED_API=ON — the configuration its FATAL_ERROR previously rejected. Closes #6711.

Scope is deliberately limited to Modules/Bridge/VtkGlue/. No CMake option, Python discovery, or wrapping-macro behaviour changes here; whether ITK should default to abi3 is separate work, tracked in #6757 along with free-threading and abi3t.

Because Limited API calls are also valid in a full-API build, one implementation serves both modes — the module does not become abi3-only, and the FATAL_ERROR is removed rather than inverted.

Verified on macOS 26 / arm64 only. No Linux or Windows verification, and no Slicer test result.

The fix, and a defect in the approach #6711 proposed

VtkGlue.i exchanged pointers with VTK through vtkPythonUtil, whose header chain reaches PyVTKObject.h and touches PyTypeObject members that Py_LIMITED_API hides. The typemaps now use Limited API calls only, parsing VTK's __this__ encoding and reconstructing through Addr=0x....

VTK::WrappingPythonCore is dropped from the wrapping link interface. This is a correctness requirement rather than tidying: that library is built against one libpython, so an extension linking it cannot be version-agnostic.

Defect in the sketch from #6711. The issue proposed PyObject_CallFunction(cls, "s", addr), i.e. vtkImageData("Addr=0x…"). On VTK 9.6.2 this fails, but only for vtkImageData and vtkPolyData, and only in the pointer-to-object direction. The cause is not ABI-related: vtkmodules/util/data_model.py registers @vtkImageData.override with a Python subclass whose __init__(self, **kwargs) is keyword-only, so the positional address string never reaches VTK's C-level reconstruction. vtkImageExport, vtkImageImport, vtkObject and vtkPoints have no override and were unaffected.

The fix is to call __new__ explicitly: it bypasses the override's __init__ while still returning the enhanced ImageData type, with pointer identity preserved.

Neither __this__ nor Addr=0x... is documented VTK API, so PythonVtkGlueABI3EncodingTest exists to fail loudly and specifically if VTK changes either one. PythonVtkGlueRoundTripTest exercises the typemaps end to end.

Environments verified (and what was not)

Platform: macOS 26 / arm64, Apple clang via conda-forge toolchain.
VTK: github.com/slicer/VTK at 6181bb1223bbc499a340a1644f5356e7e152c318 — the SHA Slicer's SuperBuild/External_VTK.cmake pins for its 9.6 series — built with Qt6 6.9.1, full rendering, and Python wrapping.

Both wrapping modes, built and tested separately

Check ITK_USE_PYTHON_LIMITED_API=ON =OFF
Build 8565/8565, 0 FAILED: 8565/8565, 0 FAILED:
ctest -R Python 177/177 177/177
PythonVtkGlueABI3EncodingTest passed passed
PythonVtkGlueRoundTripTest passed passed
Module suffix _ITKVtkGluePython.abi3.so _ITKVtkGluePython.so

pre-commit run --all-files: exit 0.

Artifact audit (limited-API mode)

Property Value
_ITKVtkGluePython.abi3.so undefined Py* symbols 71, all Limited API
PyVTK* / vtkPythonUtil symbols 0
libvtkWrappingPythonCore linked 0
libpython linked none

Multi-interpreter. Extensions were built against pixi Python 3.13.9; the others are Homebrew builds, a different distribution. VTK's Python wrappers were built separately for cp311/cp312/cp313/cp314 against the same VTK C++ libraries, so each row pairs the same ITK .abi3.so with a different version-specific VTK.

Interpreter abi3 module loads Filter result VtkGlue round-trip
3.11.15 yes 7.0 OK
3.12.13 yes 7.0 OK
3.13.9 (build interpreter) yes 7.0 OK
3.14.6 yes 7.0 OK

The 3.11 and 3.14 rows are the substantive ones: that .abi3.so was compiled against cp313 VTK headers yet drove full round-trips against cp311 and cp314 VTK modules. While VTK::WrappingPythonCore was linked, those pairings were structurally impossible.

Downstream

  • Standalone ITK with ITK_WRAP_PYTHON=OFF, Module_ITKVtkGlue=ON: 2730/2730 targets, 0 errors.
  • Slicer: its ITK external project built successfully against this branch, together with ~20 other external projects. No Slicer tests ran — the Slicer build is blocked in GDCM by a conda/system library shadowing problem in the local test environment, independent of this branch (the diff is wrapping-only and Slicer's ITK is ITK_WRAP_PYTHON=OFF).

Not verified: Linux, Windows, any non-arm64 target, and any completed Slicer test suite.

@github-actions github-actions Bot added type:Compiler Compiler support or related warnings type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots area:Python wrapping Python bindings for a class type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct area:Bridge Issues affecting the Bridge module area:Core Issues affecting the Core module area:Documentation Issues affecting the Documentation module labels Jul 27, 2026

@dzenanz dzenanz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good on a glance. Matt and Brad should review this.

Comment thread Modules/Bridge/VtkGlue/wrapping/VtkGlue.i
@hjmjohnson

Copy link
Copy Markdown
Member Author

Force-push a8b614a04430bd48d4d0b: content-only fix for the ITK.macOS.Python failure (no rebase mixed in — the branch was already current with main).

libITKCommon failed to link with undefined _Py_BuildValue / _Py_Dealloc. Fixed by folding into the commit that introduced it.

Root cause

Modules/Core/Common/src/CMakeLists.txt has two uses of _itk_python_target: the INCLUDE_DIRECTORIES source property, and target_link_libraries(ITKCommon PRIVATE ${_itk_python_target}) about 100 lines further down. The commit that replaced the variable with Python3::SABIModule updated only the first, leaving the second to expand to an empty string. ITKCommon then stopped linking Python3::SABIModule and lost its INTERFACE_LINK_OPTIONS, i.e. -undefined dynamic_lookup.

It failed on exactly one configuration because three things have to line up: an undefined CMake variable expands to empty without error, ELF shared objects tolerate undefined symbols, and static archives never resolve them at all. Only a shared macOS build links libITKCommon.dylib strictly enough to notice — hence green Linux Python (177 tests) and green CDash while macOS Python failed at 764/5404 with 0 tests run.

Verified by configuring main and this branch side by side with BUILD_SHARED_LIBS=ON: main emits 98 dynamic_lookup link options, the broken branch 97, and the fixed branch 98 again, with the missing one being libITKCommon. libITKCommon-6.0.1.dylib now links, and nm -u shows _Py_BuildValue and _Py_Dealloc present as permitted undefined symbols.

@hjmjohnson
hjmjohnson requested review from blowekamp and thewtex July 27, 2026 12:05
@hjmjohnson
hjmjohnson marked this pull request as ready for review July 27, 2026 12:27
@greptile-apps

This comment was marked as resolved.

Comment thread Modules/Bridge/VtkGlue/wrapping/VtkGlue.i
@hjmjohnson
hjmjohnson force-pushed the abi3-vtkglue branch 2 times, most recently from c046803 to d542b02 Compare July 27, 2026 13:47
@hjmjohnson

Copy link
Copy Markdown
Member Author

Two force-pushes just landed, split by concern: 33b3a662354 → c046803a5c7 is a plain rebase on main (nothing else), and c046803a5c7 → d542b02554f is content only — the fix for Greptile's P1 unvalidated-pointer finding. The second compare link shows just the two VtkGlue files.

@blowekamp

Copy link
Copy Markdown
Member

What are the implications of this for building against the Python Free Thread ABI. Currently Free Threads are not stable and there is expected to be a separate stable ABI for 3.15:
https://peps.python.org/pep-0803/

@dzenanz

dzenanz commented Jul 28, 2026

Copy link
Copy Markdown
Member

I read this, and the requirements for abi3t are abi3 + no GIL locking. So this PR is a step in the right direction, as best as I can tell.

@hjmjohnson

Copy link
Copy Markdown
Member Author

I do think this is a mandatory step toward abi3t, but I am not an expert on those details. The primary motivator was to remove complexity in the cmake options, and remove unnecessary dependance on non-abi3 code.

@thewtex thewtex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think we necessarily want to remove support for building without abi3. Linux distributions, etc. may want/need to not use abi3.

@thewtex

thewtex commented Aug 4, 2026

Copy link
Copy Markdown
Member

Yes, abi3t will need to be a separate build.

Exchange pointers with VTK's Python layer through the `__this__` and
`Addr=0x...` encodings using only Limited API calls, instead of through
vtkPythonUtil, whose header chain accesses PyTypeObject members that
Py_LIMITED_API hides.

Dropping VTK::WrappingPythonCore from the wrapping link interface is
required for correctness, not tidiness: that library is built against one
libpython, so an extension linking it cannot be version-agnostic.

Neither encoding is documented VTK API, so PythonVtkGlueABI3EncodingTest
asserts both still hold and PythonVtkGlueRoundTripTest exercises the
typemaps end to end.

Closes: InsightSoftwareConsortium#6711
@blowekamp

Copy link
Copy Markdown
Member

I don't think we necessarily want to remove support for building without abi3. Linux distributions, etc. may want/need to not use abi3.

I agree with this. For example for the condo-forge distribution they are still build per python distribution. It is unclear if the SWIG generated code utilizes the extra available interfaces or not in these configurations.

@github-actions github-actions Bot removed area:Core Issues affecting the Core module area:Documentation Issues affecting the Documentation module labels Aug 5, 2026
@hjmjohnson

hjmjohnson commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

@thewtex @blowekamp — thanks both; your comments changed the shape of this PR.

It is now only the ITKVtkGlue fix: one commit, seven files, all under Modules/Bridge/VtkGlue/. ITK_USE_PYTHON_LIMITED_API is untouched, keeps its current default, and no Python-discovery or wrapping-macro behaviour changes here. The earlier revision removed that option; that is withdrawn.

The ABI questions you raised are tracked separately in #6757 — free-threaded CPython, abi3t, whether ITK should default to abi3, and alternative binding generators. Python 3.15 makes the actions much more clear about what to do, so it is a roadmap, not a work item.

Because Limited API calls are also valid in a full-API build, one implementation serves both modes — the module does not become abi3-only, and the FATAL_ERROR is removed rather than inverted. Verified on macOS arm64 with VTK 9.6: both ITK_USE_PYTHON_LIMITED_API=ON and =OFF build 8565/8565 targets and pass 177/177 ctest -R Python.

conda-forge: abi3 is compatible with per-version builds, and does not require them to collapse

Checked against conda-forge's own recipe machinery rather than assumed.

conda-forge supports abi3 explicitly, gated per recipe on an is_abi3 selector. When a feedstock opts in, recipe/example-meta.yaml in python-abi3-feedstock sets:

python_version_independent: true   # [is_abi3]
skip: True                         # [is_abi3 and not is_python_min]

plus a python-abi3 host dependency and an abi3audit test. That produces one version-independent artifact built at python_min, instead of one per Python version.

The relevant point for ITK: this is opt-in. A feedstock that keeps its per-version matrix and simply compiles abi3 binaries is perfectly valid — an abi3 extension loads on any interpreter at or above its floor, so a per-cp3XX package containing one is a strict superset of what that package needs. abi3 makes the per-version matrix unnecessary; it does not make it unusable.

For reference, conda-forge/itk-feedstock (ITK 5.4.7) currently builds per version — 18 .ci_support variants, python 3.10/3.11/3.12 × hdf5 1.14.6/2 × linux_64/linux_aarch64/osx_arm64 — and every one is ____cpython, i.e. no free-threaded variants today.

One correction to how this is sometimes stated. Version-specific builds are not needed "for abi3t under 3.13/3.14" — abi3t does not exist there. Per PEP 803 the Stable ABI is simply unavailable on free-threaded builds:

"Extensions will fail to build for the Stable ABI on free-threaded Python (that is, when both Py_LIMITED_API and Py_GIL_DISABLED preprocessor macros are defined)."

So free-threaded 3.13t/3.14t require a build that is neither abi3 nor abi3t, which is exactly why a version-specific path has to stay reachable.

That changes at 3.15, but not to a single build: PEP 803 recommends shipping both, tagged abi3.abi3t, compiled with Py_LIMITED_API and Py_TARGET_ABI3T respectively. So the end state is two ABI variants per platform rather than one per interpreter version. CMake already models the switch — no new find_package component is needed, since USE_SABI picks Py_TARGET_ABI3T over Py_LIMITED_API when Python3_FREE_THREADED is true and the version is 3.15+ (CMake 4.4). Note CMake does not cover the 3.13t/3.14t gap: it still defines Py_LIMITED_API there, so the guard has to be ours. All captured in #6757.

@hjmjohnson
hjmjohnson requested review from dzenanz and thewtex August 5, 2026 18:23
@dzenanz

dzenanz commented Aug 5, 2026

Copy link
Copy Markdown
Member

I still like this. I don't have time to carefully review.

@hjmjohnson hjmjohnson changed the title COMP: Build all ITK Python wrapping as abi3 (stable ABI) COMP: Make ITKVtkGlue wrapping abi3-compatible Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:Bridge Issues affecting the Bridge module area:Python wrapping Python bindings for a class type:Compiler Compiler support or related warnings type:Infrastructure Infrastructure/ecosystem related changes, such as CMake or buildbots type:Testing Ensure that the purpose of a class is met/the results on a wide set of test cases are correct

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ITKVtkGlue blocks abi3 / ITK_USE_PYTHON_LIMITED_API builds: wrapping depends on VTK's non-Limited-API Python core

4 participants