Skip to content

Commit 92f7b8c

Browse files
committed
int uses _PyLong_AsByteArray()
1 parent aec6e70 commit 92f7b8c

File tree

10 files changed

+112
-123
lines changed

10 files changed

+112
-123
lines changed

src/ffi/long.rs

+37-39
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,42 @@
33
// longintrepr.h, _longobject, _PyLongValue
44

55
#[cfg(Py_3_12)]
6+
#[allow(non_upper_case_globals)]
67
const SIGN_MASK: usize = 3;
8+
79
#[cfg(Py_3_12)]
10+
#[allow(non_upper_case_globals)]
811
const SIGN_ZERO: usize = 1;
912

1013
#[cfg(Py_3_12)]
1114
#[allow(non_upper_case_globals)]
12-
const _PyLong_NON_SIZE_BITS: usize = 3;
15+
const NON_SIZE_BITS: usize = 3;
1316

1417
#[cfg(Py_3_12)]
1518
#[repr(C)]
16-
struct _PyLongValue {
19+
pub struct _PyLongValue {
1720
pub lv_tag: usize,
1821
pub ob_digit: u32,
1922
}
2023

2124
#[cfg(Py_3_12)]
2225
#[repr(C)]
23-
struct PyLongObject {
24-
pub ob_refcnt: pyo3_ffi::Py_ssize_t,
25-
pub ob_type: *mut pyo3_ffi::PyTypeObject,
26+
pub struct PyLongObject {
27+
pub ob_base: pyo3_ffi::PyObject,
2628
pub long_value: _PyLongValue,
2729
}
2830

29-
#[cfg(Py_3_12)]
30-
#[inline(always)]
31-
pub fn pylong_is_zero(ptr: *mut pyo3_ffi::PyObject) -> bool {
32-
unsafe { (*(ptr as *mut PyLongObject)).long_value.lv_tag & SIGN_MASK == SIGN_ZERO }
33-
}
34-
3531
#[cfg(not(Py_3_12))]
36-
#[inline(always)]
37-
pub fn pylong_is_zero(ptr: *mut pyo3_ffi::PyObject) -> bool {
38-
unsafe { (*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size == 0 }
32+
#[repr(C)]
33+
pub struct PyLongObject {
34+
pub ob_base: pyo3_ffi::PyVarObject,
35+
pub ob_digit: u32,
3936
}
4037

4138
#[cfg(Py_3_12)]
4239
#[inline(always)]
4340
pub fn pylong_is_unsigned(ptr: *mut pyo3_ffi::PyObject) -> bool {
44-
unsafe {
45-
1 - (((*(ptr as *mut PyLongObject)).long_value.lv_tag & _PyLong_NON_SIZE_BITS) as isize) > 0
46-
}
41+
unsafe { (*(ptr as *mut PyLongObject)).long_value.lv_tag & SIGN_MASK == 0 }
4742
}
4843

4944
#[cfg(not(Py_3_12))]
@@ -54,41 +49,44 @@ pub fn pylong_is_unsigned(ptr: *mut pyo3_ffi::PyObject) -> bool {
5449

5550
#[cfg(Py_3_12)]
5651
#[inline(always)]
57-
fn pylong_is_compact(ptr: *mut pyo3_ffi::PyObject) -> bool {
58-
unsafe { (*(ptr as *mut PyLongObject)).long_value.lv_tag < (2 << _PyLong_NON_SIZE_BITS) }
52+
pub fn pylong_fits_in_i32(ptr: *mut pyo3_ffi::PyObject) -> bool {
53+
unsafe { (*(ptr as *mut PyLongObject)).long_value.lv_tag < (2 << NON_SIZE_BITS) }
5954
}
6055

61-
#[cfg(Py_3_12)]
56+
#[cfg(not(Py_3_12))]
6257
#[inline(always)]
63-
pub fn pylong_value_unsigned(ptr: *mut pyo3_ffi::PyObject) -> u64 {
64-
if pylong_is_compact(ptr) == true {
65-
unsafe { (*(ptr as *mut PyLongObject)).long_value.ob_digit as u64 }
66-
} else {
67-
ffi!(PyLong_AsUnsignedLongLong(ptr))
68-
}
58+
pub fn pylong_fits_in_i32(ptr: *mut pyo3_ffi::PyObject) -> bool {
59+
unsafe { isize::abs((*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size) == 1 }
6960
}
7061

71-
#[cfg(not(Py_3_12))]
62+
#[cfg(Py_3_12)]
7263
#[inline(always)]
73-
pub fn pylong_value_unsigned(ptr: *mut pyo3_ffi::PyObject) -> u64 {
74-
ffi!(PyLong_AsUnsignedLongLong(ptr))
64+
pub fn pylong_is_zero(ptr: *mut pyo3_ffi::PyObject) -> bool {
65+
unsafe { (*(ptr as *mut PyLongObject)).long_value.lv_tag & SIGN_MASK == SIGN_ZERO }
7566
}
76-
7767
#[cfg(not(Py_3_12))]
7868
#[inline(always)]
79-
pub fn pylong_value_signed(ptr: *mut pyo3_ffi::PyObject) -> i64 {
80-
ffi!(PyLong_AsLongLong(ptr))
69+
pub fn pylong_is_zero(ptr: *mut pyo3_ffi::PyObject) -> bool {
70+
unsafe { (*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size == 0 }
8171
}
8272

8373
#[cfg(Py_3_12)]
8474
#[inline(always)]
85-
pub fn pylong_value_signed(ptr: *mut pyo3_ffi::PyObject) -> i64 {
86-
if pylong_is_compact(ptr) == true {
87-
unsafe {
88-
let sign = 1 - ((*(ptr as *mut PyLongObject)).long_value.lv_tag & SIGN_MASK) as i64;
89-
sign * (*(ptr as *mut PyLongObject)).long_value.ob_digit as i64
75+
pub fn pylong_get_inline_value(ptr: *mut pyo3_ffi::PyObject) -> i64 {
76+
unsafe {
77+
if pylong_is_unsigned(ptr) {
78+
(*(ptr as *mut PyLongObject)).long_value.ob_digit as i64
79+
} else {
80+
-1 * (*(ptr as *mut PyLongObject)).long_value.ob_digit as i64
9081
}
91-
} else {
92-
ffi!(PyLong_AsLongLong(ptr))
82+
}
83+
}
84+
85+
#[cfg(not(Py_3_12))]
86+
#[inline(always)]
87+
pub fn pylong_get_inline_value(ptr: *mut pyo3_ffi::PyObject) -> i64 {
88+
unsafe {
89+
(*(ptr as *mut pyo3_ffi::PyVarObject)).ob_size as i64
90+
* (*(ptr as *mut PyLongObject)).ob_digit as i64
9391
}
9492
}

src/ffi/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ pub mod yyjson;
1010
pub use buffer::*;
1111
pub use bytes::*;
1212
pub use fragment::{orjson_fragmenttype_new, Fragment};
13-
pub use long::{pylong_is_unsigned, pylong_is_zero, pylong_value_signed, pylong_value_unsigned};
13+
pub use long::{pylong_fits_in_i32, pylong_get_inline_value, pylong_is_unsigned, pylong_is_zero};

src/serialize/per_type/dict.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use crate::serialize::obtype::{pyobject_to_obtype, ObType};
66
use crate::serialize::per_type::datetimelike::DateTimeLike;
77
use crate::serialize::per_type::{
88
BoolSerializer, DataclassGenericSerializer, Date, DateTime, DateTimeBuffer, DefaultSerializer,
9-
EnumSerializer, FloatSerializer, FragmentSerializer, Int53Serializer, IntSerializer,
10-
ListTupleSerializer, NoneSerializer, NumpyScalar, NumpySerializer, StrSerializer,
11-
StrSubclassSerializer, Time, ZeroListSerializer, UUID,
9+
EnumSerializer, FloatSerializer, FragmentSerializer, IntSerializer, ListTupleSerializer,
10+
NoneSerializer, NumpyScalar, NumpySerializer, StrSerializer, StrSubclassSerializer, Time,
11+
ZeroListSerializer, UUID,
1212
};
1313
use crate::serialize::serializer::PyObjectSerializer;
1414
use crate::serialize::state::SerializerState;
@@ -100,13 +100,8 @@ macro_rules! impl_serialize_entry {
100100
$map.serialize_value(&StrSubclassSerializer::new($value))?;
101101
}
102102
ObType::Int => {
103-
if unlikely!(opt_enabled!($self.state.opts(), STRICT_INTEGER)) {
104-
$map.serialize_key($key).unwrap();
105-
$map.serialize_value(&Int53Serializer::new($value))?;
106-
} else {
107-
$map.serialize_key($key).unwrap();
108-
$map.serialize_value(&IntSerializer::new($value))?;
109-
}
103+
$map.serialize_key($key).unwrap();
104+
$map.serialize_value(&IntSerializer::new($value, $self.state.opts()))?;
110105
}
111106
ObType::None => {
112107
$map.serialize_key($key).unwrap();

src/serialize/per_type/int.rs

+51-50
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,80 @@
11
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
22

3-
use crate::ffi::{pylong_is_unsigned, pylong_is_zero, pylong_value_signed, pylong_value_unsigned};
3+
use crate::ffi::{pylong_fits_in_i32, pylong_get_inline_value, pylong_is_unsigned, pylong_is_zero};
4+
use crate::opt::{Opt, STRICT_INTEGER};
45
use crate::serialize::error::SerializeError;
56
use serde::ser::{Serialize, Serializer};
67

8+
use core::ffi::c_uchar;
9+
use core::mem::transmute;
10+
711
// https://tools.ietf.org/html/rfc7159#section-6
812
// "[-(2**53)+1, (2**53)-1]"
913
const STRICT_INT_MIN: i64 = -9007199254740991;
1014
const STRICT_INT_MAX: i64 = 9007199254740991;
1115

12-
#[repr(transparent)]
1316
pub struct IntSerializer {
1417
ptr: *mut pyo3_ffi::PyObject,
18+
opts: Opt,
1519
}
1620

1721
impl IntSerializer {
18-
pub fn new(ptr: *mut pyo3_ffi::PyObject) -> Self {
19-
IntSerializer { ptr: ptr }
22+
pub fn new(ptr: *mut pyo3_ffi::PyObject, opts: Opt) -> Self {
23+
IntSerializer {
24+
ptr: ptr,
25+
opts: opts,
26+
}
2027
}
2128
}
2229

2330
impl Serialize for IntSerializer {
24-
#[inline(never)]
31+
#[inline(always)]
2532
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2633
where
2734
S: Serializer,
2835
{
29-
if pylong_is_zero(self.ptr) {
30-
serializer.serialize_u64(0)
31-
} else if pylong_is_unsigned(self.ptr) {
32-
let val = pylong_value_unsigned(self.ptr);
33-
if unlikely!(val == u64::MAX) && !ffi!(PyErr_Occurred()).is_null() {
34-
err!(SerializeError::Integer64Bits)
35-
} else {
36-
serializer.serialize_u64(val)
36+
unsafe {
37+
if pylong_is_zero(self.ptr) {
38+
return serializer.serialize_bytes(b"0");
3739
}
38-
} else {
39-
let val = pylong_value_signed(self.ptr);
40-
if unlikely!(val == -1) && !ffi!(PyErr_Occurred()).is_null() {
41-
err!(SerializeError::Integer64Bits)
42-
}
43-
serializer.serialize_i64(val)
44-
}
45-
}
46-
}
47-
48-
#[repr(transparent)]
49-
pub struct Int53Serializer {
50-
ptr: *mut pyo3_ffi::PyObject,
51-
}
52-
53-
impl Int53Serializer {
54-
pub fn new(ptr: *mut pyo3_ffi::PyObject) -> Self {
55-
Int53Serializer { ptr: ptr }
56-
}
57-
}
58-
59-
impl Serialize for Int53Serializer {
60-
#[cold]
61-
#[inline(never)]
62-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
63-
where
64-
S: Serializer,
65-
{
66-
let val = pylong_value_signed(self.ptr);
67-
if unlikely!(val == -1) {
68-
if ffi!(PyErr_Occurred()).is_null() {
69-
serializer.serialize_i64(val)
40+
let is_signed = !pylong_is_unsigned(self.ptr) as i32;
41+
if pylong_fits_in_i32(self.ptr) {
42+
if is_signed == 0 {
43+
serializer.serialize_u64(pylong_get_inline_value(self.ptr) as u64)
44+
} else {
45+
serializer.serialize_i64(pylong_get_inline_value(self.ptr) as i64)
46+
}
7047
} else {
71-
err!(SerializeError::Integer53Bits)
48+
let mut buffer: [u8; 8] = [0; 8];
49+
let ret = pyo3_ffi::_PyLong_AsByteArray(
50+
self.ptr as *mut pyo3_ffi::PyLongObject,
51+
buffer.as_mut_ptr() as *mut c_uchar,
52+
8,
53+
1,
54+
is_signed,
55+
);
56+
if unlikely!(ret == -1) {
57+
ffi!(PyErr_Clear());
58+
err!(SerializeError::Integer64Bits)
59+
}
60+
if is_signed == 0 {
61+
let val = transmute::<[u8; 8], u64>(buffer);
62+
if unlikely!(opt_enabled!(self.opts, STRICT_INTEGER))
63+
&& val > STRICT_INT_MAX as u64
64+
{
65+
err!(SerializeError::Integer53Bits)
66+
}
67+
serializer.serialize_u64(val)
68+
} else {
69+
let val = transmute::<[u8; 8], i64>(buffer);
70+
if unlikely!(opt_enabled!(self.opts, STRICT_INTEGER))
71+
&& !(STRICT_INT_MIN..=STRICT_INT_MAX).contains(&val)
72+
{
73+
err!(SerializeError::Integer53Bits)
74+
}
75+
serializer.serialize_i64(val)
76+
}
7277
}
73-
} else if !(STRICT_INT_MIN..=STRICT_INT_MAX).contains(&val) {
74-
err!(SerializeError::Integer53Bits)
75-
} else {
76-
serializer.serialize_i64(val)
7778
}
7879
}
7980
}

src/serialize/per_type/list.rs

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
22

3-
use crate::opt::STRICT_INTEGER;
43
use crate::serialize::error::SerializeError;
54
use crate::serialize::obtype::{pyobject_to_obtype, ObType};
65
use crate::serialize::per_type::{
76
BoolSerializer, DataclassGenericSerializer, Date, DateTime, DefaultSerializer,
8-
DictGenericSerializer, EnumSerializer, FloatSerializer, FragmentSerializer, Int53Serializer,
9-
IntSerializer, NoneSerializer, NumpyScalar, NumpySerializer, StrSerializer,
10-
StrSubclassSerializer, Time, UUID,
7+
DictGenericSerializer, EnumSerializer, FloatSerializer, FragmentSerializer, IntSerializer,
8+
NoneSerializer, NumpyScalar, NumpySerializer, StrSerializer, StrSubclassSerializer, Time, UUID,
119
};
1210
use crate::serialize::serializer::PyObjectSerializer;
1311
use crate::serialize::state::SerializerState;
@@ -102,11 +100,7 @@ impl Serialize for ListTupleSerializer {
102100
seq.serialize_element(&StrSubclassSerializer::new(value))?;
103101
}
104102
ObType::Int => {
105-
if unlikely!(opt_enabled!(self.state.opts(), STRICT_INTEGER)) {
106-
seq.serialize_element(&Int53Serializer::new(value))?;
107-
} else {
108-
seq.serialize_element(&IntSerializer::new(value))?;
109-
}
103+
seq.serialize_element(&IntSerializer::new(value, self.state.opts()))?;
110104
}
111105
ObType::None => {
112106
seq.serialize_element(&NoneSerializer::new()).unwrap();

src/serialize/per_type/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub use default::DefaultSerializer;
2424
pub use dict::DictGenericSerializer;
2525
pub use float::FloatSerializer;
2626
pub use fragment::FragmentSerializer;
27-
pub use int::{Int53Serializer, IntSerializer};
27+
pub use int::IntSerializer;
2828
pub use list::{ListTupleSerializer, ZeroListSerializer};
2929
pub use none::NoneSerializer;
3030
pub use numpy::{is_numpy_array, is_numpy_scalar, NumpyScalar, NumpySerializer};

src/serialize/serializer.rs

+5-11
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// SPDX-License-Identifier: (Apache-2.0 OR MIT)
22

3-
use crate::opt::{Opt, APPEND_NEWLINE, INDENT_2, STRICT_INTEGER};
3+
use crate::opt::{Opt, APPEND_NEWLINE, INDENT_2};
44
use crate::serialize::obtype::{pyobject_to_obtype, ObType};
55
use crate::serialize::per_type::{
66
BoolSerializer, DataclassGenericSerializer, Date, DateTime, DefaultSerializer,
7-
DictGenericSerializer, EnumSerializer, FloatSerializer, FragmentSerializer, Int53Serializer,
8-
IntSerializer, ListTupleSerializer, NoneSerializer, NumpyScalar, NumpySerializer,
9-
StrSerializer, StrSubclassSerializer, Time, ZeroListSerializer, UUID,
7+
DictGenericSerializer, EnumSerializer, FloatSerializer, FragmentSerializer, IntSerializer,
8+
ListTupleSerializer, NoneSerializer, NumpyScalar, NumpySerializer, StrSerializer,
9+
StrSubclassSerializer, Time, ZeroListSerializer, UUID,
1010
};
1111
use crate::serialize::state::SerializerState;
1212
use crate::serialize::writer::{to_writer, to_writer_pretty, BytesWriter};
@@ -68,13 +68,7 @@ impl Serialize for PyObjectSerializer {
6868
match pyobject_to_obtype(self.ptr, self.state.opts()) {
6969
ObType::Str => StrSerializer::new(self.ptr).serialize(serializer),
7070
ObType::StrSubclass => StrSubclassSerializer::new(self.ptr).serialize(serializer),
71-
ObType::Int => {
72-
if unlikely!(opt_enabled!(self.state.opts(), STRICT_INTEGER)) {
73-
Int53Serializer::new(self.ptr).serialize(serializer)
74-
} else {
75-
IntSerializer::new(self.ptr).serialize(serializer)
76-
}
77-
}
71+
ObType::Int => IntSerializer::new(self.ptr, self.state.opts()).serialize(serializer),
7872
ObType::None => NoneSerializer::new().serialize(serializer),
7973
ObType::Float => FloatSerializer::new(self.ptr).serialize(serializer),
8074
ObType::Bool => BoolSerializer::new(self.ptr).serialize(serializer),

src/serialize/writer/json.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ where
134134
.map_err(Error::io)
135135
}
136136
}
137-
#[inline(never)]
137+
#[inline]
138138
fn serialize_f64(self, value: f64) -> Result<()> {
139139
if unlikely!(value.is_infinite() || value.is_nan()) {
140140
self.serialize_unit()

test/test_error.py

-1
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,3 @@ def test_dumps_normalize_exception(self):
188188
with pytest.raises(orjson.JSONEncodeError) as exc_info:
189189
orjson.dumps(10**60)
190190
assert exc_info.type == orjson.JSONEncodeError
191-
assert isinstance(exc_info.value.__cause__, OverflowError)

test/test_type.py

+8
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,14 @@ def test_int_53_exc_usize(self):
420420
with pytest.raises(orjson.JSONEncodeError):
421421
orjson.dumps(val, option=orjson.OPT_STRICT_INTEGER)
422422

423+
def test_int_53_exc_128(self):
424+
"""
425+
int 53-bit exception on 128-bit
426+
"""
427+
val = 2**65
428+
with pytest.raises(orjson.JSONEncodeError):
429+
orjson.dumps(val, option=orjson.OPT_STRICT_INTEGER)
430+
423431
def test_int_64(self):
424432
"""
425433
int 64-bit

0 commit comments

Comments
 (0)