Skip to content

add support for abi3t - #556

Merged
Icxolu merged 4 commits into
PyO3:mainfrom
Icxolu:abi3t
Aug 23, 2026
Merged

Icxolu merged 4 commits into
PyO3:mainfrom
Icxolu:abi3t

Conversation

@Icxolu

@Icxolu Icxolu commented Jun 15, 2026 •

Copy link
Copy Markdown
Member

This adds abi3t support by

  • switching to opaque structs
  • exposing data access helpers from slots 369-378
  • redirect all field accesses through the access helpers

Todo:

  • changelog entry
  • CI testing for 3.15/3.15t

See numpy/numpy#31091
Closes #555

@ngoldbaum

Copy link
Copy Markdown
Contributor

Ping @kumaraditya303 - we were just talking about this today.

@neutrinoceros

neutrinoceros commented Jun 17, 2026 •

Copy link
Copy Markdown

I confirmed that this resolved the runtime error I reported in #555
example logs (remaining failures are unrelated, they're just due to numpy not having cp315 nightlies for windows yet)

Comment thread src/npyffi/array.rs Outdated
Comment thread src/npyffi/array.rs Outdated
Comment thread src/npyffi/array.rs Outdated
Comment thread src/npyffi/objects.rs
@kumaraditya303

Copy link
Copy Markdown

I think we should also add a check that when abi3t is used, the minimum numpy version should be 2.5.

@Icxolu
Icxolu force-pushed the abi3t branch 4 times, most recently from cfef0a3 to 23f2955 Compare June 20, 2026 09:28
@Icxolu

Icxolu commented Jun 20, 2026

Copy link
Copy Markdown
Member Author

Looks like 32bit windows still has some issues... not sure why though
There was also https://github.com/PyO3/rust-numpy/actions/runs/27866655836/job/82471861978 earlier

@neutrinoceros

Copy link
Copy Markdown

My guess is that'd be because there are no wheels for cp315*-*win32
xref numpy/numpy-release#53

@Icxolu

Icxolu commented Jun 20, 2026

Copy link
Copy Markdown
Member Author

To me it looks like there are cp315-win32 wheels here and they installed fine. The version specific run had no issues as well. It's just the abi3t run that failed, so I think this is a genuine failure, but I'm not sure which fault it could be: ours, pyo3, numpy or python

@kumaraditya303

Copy link
Copy Markdown

I think it could alignment issue on 32 bit MSVC, I am not very familiar with rust but it seems flags is defined as npy_uint64 which rust gives 8 bytes alignment whereas in C I think it is 4 bytes which could be an issue.

@kumaraditya303

Copy link
Copy Markdown

@Icxolu Can you try forcing a 4 byte alignment layout using #[cfg_attr(all(Py_LIMITED_API, Py_GIL_DISABLED), repr(C, packed(4)))]?

@ngoldbaum

Copy link
Copy Markdown
Contributor

If you all don't sort this out in the meantime I'll try to look on Monday. While win32 isn't so important per se, I do find it useful to detect and sort out CPU architecture-sensitive issues. It's possible this issue is in any one of CPython, NumPy, or rust-numpy.

Glad to see everything mostly works otherwise!

@Icxolu

Icxolu commented Jun 20, 2026

Copy link
Copy Markdown
Member Author

I did a bit of digging with compiler explorer: https://godbolt.org/z/qYe3Ghz7a
Both rustc and msvc agree on both architectures on the structure size and that flags is at offset 16 (and thus 8 byte aligned as it should), so I think there is no issue with our definitions here. I did however also find this: rust-lang/rust#112480, which does not really paint a great picture about win32... perhaps we ran into that...

@ngoldbaum

Copy link
Copy Markdown
Contributor

I think I know what the issue is.

PyArray_Descr_fields has a field that causes alignment issues on 32bit builds:

https://github.com/numpy/numpy/blob/1d656e454dea882b2f2a20ccdd93b02f3c7880c1/numpy/_core/include/numpy/ndarraytypes.h#L650

It's explicitly npy_uint64 - 8 bytes. In this case, it happens to lead to a field access with incorrect alignment and you get a segfault.

I think the fix needs to live in NumPy's headers. There probably needs to be a mirrored change here to ensure correct alignment for the flags field in PyArray_Descr_fields and _PyArray_LegacyDescr_fields. I'll have a PR open on NumPy shortly.

@ngoldbaum

Copy link
Copy Markdown
Contributor

I opened numpy/numpy#31759

@ngoldbaum

ngoldbaum commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

It turns out my first attempt at a fix had a flaw and I think the only real fix requires NumPy to add new a C API function to fix this issue: numpy/numpy#31762

@kumaraditya303

This comment was marked as outdated.

@kumaraditya303

This comment was marked as outdated.

@Icxolu

Icxolu commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Thank you both for continuing to look into this, much appreciated ❤️. That patch seems to make it run indeed. What I don't really understand is, why this should work. If I got it correctly, then from numpy's definition there are padding bytes inserted to align the flags field on 32bit. If we now tell Rust to use a packed layout, wouldn't we read the padding bytes instead of the field? I'd also assume that this than effects all 32bit platforms, not just windows and not just x86, we just don't have any CI configured for other cases.

@ngoldbaum

Copy link
Copy Markdown
Contributor

I think we need a similar fix on the NumPy side too.

This does affect all 32 bit platforms, I think.

@Icxolu
Icxolu merged commit 9df4023 into PyO3:main Aug 23, 2026
138 of 140 checks passed
@Icxolu
Icxolu deleted the abi3t branch August 23, 2026 16:44
@neutrinoceros

Copy link
Copy Markdown

If I may ask, what's the expected timeline for this to land into a release ?

@Icxolu

Icxolu commented Aug 26, 2026

Copy link
Copy Markdown
Member Author

Whenever the next pyo3 (0.30) releases, I'll prepare a new release which includes this.

@ngoldbaum

Copy link
Copy Markdown
Contributor

Whenever the next pyo3 (0.30) releases, I'll prepare a new release which includes this.

I'm in the monthly PyO3 community call with David and Lucas Colley and the topic of shipping 0.30 came up. He's fine with it but is curious why you can't do a rust-numpy 0.29.1 release.

@Icxolu

Icxolu commented Aug 26, 2026

Copy link
Copy Markdown
Member Author

The restructuring of the public npyffi to allow the abi3t compatible opaque layout is a breaking api change.

@davidhewitt

Copy link
Copy Markdown
Member

In which case let's move forward with PyO3 0.30 👍

samantha-earthmover added a commit to samantha-earthmover/gribberish that referenced this pull request Oct 9, 2026
rust-numpy 0.29.0 reads dtype fields through struct layouts that assume
a visible PyObject header. The abi3t build makes PyObject opaque, so
datetime64 arrays crash with a null reference on 3.15. rust-numpy main
fixes this (PyO3/rust-numpy#556) but the fix is not released yet. Until
it is, build a cp315t wheel; 3.15 GIL builds use the cp312-abi3 wheel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mpiannucci pushed a commit to mpiannucci/gribberish that referenced this pull request Oct 9, 2026
… 3.15 (#209)

* Build abi3/abi3t wheels and add free-threaded and Python 3.15 support

Bump pyo3 to 0.29 and rust-numpy to 0.29 for Python 3.15 support, and
enable the abi3-py312 and abi3t-py315 features. Each platform now ships
one cp312-abi3 wheel for GIL builds 3.12+, one cp314t wheel, one
cp315-abi3t wheel for 3.15+ GIL and free-threaded builds, and PyPy.
The test job runs on 3.12 through 3.15t and checks the GIL stays off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Build a version-specific cp315t wheel instead of abi3t

rust-numpy 0.29.0 reads dtype fields through struct layouts that assume
a visible PyObject header. The abi3t build makes PyObject opaque, so
datetime64 arrays crash with a null reference on 3.15. rust-numpy main
fixes this (PyO3/rust-numpy#556) but the fix is not released yet. Until
it is, build a cp315t wheel; 3.15 GIL builds use the cp312-abi3 wheel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
li-em added a commit to earth-mover/icechunk that referenced this pull request Oct 12, 2026
The free-threaded CI checks, pixi envs and Nix shells now use 3.15t.
The release workflow builds a 3.15t wheel and keeps the 3.14t wheel.

The 3.15t wheel is abi3t (PEP 803). rust-numpy 0.29.0 reads array
fields at fixed offsets, and these offsets are wrong under abi3t.
The numpy crate is pinned to the git commit that has
PyO3/rust-numpy#556 until a crates.io release has it.

pyo3 gives simple enums a default `__repr__`. A custom `__repr__`
adds a second Py_tp_repr slot, and 3.15 rejects the type
(PyO3/pyo3#6494). This change removes the custom `__repr__` from
SpecVersion and ConflictType.

netcdf4 has no 3.15 build, so it moves to a feature that only the
default env uses.
kylebarron added a commit to kylebarron/arro3 that referenced this pull request Oct 12, 2026
Add `-i 3.15t` to the version-specific wheel builds on every native
platform, so we publish `cp315-cp315t` wheels alongside the existing
`cp314-cp314t` ones. Bump the native jobs' uv pin to 0.13.x so that
`uv python install 3.15t` resolves the final 3.15 release.

We are not building `abi3t` wheels yet: rust-numpy 0.29 has the wrong
struct layout under abi3t and segfaults in numpy interop
(PyO3/rust-numpy#555). The fix (PyO3/rust-numpy#556) will only ship
alongside PyO3 0.30.

Co-authored-by: Shaan Majid <70789625+shaanmajid@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
brndnmtthws added a commit to brndnmtthws/hessboost that referenced this pull request Oct 14, 2026
)

* build(mise): bump uv to 0.13.1 for CPython 3.15.0

uv 0.12.23 knows CPython 3.15 only up to 3.15.0rc3; 0.13.0 adds 3.15.0
final (and makes it the default stable version).

* feat(python)!: abi3t wheels for CPython 3.15, require CPython 3.12

Three wheels per platform, each built with its family's oldest CPython:
cp312-abi3 (GIL-enabled 3.12 to 3.14), cp315-abi3.abi3t (PEP 803: both
builds of 3.15 and newer, which uv and pip prefer there), and
cp314-cp314t (free-threaded 3.14 has no stable ABI). PyO3's abi3-py312
and abi3t-py315 features let the build interpreter pick the extension.

- rust-numpy comes from a pinned git revision: its abi3t support
  (PyO3/rust-numpy#556) is not in a release yet. The abi3t extension
  reads arrays through NumPy 2.5's C API, so CPython 3.15 and newer
  require numpy>=2.5.
- requires-python >=3.12: numpy 2.5 and SciPy 1.18 already dropped 3.11,
  and 3.12 is the most-used CPython. ruff and ty target 3.12, and the
  type aliases become PEP 695 `type` statements.
- CI builds the abi3 and abi3t wheels on every platform (cp314t on
  x86_64 Linux) and tests them on 3.12 and 3.14, 3.15 and 3.15t (3.14t);
  publish.yml builds and tests all three, and the CUDA runtime's, on every
  platform. The tests name each version, since uv's "3" also matches an
  installed free-threaded interpreter.

BREAKING CHANGE: CPython 3.11 is no longer supported; installers there
keep resolving hessboost 0.3.1.

* ci: test the GIL-enabled CPython an unsuffixed version names

For CPython 3.14 and newer uv takes an installed free-threaded
interpreter for a plain version request, and keeps a free-threaded
project environment for one. publish.yml therefore tested the abi3
wheels against 3.14t, which refuses them. ci.yml tested its "3.15" runs
on the 3.15t build interpreter. test-wheel.sh now installs the requested
version (uv downloads the GIL-enabled build for a plain version) and
asks for it as "+gil".
kylebarron added a commit to kylebarron/arro3 that referenced this pull request Oct 15, 2026
numpy 0.30 ships the abi3t fix from PyO3/rust-numpy#556, which we need
before we can publish abi3t wheels (#532). pyo3-arrow builds against
both without source changes.

arro3 itself can't move to pyo3 0.30 yet, because arro3-io needs pyo3
0.30 releases of pyo3-async-runtimes, pyo3-bytes, pyo3-object_store,
and pyo3-file. As described in DEVELOP.md, pin the arro3 crates to
pyo3-arrow at the current main commit until then.

The root Cargo.lock also picks up thiserror 2 for pyo3-arrow, which
#539 changed without updating that lockfile.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
li-em added a commit to earth-mover/icechunk that referenced this pull request Oct 16, 2026
numpy 0.30.0 has the abi3t support from PyO3/rust-numpy#556, so the
git pin is replaced with the crates.io release. numpy 0.30 needs
pyo3 0.30, so pyo3, pyo3-async-runtimes and pyo3-bytes move to their
0.30 releases.

pyo3 0.30 makes `skip_from_py_object` the default and deprecates the
option, so this change removes it from all `#[pyclass]` attributes.
li-em added a commit to earth-mover/icechunk that referenced this pull request Oct 16, 2026
The free-threaded CI checks, pixi envs and Nix shells now use 3.15t.
The release workflow builds a 3.15t wheel and keeps the 3.14t wheel.

The 3.15t wheel is abi3t (PEP 803). rust-numpy 0.29.0 reads array
fields at fixed offsets, and these offsets are wrong under abi3t.
The numpy crate is pinned to the git commit that has
PyO3/rust-numpy#556 until a crates.io release has it.

pyo3 gives simple enums a default `__repr__`. A custom `__repr__`
adds a second Py_tp_repr slot, and 3.15 rejects the type
(PyO3/pyo3#6494). This change removes the custom `__repr__` from
SpecVersion and ConflictType.

netcdf4 has no 3.15 build, so it moves to a feature that only the
default env uses.
li-em added a commit to earth-mover/icechunk that referenced this pull request Oct 16, 2026
numpy 0.30.0 has the abi3t support from PyO3/rust-numpy#556, so the
git pin is replaced with the crates.io release. numpy 0.30 needs
pyo3 0.30, so pyo3, pyo3-async-runtimes and pyo3-bytes move to their
0.30 releases.

pyo3 0.30 makes `skip_from_py_object` the default and deprecates the
option, so this change removes it from all `#[pyclass]` attributes.
li-em added a commit to earth-mover/icechunk that referenced this pull request Oct 16, 2026
The free-threaded CI checks, pixi envs and Nix shells now use 3.15t.
The release workflow builds a 3.15t wheel and keeps the 3.14t wheel.

The 3.15t wheel is abi3t (PEP 803). rust-numpy 0.29.0 reads array
fields at fixed offsets, and these offsets are wrong under abi3t.
The numpy crate is pinned to the git commit that has
PyO3/rust-numpy#556 until a crates.io release has it.

pyo3 gives simple enums a default `__repr__`. A custom `__repr__`
adds a second Py_tp_repr slot, and 3.15 rejects the type
(PyO3/pyo3#6494). This change removes the custom `__repr__` from
SpecVersion and ConflictType.

netcdf4 has no 3.15 build, so it moves to a feature that only the
default env uses.
li-em added a commit to earth-mover/icechunk that referenced this pull request Oct 16, 2026
numpy 0.30.0 has the abi3t support from PyO3/rust-numpy#556, so the
git pin is replaced with the crates.io release. numpy 0.30 needs
pyo3 0.30, so pyo3, pyo3-async-runtimes and pyo3-bytes move to their
0.30 releases.

pyo3 0.30 makes `skip_from_py_object` the default and deprecates the
option, so this change removes it from all `#[pyclass]` attributes.
paraseba pushed a commit to DahnJ/icechunk that referenced this pull request Oct 17, 2026
…ls (earth-mover#2472)

The free-threaded CI checks, pixi envs and Nix shells now use 3.15t. The
release workflow builds a 3.15t wheel and keeps the 3.14t wheel.

The 3.15t wheel is abi3t (PEP 803). rust-numpy 0.30.0 includes
PyO3/rust-numpy#556 fixing behavior for 3.15t, but brings a PyO3 bump to
0.30.1 too.

pyo3 gives simple enums a default `__repr__`. A custom `__repr__` adds a
second Py_tp_repr slot, and 3.15 rejects the type (PyO3/pyo3#6494). This
change removes the custom `__repr__` from SpecVersion and ConflictType.

netcdf4 has no 3.15 build, so it moves to a feature that only the
default env uses.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI-no-fail-fast If one job fails, allow the rest to keep testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: function built with -F pyo3/abi3t raises TypeError at runtime (CPython 3.15.0b2-free-threaded))

5 participants