Skip to content

Commit

Permalink
inline_int
Browse files Browse the repository at this point in the history
  • Loading branch information
ijl committed Oct 19, 2024
1 parent 3be7253 commit 01ba440
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 22 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ yyjson = []
intrinsics = []
optimize = []
strict_provenance = []
inline_int = []

[dependencies]
arrayvec = { version = "0.7", default-features = false, features = ["std", "serde"] }
Expand Down
3 changes: 3 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ fn main() {
}
}

#[cfg(target_pointer_width = "64")]
println!("cargo:rustc-cfg=feature=\"inline_int\"");

if env::var("ORJSON_DISABLE_YYJSON").is_ok() {
if env::var("CARGO_FEATURE_YYJSON").is_ok() {
panic!("ORJSON_DISABLE_YYJSON and --features=yyjson both enabled.")
Expand Down
19 changes: 10 additions & 9 deletions src/ffi/long.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ const SIGN_ZERO: usize = 1;
#[allow(non_upper_case_globals)]
const NON_SIZE_BITS: usize = 3;

#[cfg(Py_3_12)]
#[cfg(all(Py_3_12, feature = "inline_int"))]
#[repr(C)]
pub struct _PyLongValue {
pub lv_tag: usize,
pub ob_digit: u32,
}

#[cfg(Py_3_12)]
#[cfg(all(Py_3_12, feature = "inline_int"))]
#[repr(C)]
pub struct PyLongObject {
pub ob_base: pyo3_ffi::PyObject,
pub long_value: _PyLongValue,
}

#[cfg(not(Py_3_12))]
#[cfg(all(not(Py_3_12), feature = "inline_int"))]
#[repr(C)]
pub struct PyLongObject {
pub ob_base: pyo3_ffi::PyVarObject,
Expand All @@ -47,30 +47,31 @@ pub fn pylong_is_unsigned(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { (*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size > 0 }
}

#[cfg(Py_3_12)]
#[cfg(all(Py_3_12, feature = "inline_int"))]
#[inline(always)]
pub fn pylong_fits_in_i32(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { (*(ptr as *mut PyLongObject)).long_value.lv_tag < (2 << NON_SIZE_BITS) }
}

#[cfg(not(Py_3_12))]
#[cfg(all(not(Py_3_12), feature = "inline_int"))]
#[inline(always)]
pub fn pylong_fits_in_i32(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { isize::abs((*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size) == 1 }
}

#[cfg(Py_3_12)]
#[cfg(all(Py_3_12, feature = "inline_int"))]
#[inline(always)]
pub fn pylong_is_zero(ptr: *mut pyo3_ffi::PyObject) -> bool {
unsafe { (*(ptr as *mut PyLongObject)).long_value.lv_tag & SIGN_MASK == SIGN_ZERO }
}
#[cfg(not(Py_3_12))]

#[cfg(all(not(Py_3_12), feature = "inline_int"))]
#[inline(always)]
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)]
#[cfg(all(Py_3_12, feature = "inline_int"))]
#[inline(always)]
pub fn pylong_get_inline_value(ptr: *mut pyo3_ffi::PyObject) -> i64 {
unsafe {
Expand All @@ -82,7 +83,7 @@ pub fn pylong_get_inline_value(ptr: *mut pyo3_ffi::PyObject) -> i64 {
}
}

#[cfg(not(Py_3_12))]
#[cfg(all(not(Py_3_12), feature = "inline_int"))]
#[inline(always)]
pub fn pylong_get_inline_value(ptr: *mut pyo3_ffi::PyObject) -> i64 {
unsafe {
Expand Down
4 changes: 3 additions & 1 deletion src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ pub mod yyjson;
pub use buffer::*;
pub use bytes::*;
pub use fragment::{orjson_fragmenttype_new, Fragment};
pub use long::{pylong_fits_in_i32, pylong_get_inline_value, pylong_is_unsigned, pylong_is_zero};
pub use long::pylong_is_unsigned;
#[cfg(feature = "inline_int")]
pub use long::{pylong_fits_in_i32, pylong_get_inline_value, pylong_is_zero};
56 changes: 44 additions & 12 deletions src/serialize/per_type/int.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
// SPDX-License-Identifier: (Apache-2.0 OR MIT)

use crate::ffi::{pylong_fits_in_i32, pylong_get_inline_value, pylong_is_unsigned, pylong_is_zero};
use crate::opt::{Opt, STRICT_INTEGER};
use crate::serialize::error::SerializeError;
use serde::ser::{Serialize, Serializer};

use core::ffi::c_uchar;
use core::mem::transmute;

// https://tools.ietf.org/html/rfc7159#section-6
// "[-(2**53)+1, (2**53)-1]"
const STRICT_INT_MIN: i64 = -9007199254740991;
Expand All @@ -29,26 +25,27 @@ impl IntSerializer {

impl Serialize for IntSerializer {
#[inline(always)]
#[cfg(feature = "inline_int")]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
unsafe {
if pylong_is_zero(self.ptr) {
if crate::ffi::pylong_is_zero(self.ptr) {
return serializer.serialize_bytes(b"0");
}
let is_signed = !pylong_is_unsigned(self.ptr) as i32;
if pylong_fits_in_i32(self.ptr) {
let is_signed = !crate::ffi::pylong_is_unsigned(self.ptr) as i32;
if crate::ffi::pylong_fits_in_i32(self.ptr) {
if is_signed == 0 {
serializer.serialize_u64(pylong_get_inline_value(self.ptr) as u64)
serializer.serialize_u64(crate::ffi::pylong_get_inline_value(self.ptr) as u64)
} else {
serializer.serialize_i64(pylong_get_inline_value(self.ptr) as i64)
serializer.serialize_i64(crate::ffi::pylong_get_inline_value(self.ptr) as i64)
}
} else {
let mut buffer: [u8; 8] = [0; 8];
let ret = pyo3_ffi::_PyLong_AsByteArray(
self.ptr as *mut pyo3_ffi::PyLongObject,
buffer.as_mut_ptr() as *mut c_uchar,
buffer.as_mut_ptr() as *mut core::ffi::c_uchar,
8,
1,
is_signed,
Expand All @@ -58,15 +55,15 @@ impl Serialize for IntSerializer {
err!(SerializeError::Integer64Bits)
}
if is_signed == 0 {
let val = transmute::<[u8; 8], u64>(buffer);
let val = core::mem::transmute::<[u8; 8], u64>(buffer);
if unlikely!(opt_enabled!(self.opts, STRICT_INTEGER))
&& val > STRICT_INT_MAX as u64
{
err!(SerializeError::Integer53Bits)
}
serializer.serialize_u64(val)
} else {
let val = transmute::<[u8; 8], i64>(buffer);
let val = core::mem::transmute::<[u8; 8], i64>(buffer);
if unlikely!(opt_enabled!(self.opts, STRICT_INTEGER))
&& !(STRICT_INT_MIN..=STRICT_INT_MAX).contains(&val)
{
Expand All @@ -77,4 +74,39 @@ impl Serialize for IntSerializer {
}
}
}

#[inline(always)]
#[cfg(not(feature = "inline_int"))]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
unsafe {
if crate::ffi::pylong_is_unsigned(self.ptr) {
let val = ffi!(PyLong_AsUnsignedLongLong(self.ptr));
if unlikely!(val == u64::MAX) && !ffi!(PyErr_Occurred()).is_null() {
ffi!(PyErr_Clear());
err!(SerializeError::Integer64Bits)
} else if unlikely!(opt_enabled!(self.opts, STRICT_INTEGER))
&& val > STRICT_INT_MAX as u64
{
err!(SerializeError::Integer53Bits)
} else {
serializer.serialize_u64(val)
}
} else {
let val = ffi!(PyLong_AsLongLong(self.ptr));
if unlikely!(val == -1) && !ffi!(PyErr_Occurred()).is_null() {
ffi!(PyErr_Clear());
err!(SerializeError::Integer64Bits)
} else if unlikely!(opt_enabled!(self.opts, STRICT_INTEGER))
&& !(STRICT_INT_MIN..=STRICT_INT_MAX).contains(&val)
{
err!(SerializeError::Integer53Bits)
} else {
serializer.serialize_i64(val)
}
}
}
}
}

0 comments on commit 01ba440

Please sign in to comment.