From 804fc1ddf27eb458d995f95841965fb01033e228 Mon Sep 17 00:00:00 2001 From: "Benjamin A. Beasley" Date: Wed, 23 Jul 2025 15:53:01 -0400 Subject: [PATCH] Patch for PyStr regression on big-endian architectures --- ...ff9df61b3b9d71527acf4fb60a735d841025.patch | 175 ++++++++++++++++++ python-orjson.spec | 9 + 2 files changed, 184 insertions(+) create mode 100644 9c33ff9df61b3b9d71527acf4fb60a735d841025.patch diff --git a/9c33ff9df61b3b9d71527acf4fb60a735d841025.patch b/9c33ff9df61b3b9d71527acf4fb60a735d841025.patch new file mode 100644 index 0000000..fd5153d --- /dev/null +++ b/9c33ff9df61b3b9d71527acf4fb60a735d841025.patch @@ -0,0 +1,175 @@ +From 9c33ff9df61b3b9d71527acf4fb60a735d841025 Mon Sep 17 00:00:00 2001 +From: ijl +Date: Wed, 23 Jul 2025 13:49:55 +0000 +Subject: [PATCH] Fix big-endian PyStr + +--- + .github/workflows/artifact.yaml | 29 ++++++++++++++++ + README.md | 5 +-- + src/str/pystr.rs | 61 +++++++++++++++++++++------------ + 3 files changed, 72 insertions(+), 23 deletions(-) + +diff --git a/.github/workflows/artifact.yaml b/.github/workflows/artifact.yaml +index fa628c03..d7283562 100644 +--- a/.github/workflows/artifact.yaml ++++ b/.github/workflows/artifact.yaml +@@ -269,6 +269,35 @@ jobs: + if-no-files-found: "error" + compression-level: 0 + ++ - name: setup-qemu-container ++ if: "matrix.target.arch == 's390x'" ++ uses: sandervocke/setup-qemu-container@v1 ++ with: ++ container: registry.fedoraproject.org/fedora:42 ++ arch: ${{ matrix.target.arch }} ++ podman_args: "-v .:/orjson -v /tmp:/tmp --workdir /orjson" ++ ++ - name: setup-shell-wrapper ++ uses: sandervocke/setup-shell-wrapper@v1 ++ ++ - name: Emulated Test ++ if: "matrix.target.arch == 's390x'" ++ shell: wrap-shell {0} ++ env: ++ WRAP_SHELL: run-in-container.sh ++ run: | ++ set -eou pipefail ++ ++ dnf install --setopt=install_weak_deps=false -y ${{ matrix.python.interpreter }} python3-uv ++ ++ uv venv --python ${{ matrix.python.interpreter }} ++ source .venv/bin/activate ++ ++ uv pip install -r test/requirements.txt ++ uv pip install dist/orjson*.whl ++ ++ pytest -v test ++ + musllinux_amd64: + runs-on: ubuntu-24.04 + timeout-minutes: 10 +diff --git a/README.md b/README.md +index f0b9a6a0..3b7b642f 100644 +--- a/README.md ++++ b/README.md +@@ -1088,8 +1088,9 @@ is prudent to pin the nightly version because that channel can introduce + breaking changes. There is a significant performance benefit to using + nightly. + +-orjson is tested for amd64, aarch64, and i686 on Linux and cross-compiles for +-arm7, ppc64le, and s390x. It is tested for either aarch64 or amd64 on macOS and ++orjson is tested on native hardware for amd64, aarch64, and i686 on Linux and ++for arm7, ppc64le, and s390x is cross-compiled and may be tested via ++emulation. It is tested for either aarch64 or amd64 on macOS and + cross-compiles for the other, depending on version. For Windows it is + tested on amd64 and i686. + +diff --git a/src/str/pystr.rs b/src/str/pystr.rs +index a856b13a..186a0e50 100644 +--- a/src/str/pystr.rs ++++ b/src/str/pystr.rs +@@ -1,11 +1,14 @@ + // SPDX-License-Identifier: (Apache-2.0 OR MIT) + + use crate::typeref::{EMPTY_UNICODE, STR_TYPE}; ++#[cfg(target_endian = "little")] + use crate::util::isize_to_usize; +- ++#[cfg(target_endian = "little")] + use core::ffi::c_void; + use core::ptr::NonNull; +-use pyo3_ffi::{PyASCIIObject, PyCompactUnicodeObject, PyObject}; ++#[cfg(target_endian = "little")] ++use pyo3_ffi::PyCompactUnicodeObject; ++use pyo3_ffi::{PyASCIIObject, PyObject}; + + fn to_str_via_ffi(op: *mut PyObject) -> Option<&'static str> { + let mut str_size: pyo3_ffi::Py_ssize_t = 0; +@@ -32,29 +35,21 @@ pub fn set_str_create_fn() { + } + } + +-#[cfg(all(Py_3_14, Py_GIL_DISABLED))] +-const STATE_COMPACT_ASCII: u32 = u32::from_le(0b000000000000000000_0_1_1_001_00000000); +- +-#[cfg(not(all(Py_3_14, Py_GIL_DISABLED)))] +-const STATE_COMPACT_ASCII: u32 = u32::from_le(0b000000000000000000000000_0_1_1_001_00); +- +-#[cfg(all(Py_3_14, Py_GIL_DISABLED))] +-const STATE_COMPACT: u32 = u32::from_le(0b000000000000000000_0_0_1_000_00000000); +- +-#[cfg(not(all(Py_3_14, Py_GIL_DISABLED)))] +-const STATE_COMPACT: u32 = u32::from_le(0b000000000000000000000000_0_0_1_000_00); ++#[cfg(all(target_endian = "little", Py_3_14, Py_GIL_DISABLED))] ++const STATE_KIND_SHIFT: usize = 8; + +-#[cfg(all(Py_3_14, Py_GIL_DISABLED))] +-const STATE_KIND_MASK: u32 = u32::from_le(0b111_00000000); ++#[cfg(all(target_endian = "little", not(all(Py_3_14, Py_GIL_DISABLED))))] ++const STATE_KIND_SHIFT: usize = 2; + +-#[cfg(all(Py_3_14, Py_GIL_DISABLED))] +-const STATE_KIND_SHIFT: usize = 8; ++#[cfg(target_endian = "little")] ++const STATE_KIND_MASK: u32 = 7 << STATE_KIND_SHIFT; + +-#[cfg(not(all(Py_3_14, Py_GIL_DISABLED)))] +-const STATE_KIND_MASK: u32 = u32::from_le(0b111_00); ++#[cfg(target_endian = "little")] ++const STATE_COMPACT_ASCII: u32 = ++ 1 << STATE_KIND_SHIFT | 1 << (STATE_KIND_SHIFT + 3) | 1 << (STATE_KIND_SHIFT + 4); + +-#[cfg(not(all(Py_3_14, Py_GIL_DISABLED)))] +-const STATE_KIND_SHIFT: usize = 2; ++#[cfg(target_endian = "little")] ++const STATE_COMPACT: u32 = 1 << (STATE_KIND_SHIFT + 3); + + #[repr(transparent)] + #[derive(Copy, Clone)] +@@ -96,6 +91,7 @@ impl PyStr { + } + } + ++ #[cfg(target_endian = "little")] + pub fn hash(&mut self) { + unsafe { + let ptr = self.ptr.as_ptr().cast::(); +@@ -118,7 +114,24 @@ impl PyStr { + } + } + ++ #[cfg(not(target_endian = "little"))] ++ pub fn hash(&mut self) { ++ unsafe { ++ let data_ptr = ffi!(PyUnicode_DATA(self.ptr.as_ptr())); ++ #[allow(clippy::cast_possible_wrap)] ++ let num_bytes = ++ ffi!(PyUnicode_KIND(self.ptr.as_ptr())) as isize * ffi!(Py_SIZE(self.ptr.as_ptr())); ++ #[cfg(Py_3_14)] ++ let hash = pyo3_ffi::Py_HashBuffer(data_ptr, num_bytes); ++ #[cfg(not(Py_3_14))] ++ let hash = pyo3_ffi::_Py_HashBytes(data_ptr, num_bytes); ++ (*self.ptr.as_ptr().cast::()).hash = hash; ++ debug_assert!((*self.ptr.as_ptr().cast::()).hash != -1); ++ } ++ } ++ + #[inline(always)] ++ #[cfg(target_endian = "little")] + pub fn to_str(self) -> Option<&'static str> { + unsafe { + let op = self.ptr.as_ptr(); +@@ -140,6 +153,12 @@ impl PyStr { + } + } + ++ #[inline(always)] ++ #[cfg(not(target_endian = "little"))] ++ pub fn to_str(self) -> Option<&'static str> { ++ to_str_via_ffi(self.ptr.as_ptr()) ++ } ++ + pub fn as_ptr(self) -> *mut PyObject { + self.ptr.as_ptr() + } diff --git a/python-orjson.spec b/python-orjson.spec index d186bdf..cd1fa1c 100644 --- a/python-orjson.spec +++ b/python-orjson.spec @@ -64,6 +64,15 @@ Source0: orjson-%{version}-filtered.tar.xz # ./get_source ${COMMIT} (or ${TAG}) Source1: get_source +# Fix big-endian PyStr +# https://github.com/ijl/orjson/commit/9c33ff9df61b3b9d71527acf4fb60a735d841025 +# +# Fixes: +# +# Test regressions (segfaults) on s390x architecture in 3.11.0 +# https://github.com/ijl/orjson/issues/584 +Patch: %{url}/commit/9c33ff9df61b3b9d71527acf4fb60a735d841025.patch + BuildRequires: tomcli BuildRequires: python3-devel BuildRequires: %{py3_dist pytest-forked}