From 8c4cba25b7ca1f4e3faa319d9dd73a0123c50e26 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 12 Dec 2020 17:01:31 +0900 Subject: [PATCH] Implement 128bit integer conversion for limited API --- CHANGELOG.md | 1 + src/types/num.rs | 156 +++++++++++++++++++++++++++++++---------------- 2 files changed, 103 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39236e43..38f233c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Add FFI definitions for `PyBuffer_SizeFromFormat`, `PyObject_LengthHint`, `PyObject_CallNoArgs`, `PyObject_CallOneArg`, `PyObject_CallMethodNoArgs`, `PyObject_CallMethodOneArg`, `PyObject_VectorcallDict`, and `PyObject_VectorcallMethod`. [#1287](https://github.com/PyO3/pyo3/pull/1287) - Allow the use of a string literal in `#[pyclass(name = "string literal")]`. [#1295](https://github.com/PyO3/pyo3/pull/1295) - Add section about Python::check_signals to the FAQ page of the user guide. [#1301](https://github.com/PyO3/pyo3/pull/1301) +- Add conversions between u128/i128 and PyLong for abi3 and PyPy. [#1310](https://github.com/PyO3/pyo3/pull/1310) ### Changed - Change return type `PyType::name()` from `Cow` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152) diff --git a/src/types/num.rs b/src/types/num.rs index 09944d84..d27e53d1 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -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 { @@ -183,11 +173,12 @@ mod int128_conversion { impl IntoPy 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, + bytes.len(), + 1, $is_signed, ); PyObject::from_owned_ptr(py, obj) @@ -202,18 +193,18 @@ mod int128_conversion { if num.is_null() { return Err(PyErr::fetch(ob.py())); } - let mut buffer = [0; $byte_size]; + let mut buffer = [0; std::mem::size_of::<$rust_type>()]; let ok = ffi::_PyLong_AsByteArray( num as *mut ffi::PyLongObject, buffer.as_mut_ptr(), - $byte_size, - IS_LITTLE_ENDIAN, + buffer.len(), + 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)) } } } @@ -221,64 +212,121 @@ mod int128_conversion { }; } - 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 `call_method`. +#[cfg(any(Py_LIMITED_API, PyPy))] +mod slow_128bit_int_conversion { + use super::*; + use crate::types::{IntoPyDict, PyBytes, PyDict, PyType}; - #[test] - fn test_i128_max() { - let gil = Python::acquire_gil(); - let py = gil.python(); + // Returns `{'signed': True}` if is_signed else None + fn kwargs(py: Python, is_signed: bool) -> Option<&PyDict> { + if is_signed { + Some([("signed", is_signed)].into_py_dict(py)) + } else { + None + } + } + + // for 128bit Integers + macro_rules! int_convert_128 { + ($rust_type: ty, $is_signed: literal) => { + impl ToPyObject for $rust_type { + #[inline] + fn to_object(&self, py: Python) -> PyObject { + (*self).into_py(py) + } + } + + impl IntoPy for $rust_type { + fn into_py(self, py: Python) -> PyObject { + let bytes = PyBytes::new(py, &self.to_le_bytes()); + let longtype = unsafe { PyType::from_type_ptr(py, &mut ffi::PyLong_Type) }; + longtype + .call_method("from_bytes", (bytes, "little"), kwargs(py, $is_signed)) + .expect("Integer conversion (u128/i128 to PyLong) failed") + .into_py(py) + } + } + + impl<'source> FromPyObject<'source> for $rust_type { + fn extract(ob: &'source PyAny) -> PyResult<$rust_type> { + let py = ob.py(); + let num: &PyLong = unsafe { + crate::FromPyPointer::from_owned_ptr_or_err( + py, + ffi::PyNumber_Index(ob.as_ptr()), + ) + }?; + let mut bytes = [0u8; std::mem::size_of::<$rust_type>()]; + let pybytes: &PyBytes = num + .call_method("to_bytes", (bytes.len(), "little"), kwargs(py, $is_signed))? + .extract()?; + bytes.copy_from_slice(pybytes.as_bytes()); + Ok(<$rust_type>::from_le_bytes(bytes)) + } + } + }; + } + + int_convert_128!(i128, true); + int_convert_128!(u128, 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::(py).unwrap()); assert_eq!(v as u128, obj.extract::(py).unwrap()); assert!(obj.extract::(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::(py).unwrap()); assert!(obj.extract::(py).is_err()); assert!(obj.extract::(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::(py).unwrap()); assert!(obj.extract::(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::(py).unwrap_err(); assert!(err.is_instance::(py)); } - } + }) } }