Skip to content

Commit

Permalink
ABI compatibility with free-threading builds
Browse files Browse the repository at this point in the history
  • Loading branch information
ijl committed Jan 8, 2025
1 parent 0c3ca67 commit a817c91
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 21 deletions.
15 changes: 8 additions & 7 deletions .github/workflows/artifact.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ jobs:
fail-fast: false
matrix:
python: [
{ interpreter: 'python3.13', package: 'python3.13', compatibility: "manylinux_2_17" },
{ interpreter: 'python3.12', package: 'python3.12', compatibility: "manylinux_2_17" },
{ interpreter: 'python3.11', package: 'python3.11', compatibility: "manylinux_2_17" },
{ interpreter: 'python3.10', package: 'python3.10', compatibility: "manylinux_2_17" },
{ interpreter: 'python3.9', package: 'python3.9', compatibility: "manylinux_2_17" },
{ interpreter: 'python3.8', package: 'python3.8', compatibility: "manylinux_2_17" },
{ interpreter: 'python3.13', package: 'python3.13-freethreading', compatibility: "manylinux_2_34", publish: false },
{ interpreter: 'python3.13', package: 'python3.13', compatibility: "manylinux_2_17", publish: true },
{ interpreter: 'python3.12', package: 'python3.12', compatibility: "manylinux_2_17", publish: true },
{ interpreter: 'python3.11', package: 'python3.11', compatibility: "manylinux_2_17", publish: true },
{ interpreter: 'python3.10', package: 'python3.10', compatibility: "manylinux_2_17", publish: true },
{ interpreter: 'python3.9', package: 'python3.9', compatibility: "manylinux_2_17", publish: true },
{ interpreter: 'python3.8', package: 'python3.8', compatibility: "manylinux_2_17", publish: true },
]
arch: [
{
Expand Down Expand Up @@ -154,7 +155,7 @@ jobs:
cp ${CARGO_TARGET_DIR}/wheels/orjson*.whl dist
- name: Store wheels
if: "startsWith(github.ref, 'refs/tags/')"
if: "startsWith(github.ref, 'refs/tags/') && matrix.python.publish == true"
uses: actions/upload-artifact@v4
with:
name: orjson_manylinux_2_17_amd64_${{ matrix.python.interpreter }}
Expand Down
59 changes: 54 additions & 5 deletions src/ffi/fragment.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use core::ffi::{c_char, c_ulong};
use core::ffi::c_char;

#[cfg(Py_GIL_DISABLED)]
use std::sync::atomic::{AtomicIsize, AtomicU32, AtomicU64};

use core::ptr::null_mut;
use pyo3_ffi::*;

// https://docs.python.org/3/c-api/typeobj.html#typedef-examples

#[cfg(Py_GIL_DISABLED)]
#[allow(non_upper_case_globals)]
const _Py_IMMORTAL_REFCNT_LOCAL: u32 = u32::MAX;

#[repr(C)]
pub struct Fragment {
#[cfg(Py_GIL_DISABLED)]
pub ob_tid: usize,
#[cfg(Py_GIL_DISABLED)]
pub _padding: u16,
#[cfg(Py_GIL_DISABLED)]
pub ob_mutex: PyMutex,
#[cfg(Py_GIL_DISABLED)]
pub ob_gc_bits: u8,
#[cfg(Py_GIL_DISABLED)]
pub ob_ref_local: AtomicU32,
#[cfg(Py_GIL_DISABLED)]
pub ob_ref_shared: AtomicIsize,
#[cfg(not(Py_GIL_DISABLED))]
pub ob_refcnt: pyo3_ffi::Py_ssize_t,
pub ob_type: *mut pyo3_ffi::PyTypeObject,
pub contents: *mut pyo3_ffi::PyObject,
Expand Down Expand Up @@ -42,6 +63,19 @@ pub unsafe extern "C" fn orjson_fragment_tp_new(
let contents = PyTuple_GET_ITEM(args, 0);
Py_INCREF(contents);
let obj = Box::new(Fragment {
#[cfg(Py_GIL_DISABLED)]
ob_tid: 0,
#[cfg(Py_GIL_DISABLED)]
_padding: 0,
#[cfg(Py_GIL_DISABLED)]
ob_mutex: PyMutex::new(),
#[cfg(Py_GIL_DISABLED)]
ob_gc_bits: 0,
#[cfg(Py_GIL_DISABLED)]
ob_ref_local: AtomicU32::new(0),
#[cfg(Py_GIL_DISABLED)]
ob_ref_shared: AtomicIsize::new(0),
#[cfg(not(Py_GIL_DISABLED))]
ob_refcnt: 1,
ob_type: crate::typeref::FRAGMENT_TYPE,
contents: contents,
Expand All @@ -61,11 +95,14 @@ pub unsafe extern "C" fn orjson_fragment_dealloc(object: *mut PyObject) {
}
}

#[cfg(Py_3_10)]
const FRAGMENT_TP_FLAGS: c_ulong = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE;
#[cfg(Py_GIL_DISABLED)]
const FRAGMENT_TP_FLAGS: AtomicU64 = AtomicU64::new(Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE);

#[cfg(all(Py_3_10, not(Py_GIL_DISABLED)))]
const FRAGMENT_TP_FLAGS: core::ffi::c_ulong = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE;

#[cfg(not(Py_3_10))]
const FRAGMENT_TP_FLAGS: c_ulong = Py_TPFLAGS_DEFAULT;
const FRAGMENT_TP_FLAGS: core::ffi::c_ulong = Py_TPFLAGS_DEFAULT;

#[unsafe(no_mangle)]
#[cold]
Expand All @@ -75,7 +112,19 @@ pub unsafe extern "C" fn orjson_fragmenttype_new() -> *mut PyTypeObject {
let ob = Box::new(PyTypeObject {
ob_base: PyVarObject {
ob_base: PyObject {
#[cfg(Py_3_12)]
#[cfg(Py_GIL_DISABLED)]
ob_tid: 0,
#[cfg(Py_GIL_DISABLED)]
_padding: 0,
#[cfg(Py_GIL_DISABLED)]
ob_mutex: PyMutex::new(),
#[cfg(Py_GIL_DISABLED)]
ob_gc_bits: 0,
#[cfg(Py_GIL_DISABLED)]
ob_ref_local: AtomicU32::new(_Py_IMMORTAL_REFCNT_LOCAL),
#[cfg(Py_GIL_DISABLED)]
ob_ref_shared: AtomicIsize::new(0),
#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))]
ob_refcnt: pyo3_ffi::PyObjectObRefcnt { ob_refcnt: 0 },
#[cfg(not(Py_3_12))]
ob_refcnt: 0,
Expand Down
10 changes: 6 additions & 4 deletions src/serialize/obtype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@ pub fn pyobject_to_obtype_unlikely(ob_type: *mut pyo3_ffi::PyTypeObject, opts: O
}
}

let tp_flags = tp_flags!(ob_type);

if opt_disabled!(opts, PASSTHROUGH_SUBCLASS) {
if is_subclass_by_flag!(ob_type, Py_TPFLAGS_UNICODE_SUBCLASS) {
if is_subclass_by_flag!(tp_flags, Py_TPFLAGS_UNICODE_SUBCLASS) {
return ObType::StrSubclass;
} else if is_subclass_by_flag!(ob_type, Py_TPFLAGS_LONG_SUBCLASS) {
} else if is_subclass_by_flag!(tp_flags, Py_TPFLAGS_LONG_SUBCLASS) {
return ObType::Int;
} else if is_subclass_by_flag!(ob_type, Py_TPFLAGS_LIST_SUBCLASS) {
} else if is_subclass_by_flag!(tp_flags, Py_TPFLAGS_LIST_SUBCLASS) {
return ObType::List;
} else if is_subclass_by_flag!(ob_type, Py_TPFLAGS_DICT_SUBCLASS) {
} else if is_subclass_by_flag!(tp_flags, Py_TPFLAGS_DICT_SUBCLASS) {
return ObType::Dict;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/serialize/per_type/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl ListTupleSerializer {
) -> Self {
debug_assert!(
is_type!(ob_type!(ptr), LIST_TYPE)
|| is_subclass_by_flag!(ob_type!(ptr), Py_TPFLAGS_LIST_SUBCLASS)
|| is_subclass_by_flag!(tp_flags!(ob_type!(ptr)), Py_TPFLAGS_LIST_SUBCLASS)
);
let data_ptr = unsafe { (*(ptr as *mut pyo3_ffi::PyListObject)).ob_item };
let len = ffi!(Py_SIZE(ptr)) as usize;
Expand All @@ -66,7 +66,7 @@ impl ListTupleSerializer {
) -> Self {
debug_assert!(
is_type!(ob_type!(ptr), TUPLE_TYPE)
|| is_subclass_by_flag!(ob_type!(ptr), Py_TPFLAGS_TUPLE_SUBCLASS)
|| is_subclass_by_flag!(tp_flags!(ob_type!(ptr)), Py_TPFLAGS_TUPLE_SUBCLASS)
);
let data_ptr = unsafe { (*(ptr as *mut pyo3_ffi::PyTupleObject)).ob_item.as_ptr() };
let len = ffi!(Py_SIZE(ptr)) as usize;
Expand Down
32 changes: 29 additions & 3 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,27 @@ macro_rules! is_class_by_type {
};
}

#[cfg(not(Py_GIL_DISABLED))]
macro_rules! tp_flags {
($ob_type:expr) => {
unsafe { (*$ob_type).tp_flags }
};
}

#[cfg(Py_GIL_DISABLED)]
macro_rules! tp_flags {
($ob_type:expr) => {
unsafe {
(*$ob_type)
.tp_flags
.load(std::sync::atomic::Ordering::Relaxed)
}
};
}

macro_rules! is_subclass_by_flag {
($ob_type:expr, $flag:ident) => {
unsafe { (((*$ob_type).tp_flags & pyo3_ffi::$flag) != 0) }
($tp_flags:expr, $flag:ident) => {
unsafe { (($tp_flags & pyo3_ffi::$flag) != 0) }
};
}

Expand Down Expand Up @@ -98,7 +116,7 @@ macro_rules! str_from_slice {
};
}

#[cfg(Py_3_12)]
#[cfg(all(Py_3_12, not(Py_GIL_DISABLED)))]
macro_rules! reverse_pydict_incref {
($op:expr) => {
unsafe {
Expand All @@ -110,6 +128,14 @@ macro_rules! reverse_pydict_incref {
};
}

#[cfg(Py_GIL_DISABLED)]
macro_rules! reverse_pydict_incref {
($op:expr) => {
debug_assert!(ffi!(Py_REFCNT($op)) >= 2);
ffi!(Py_DECREF($op))
};
}

#[cfg(not(Py_3_12))]
macro_rules! reverse_pydict_incref {
($op:expr) => {
Expand Down

0 comments on commit a817c91

Please sign in to comment.