Skip to content

Commit

Permalink
Implement 128bit integer conversion for limited API
Browse files Browse the repository at this point in the history
  • Loading branch information
kngwyu committed Dec 12, 2020
1 parent 9aa70f7 commit 09423d0
Showing 1 changed file with 93 additions and 54 deletions.
147 changes: 93 additions & 54 deletions src/types/num.rs
Expand Up @@ -157,23 +157,13 @@ int_convert_u64_or_i64!(
ffi::PyLong_AsUnsignedLongLong
);

// manual implementation for 128bit integers
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
mod int128_conversion {
use crate::{
ffi, AsPyPointer, FromPyObject, IntoPy, PyAny, PyErr, PyNativeType, PyObject, PyResult,
Python, ToPyObject,
};
use std::os::raw::{c_int, c_uchar};

#[cfg(target_endian = "little")]
const IS_LITTLE_ENDIAN: c_int = 1;
#[cfg(not(target_endian = "little"))]
const IS_LITTLE_ENDIAN: c_int = 0;
mod fast_128bit_int_conversion {
use super::*;

// for 128bit Integers
macro_rules! int_convert_128 {
($rust_type: ty, $byte_size: expr, $is_signed: expr) => {
($rust_type: ty, $is_signed: expr) => {
impl ToPyObject for $rust_type {
#[inline]
fn to_object(&self, py: Python) -> PyObject {
Expand All @@ -183,11 +173,12 @@ mod int128_conversion {
impl IntoPy<PyObject> for $rust_type {
fn into_py(self, py: Python) -> PyObject {
unsafe {
let bytes = self.to_ne_bytes();
// Always use little endian
let bytes = self.to_le_bytes();
let obj = ffi::_PyLong_FromByteArray(
bytes.as_ptr() as *const c_uchar,
$byte_size,
IS_LITTLE_ENDIAN,
bytes.as_ptr() as *const std::os::raw::c_uchar,
16,
1,
$is_signed,
);
PyObject::from_owned_ptr(py, obj)
Expand All @@ -202,83 +193,131 @@ mod int128_conversion {
if num.is_null() {
return Err(PyErr::fetch(ob.py()));
}
let mut buffer = [0; $byte_size];
let mut buffer = [0; 16];
let ok = ffi::_PyLong_AsByteArray(
num as *mut ffi::PyLongObject,
buffer.as_mut_ptr(),
$byte_size,
IS_LITTLE_ENDIAN,
16,
1,
$is_signed,
);
if ok == -1 {
Err(PyErr::fetch(ob.py()))
} else {
Ok(<$rust_type>::from_ne_bytes(buffer))
Ok(<$rust_type>::from_le_bytes(buffer))
}
}
}
}
};
}

int_convert_128!(i128, 16, 1);
int_convert_128!(u128, 16, 0);
int_convert_128!(i128, 1);
int_convert_128!(u128, 0);
}

#[cfg(test)]
mod test {
use crate::{Python, ToPyObject};
// For ABI3 and PyPy, we implement the conversion using `eval`, which is slow.
#[cfg(any(Py_LIMITED_API, PyPy))]
mod slow_128bit_int_conversion {
use super::*;
use crate::types::{IntoPyDict, PyBytes};

#[test]
fn test_i128_max() {
let gil = Python::acquire_gil();
let py = gil.python();
// for 128bit Integers
macro_rules! int_convert_128 {
($rust_type: ty, $from_bytes: literal, $to_bytes: literal) => {
impl ToPyObject for $rust_type {
#[inline]
fn to_object(&self, py: Python) -> PyObject {
(*self).into_py(py)
}
}

impl IntoPy<PyObject> for $rust_type {
fn into_py(self, py: Python) -> PyObject {
// Always use little endian
let bytes = PyBytes::new(py, &self.to_le_bytes());
let dict = [("b", bytes)].into_py_dict(py);
py.eval($from_bytes, None, Some(dict)).unwrap().into_py(py)
}
}

impl<'source> FromPyObject<'source> for $rust_type {
fn extract(ob: &'source PyAny) -> PyResult<$rust_type> {
let py = ob.py();
let dict = [("i", ob)].into_py_dict(py);
let obj = py.eval($to_bytes, None, Some(dict))?;
let pybytes = obj.downcast::<PyBytes>()?;
let mut bytes = [0u8; 16];
bytes.copy_from_slice(pybytes.as_bytes());
Ok(<$rust_type>::from_le_bytes(bytes))
}
}
};
}

int_convert_128!(
i128,
"int.from_bytes(b, 'little', signed=True)",
"i.to_bytes(16, 'little', signed=True)"
);
int_convert_128!(
u128,
"int.from_bytes(b, 'little', signed=False)",
"i.to_bytes(16, 'little', signed=False)"
);
}

#[cfg(test)]
mod test_128bit_intergers {
use super::*;

#[test]
fn test_i128_max() {
Python::with_gil(|py| {
let v = std::i128::MAX;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<i128>(py).unwrap());
assert_eq!(v as u128, obj.extract::<u128>(py).unwrap());
assert!(obj.extract::<u64>(py).is_err());
}
})
}

#[test]
fn test_i128_min() {
let gil = Python::acquire_gil();
let py = gil.python();
#[test]
fn test_i128_min() {
Python::with_gil(|py| {
let v = std::i128::MIN;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<i128>(py).unwrap());
assert!(obj.extract::<i64>(py).is_err());
assert!(obj.extract::<u128>(py).is_err());
}
})
}

#[test]
fn test_u128_max() {
let gil = Python::acquire_gil();
let py = gil.python();
#[test]
fn test_u128_max() {
Python::with_gil(|py| {
let v = std::u128::MAX;
let obj = v.to_object(py);
assert_eq!(v, obj.extract::<u128>(py).unwrap());
assert!(obj.extract::<i128>(py).is_err());
}
})
}

#[test]
fn test_u128_overflow() {
use crate::{exceptions, ffi, PyObject};
use std::os::raw::c_uchar;
let gil = Python::acquire_gil();
let py = gil.python();
#[test]
#[cfg(not(any(Py_LIMITED_API, PyPy)))]
fn test_u128_overflow() {
use crate::{exceptions, ffi, PyObject};
use std::os::raw::c_uchar;
Python::with_gil(|py| {
let overflow_bytes: [c_uchar; 20] = [255; 20];
unsafe {
let obj = ffi::_PyLong_FromByteArray(
overflow_bytes.as_ptr() as *const c_uchar,
20,
super::IS_LITTLE_ENDIAN,
0,
);
let obj =
ffi::_PyLong_FromByteArray(overflow_bytes.as_ptr() as *const c_uchar, 20, 1, 0);
let obj = PyObject::from_owned_ptr(py, obj);
let err = obj.extract::<u128>(py).unwrap_err();
assert!(err.is_instance::<exceptions::PyOverflowError>(py));
}
}
})
}
}

Expand Down

0 comments on commit 09423d0

Please sign in to comment.