Skip to content

Commit

Permalink
Use PyLongObject ob_size, lv_tag to identify signedness
Browse files Browse the repository at this point in the history
  • Loading branch information
ijl committed Aug 29, 2023
1 parent 35119dd commit d1cd27e
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 36 deletions.
39 changes: 39 additions & 0 deletions src/ffi/long.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

// longintrepr.h, _longobject, _PyLongValue

#[cfg(Py_3_12)]
const SIGN_MASK: usize = 3;
#[cfg(Py_3_12)]
const SIGN_ZERO: usize = 1;
#[cfg(Py_3_12)]
const SIGN_POSITIVE: usize = 0;

#[cfg(Py_3_12)]
#[allow(dead_code)]
struct PyLongObject {
pub ob_refcnt: pyo3_ffi::Py_ssize_t,
pub ob_type: *mut pyo3_ffi::PyTypeObject,
pub lv_tag: usize,
pub ob_digit: u8,
}

#[cfg(Py_3_12)]
pub fn pylong_is_zero(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { (*(ptr as *mut PyLongObject)).lv_tag & SIGN_MASK == SIGN_ZERO }
}

#[cfg(not(Py_3_12))]
pub fn pylong_is_zero(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { (*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size == 0 }
}

#[cfg(Py_3_12)]
pub fn pylong_is_unsigned(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { (*(ptr as *mut PyLongObject)).lv_tag & SIGN_MASK == SIGN_POSITIVE }
}

#[cfg(not(Py_3_12))]
pub fn pylong_is_unsigned(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { (*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size > 0 }
}
2 changes: 2 additions & 0 deletions src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ mod bytes;
mod dict;
mod fragment;
mod list;
mod long;

pub use buffer::*;
pub use bytes::*;
pub use dict::*;
pub use fragment::{orjson_fragmenttype_new, Fragment};
pub use list::PyListIter;
pub use long::{pylong_is_unsigned, pylong_is_zero};
47 changes: 11 additions & 36 deletions src/serialize/int.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use crate::ffi::{pylong_is_unsigned, pylong_is_zero};
use crate::serialize::error::*;
use serde::ser::{Serialize, Serializer};

Expand All @@ -20,51 +21,25 @@ impl IntSerializer {
}

impl Serialize for IntSerializer {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let val = ffi!(PyLong_AsLongLong(self.ptr));
if val == -1 {
if unlikely!(!ffi!(PyErr_Occurred()).is_null()) {
UIntSerializer::new(self.ptr).serialize(serializer)
if pylong_is_zero(self.ptr) {
serializer.serialize_u64(0)
} else if pylong_is_unsigned(self.ptr) {
let val = ffi!(PyLong_AsUnsignedLongLong(self.ptr));
if unlikely!(val == u64::MAX) && !ffi!(PyErr_Occurred()).is_null() {
err!(SerializeError::Integer64Bits)
} else {
serializer.serialize_i64(val)
serializer.serialize_u64(val)
}
} else {
serializer.serialize_i64(val)
}
}
}

#[repr(transparent)]
pub struct UIntSerializer {
ptr: *mut pyo3_ffi::PyObject,
}

impl UIntSerializer {
pub fn new(ptr: *mut pyo3_ffi::PyObject) -> Self {
UIntSerializer { ptr: ptr }
}
}

impl Serialize for UIntSerializer {
#[inline(never)]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ffi!(PyErr_Clear());
let val = ffi!(PyLong_AsUnsignedLongLong(self.ptr));
if unlikely!(val == u64::MAX) {
if ffi!(PyErr_Occurred()).is_null() {
serializer.serialize_u64(val)
} else {
let val = ffi!(PyLong_AsLongLong(self.ptr));
if unlikely!(val == -1) && !ffi!(PyErr_Occurred()).is_null() {
err!(SerializeError::Integer64Bits)
}
} else {
serializer.serialize_u64(val)
serializer.serialize_i64(val)
}
}
}
Expand Down

0 comments on commit d1cd27e

Please sign in to comment.