diff --git a/Cargo.lock b/Cargo.lock index 743e293f161..7c2a14a3db2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3447,6 +3447,7 @@ version = "0.5.0" dependencies = [ "bitflags 2.13.0", "itertools 0.14.0", + "malachite-bigint", "num-complex", "pyo3", "rustpython-pylib", diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index 601989faa1d..3bdeaafc67d 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] bitflags = { workspace = true } itertools = { workspace = true } +malachite-bigint = { workspace = true } num-complex = { workspace = true } rustpython-vm = { workspace = true, features = ["threading", "compiler", "importlib", "host_env"] } rustpython-stdlib = {workspace = true, features = ["threading"] } diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index 8c9fe5e1acb..f69f6c4bc27 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -1,7 +1,8 @@ use crate::PyObject; use crate::object::define_py_check; use crate::pystate::with_vm; -use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong}; +use core::ffi::{c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; +use malachite_bigint::{BigInt, BigUint, Sign}; use rustpython_vm::PyResult; use rustpython_vm::builtins::PyInt; @@ -64,6 +65,172 @@ pub unsafe extern "C" fn PyLong_AsUnsignedLongLong(obj: *mut PyObject) -> c_ulon }) } +#[repr(C)] +pub struct PyLongLayout { + pub bits_per_digit: u8, + pub digit_size: u8, + pub digits_order: i8, + pub digit_endianness: i8, +} + +#[repr(C)] +#[derive(Default)] +pub struct PyLongExport { + pub value: i64, + pub negative: u8, + pub ndigits: isize, + pub digits: *const c_void, + _reserved: *mut Vec, +} + +pub struct PyLongWriter { + negative: bool, + digits: Vec, +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_GetNativeLayout() -> *const PyLongLayout { + const NATIVE_LONG_LAYOUT: PyLongLayout = PyLongLayout { + bits_per_digit: 32, + digit_size: 4, + digits_order: -1, + digit_endianness: if cfg!(target_endian = "little") { + -1 + } else { + 1 + }, + }; + &NATIVE_LONG_LAYOUT +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_Export( + obj: *mut PyObject, + export_long: *mut PyLongExport, +) -> c_int { + with_vm::, _>(|vm| { + let py_int = unsafe { &*obj }.try_downcast_ref::(vm)?; + let bigint = py_int.as_bigint(); + + if let Ok(value) = i64::try_from(bigint) { + unsafe { + *export_long = PyLongExport { + value, + ..Default::default() + }; + } + return Ok(()); + } + + let (sign, digits) = bigint.to_u32_digits(); + let boxed_digits = Box::new(digits); + let ndigits = boxed_digits.len().try_into().map_err(|_| { + vm.new_overflow_error("PyLong_Export: too many digits to fit into Py_ssize_t") + })?; + + unsafe { + *export_long = PyLongExport { + value: 0, + negative: u8::from(matches!(sign, Sign::Minus)), + ndigits, + digits: boxed_digits.as_ptr().cast(), + _reserved: Box::into_raw(boxed_digits), + }; + } + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_FreeExport(export_long: *mut PyLongExport) { + if export_long.is_null() { + return; + } + + let export_long = unsafe { &mut *export_long }; + if !export_long._reserved.is_null() { + unsafe { + drop(Box::from_raw(export_long._reserved)); + } + } + core::mem::take(export_long); +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLongWriter_Create( + negative: c_int, + ndigits: isize, + digits: *mut *mut c_void, +) -> *mut PyLongWriter { + with_vm::, _>(|vm| { + if ndigits <= 0 { + return Err(vm.new_value_error("PyLongWriter_Create: ndigits must be greater than 0")); + } + if digits.is_null() { + return Err(vm.new_system_error("PyLongWriter_Create: digits must not be null")); + } + if negative != 0 && negative != 1 { + return Err(vm.new_value_error("PyLongWriter_Create: negative must be 0 or 1")); + } + + let ndigits = ndigits + .try_into() + .map_err(|_| vm.new_overflow_error("PyLongWriter_Create: ndigits out of range"))?; + + let mut writer = Box::new(PyLongWriter { + negative: negative == 1, + digits: vec![0; ndigits], + }); + + unsafe { + *digits = writer.digits.as_mut_ptr().cast(); + } + + Ok(Box::into_raw(writer).cast()) + }) + .cast() +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLongWriter_Finish(writer: *mut PyLongWriter) -> *mut PyObject { + with_vm(|vm| { + if writer.is_null() { + return Err(vm.new_system_error("PyLongWriter_Finish: writer must not be null")); + } + + let writer = unsafe { Box::from_raw(writer) }; + let mut digits = writer.digits; + while matches!(digits.last(), Some(0)) { + digits.pop(); + } + + if digits.is_empty() { + return Ok(vm.ctx.new_int(0)); + } + + let magnitude = BigUint::new(digits); + let sign = if writer.negative { + Sign::Minus + } else { + Sign::Plus + }; + + let value = BigInt::from_biguint(sign, magnitude); + Ok(vm.ctx.new_int(value)) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLongWriter_Discard(writer: *mut PyLongWriter) { + if writer.is_null() { + return; + } + + unsafe { + drop(Box::from_raw(writer)); + } +} + #[cfg(false)] mod tests { use pyo3::prelude::*;