From 8c3e70cf874692d3e0b929972a7be465d9f9c026 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 24 Jun 2026 13:50:51 +0200 Subject: [PATCH 1/3] Add more int functions to the c-api --- crates/capi/src/longobject.rs | 235 +++++++++++++++++++++++++++++++++- crates/capi/src/util.rs | 75 ++++++++++- crates/vm/src/builtins/int.rs | 22 +++- 3 files changed, 321 insertions(+), 11 deletions(-) diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index 8c9fe5e1acb..a2868feded7 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -1,9 +1,11 @@ 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 rustpython_vm::PyResult; -use rustpython_vm::builtins::PyInt; +use core::ffi::{CStr, c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; +use rustpython_vm::builtins::{PyInt, try_bigint_to_f64, try_f64_to_bigint}; +use rustpython_vm::common::int::bytes_to_int; +use rustpython_vm::protocol::handle_bytes_to_int_err; +use rustpython_vm::{AsObject, PyResult}; define_py_check!(fn PyLong_Check, types.int_type); define_py_check!(exact fn PyLong_CheckExact, types.int_type); @@ -38,6 +40,59 @@ pub extern "C" fn PyLong_FromUnsignedLongLong(value: c_ulonglong) -> *mut PyObje with_vm(|vm| vm.ctx.new_int(value)) } +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromDouble(value: c_double) -> *mut PyObject { + with_vm(|vm| Ok(vm.ctx.new_bigint(&try_f64_to_bigint(value, vm)?))) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromInt32(value: i32) -> *mut PyObject { + with_vm(|vm| vm.ctx.new_int(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromInt64(value: i64) -> *mut PyObject { + with_vm(|vm| vm.ctx.new_int(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromUInt32(value: u32) -> *mut PyObject { + with_vm(|vm| vm.ctx.new_int(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromUInt64(value: u64) -> *mut PyObject { + with_vm(|vm| vm.ctx.new_int(value)) +} + +#[unsafe(no_mangle)] +pub extern "C" fn PyLong_FromVoidPtr(ptr: *mut c_void) -> *mut PyObject { + with_vm(|vm| vm.ctx.new_int(ptr as usize)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_FromString( + str: *const c_char, + pend: *mut *mut c_char, + base: c_int, +) -> *mut PyObject { + with_vm(|vm| { + let bytes = unsafe { CStr::from_ptr(str) }.to_bytes(); + let parsed = bytes_to_int(bytes, base as u32, vm.state.int_max_str_digits.load()) + .map(|value| vm.ctx.new_bigint(&value)); + + if let Some(pend) = unsafe { pend.as_mut() } { + let end_offset = if parsed.is_ok() { bytes.len() } else { 0 }; + unsafe { *pend = bytes.as_ptr().add(end_offset).cast_mut().cast() }; + } + + parsed.map_err(|err| { + let obj = vm.ctx.new_bytes(bytes.to_vec()); + handle_bytes_to_int_err(err, obj.as_object(), vm) + }) + }) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long { with_vm::, _>(|vm| { @@ -50,12 +105,173 @@ pub unsafe extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long { }) } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsDouble(obj: *mut PyObject) -> c_double { + with_vm::, _>(|vm| { + let int = unsafe { &*obj }.try_downcast_ref::(vm)?; + try_bigint_to_f64(int.as_bigint(), vm) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsInt(obj: *mut PyObject) -> c_int { + with_vm::, _>(|vm| { + unsafe { &*obj } + .to_owned() + .try_index(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to C int")) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsInt32(obj: *mut PyObject, out: *mut i32) -> c_int { + with_vm(|vm| { + let value: i32 = unsafe { &*obj } + .to_owned() + .try_index(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to int32_t"))?; + unsafe { *out = value }; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsInt64(obj: *mut PyObject, out: *mut i64) -> c_int { + with_vm(|vm| { + let value: i64 = unsafe { &*obj } + .to_owned() + .try_index(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to int64_t"))?; + unsafe { *out = value }; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsLongLong(obj: *mut PyObject) -> c_longlong { + with_vm::, _>(|vm| { + unsafe { &*obj } + .to_owned() + .try_index(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long long")) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsSize_t(obj: *mut PyObject) -> usize { + with_vm::, _>(|vm| { + let value: usize = unsafe { &*obj } + .try_downcast_ref::(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to C size_t"))?; + Ok(value) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsSsize_t(obj: *mut PyObject) -> isize { + with_vm::, _>(|vm| { + unsafe { &*obj } + .try_downcast_ref::(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to C ssize_t")) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsUInt32(obj: *mut PyObject, out: *mut u32) -> c_int { + with_vm(|vm| { + let value: u32 = unsafe { &*obj } + .to_owned() + .try_index(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to uint32_t"))?; + unsafe { *out = value }; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsUInt64(obj: *mut PyObject, out: *mut u64) -> c_int { + with_vm(|vm| { + let value: u64 = unsafe { &*obj } + .to_owned() + .try_index(vm)? + .as_bigint() + .try_into() + .map_err(|_| vm.new_overflow_error("Python int too large to convert to uint64_t"))?; + unsafe { *out = value }; + Ok(()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsUnsignedLong(obj: *mut PyObject) -> c_ulong { + with_vm::, _>(|vm| { + unsafe { &*obj } + .try_downcast_ref::(vm)? + .as_bigint() + .try_into() + .map_err(|_| { + vm.new_overflow_error("Python int too large to convert to C unsigned long") + }) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsUnsignedLongMask(obj: *mut PyObject) -> c_ulong { + with_vm::, _>(|vm| { + let int = unsafe { &*obj }.to_owned().try_index(vm)?; + if const { c_ulong::BITS == 32 } { + Ok(c_ulong::from(int.as_u32_mask())) + } else { + Ok(int.as_u64_mask() as c_ulong) + } + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsUnsignedLongLongMask(obj: *mut PyObject) -> c_ulonglong { + with_vm::, _>(|vm| { + let int = unsafe { &*obj }.to_owned().try_index(vm)?; + Ok(int.as_u64_mask()) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_AsVoidPtr(obj: *mut PyObject) -> *mut c_void { + with_vm(|vm| { + let value = unsafe { &*obj }.try_downcast_ref::(vm)?; + + let unsigned: Result = value.as_bigint().try_into(); + if let Ok(v) = unsigned { + return Ok(v as *mut c_void); + } + let signed: Result = value.as_bigint().try_into(); + if let Ok(v) = signed { + return Ok((v as usize) as *mut c_void); + } + + Err(vm.new_overflow_error("int too large to convert to pointer")) + }) +} + #[unsafe(no_mangle)] pub unsafe extern "C" fn PyLong_AsUnsignedLongLong(obj: *mut PyObject) -> c_ulonglong { with_vm::, _>(|vm| { unsafe { &*obj } - .to_owned() - .try_downcast::(vm)? + .try_downcast_ref::(vm)? .as_bigint() .try_into() .map_err(|_| { @@ -86,4 +302,13 @@ mod tests { assert_eq!(number.extract::().unwrap(), 123); }) } + + #[test] + fn py_int_u128() { + Python::attach(|py| { + let value = 1u128 << 100; + let number = PyInt::new(py, value); + assert_eq!(number.extract::().unwrap(), value); + }) + } } diff --git a/crates/capi/src/util.rs b/crates/capi/src/util.rs index 4061a8f370d..ccb8ee254af 100644 --- a/crates/capi/src/util.rs +++ b/crates/capi/src/util.rs @@ -1,6 +1,6 @@ use crate::PyObject; use core::convert::Infallible; -use core::ffi::{c_char, c_double, c_int, c_long, c_ulonglong, c_void}; +use core::ffi::{c_char, c_double, c_int, c_long, c_ulong, c_void}; use rustpython_vm::{PyObjectRef, PyRef, PyResult, VirtualMachine}; pub(crate) trait FfiResult { @@ -109,6 +109,23 @@ impl FfiResult for isize { } } +#[cfg(not(windows))] +impl FfiResult for c_int { + const ERR_VALUE: Self = -1; + + fn into_output(self, _vm: &VirtualMachine) -> Self { + self + } +} + +impl FfiResult for usize { + const ERR_VALUE: Self = Self::MAX; + + fn into_output(self, _vm: &VirtualMachine) -> Self { + self + } +} + impl FfiResult for c_long { const ERR_VALUE: Self = -1; @@ -117,7 +134,25 @@ impl FfiResult for c_long { } } -impl FfiResult for c_ulonglong { +impl FfiResult for c_ulong { + const ERR_VALUE: Self = Self::MAX; + + fn into_output(self, _vm: &VirtualMachine) -> Self { + self + } +} + +#[cfg(windows)] +impl FfiResult for core::ffi::c_longlong { + const ERR_VALUE: Self = -1; + + fn into_output(self, _vm: &VirtualMachine) -> Self { + self + } +} + +#[cfg(windows)] +impl FfiResult for core::ffi::c_ulonglong { const ERR_VALUE: Self = Self::MAX; fn into_output(self, _vm: &VirtualMachine) -> Self { @@ -167,3 +202,39 @@ where ) } } + +#[cfg(test)] +mod tests { + use super::*; + use core::any::type_name; + use core::ffi::{c_longlong, c_ulonglong}; + use core::fmt::Debug; + + #[test] + fn ffi_result_err_value() { + fn assert_error_value(value: Output) + where + T: FfiResult + 'static, + Output: PartialEq + Debug, + { + assert_eq!(value, T::ERR_VALUE, "{}", type_name::(),); + } + + assert_error_value::<(), _>(()); + assert_error_value::<(), c_int>(-1); + + assert_error_value::(-1); + assert_error_value::(usize::MAX); + assert_error_value::(-1); + assert_error_value::(-1); // i32 + assert_error_value::(-1); //Windows i32, unix i64 + assert_error_value::(c_ulong::MAX); // Windows u32, unix u64 + assert_error_value::(-1); // i64 + assert_error_value::(c_ulonglong::MAX); // u64 + assert_error_value::(-1.0); + assert_error_value::(-1); + + assert_error_value::, _>(-1); + assert_error_value::, _>(usize::MAX); + } +} diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 198c2765cdc..066aed8ea93 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -322,12 +322,26 @@ impl PyInt { v.to_u32() .or_else(|| v.to_i32().map(|i| i as u32)) .unwrap_or_else(|| { - let mut out = 0u32; - for digit in v.iter_u32_digits() { - out = out.wrapping_shl(32) | digit; + let out = v.iter_u32_digits().next().unwrap_or(0); + match v.sign() { + Sign::Minus => out.wrapping_neg(), + _ => out, } + }) + } + + // _PyLong_AsUnsignedLongLongMask + #[must_use] + pub fn as_u64_mask(&self) -> u64 { + let v = self.as_bigint(); + v.to_u64() + .or_else(|| v.to_i64().map(|i| i as u64)) + .unwrap_or_else(|| { + let mut digits = v.iter_u32_digits(); + let out = u64::from(digits.next().unwrap_or(0)) + | (u64::from(digits.next().unwrap_or(0)) << 32); match v.sign() { - Sign::Minus => out * -1i32 as u32, + Sign::Minus => out.wrapping_neg(), _ => out, } }) From 6f72de66b4a7157481f766aba9b516a6ae60b493 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Thu, 25 Jun 2026 15:52:51 +0200 Subject: [PATCH 2/3] Simplify as_mask logic --- crates/vm/src/builtins/int.rs | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/crates/vm/src/builtins/int.rs b/crates/vm/src/builtins/int.rs index 066aed8ea93..b9246149731 100644 --- a/crates/vm/src/builtins/int.rs +++ b/crates/vm/src/builtins/int.rs @@ -319,32 +319,24 @@ impl PyInt { #[must_use] pub fn as_u32_mask(&self) -> u32 { let v = self.as_bigint(); - v.to_u32() - .or_else(|| v.to_i32().map(|i| i as u32)) - .unwrap_or_else(|| { - let out = v.iter_u32_digits().next().unwrap_or(0); - match v.sign() { - Sign::Minus => out.wrapping_neg(), - _ => out, - } - }) + let out = v.iter_u32_digits().next().unwrap_or(0); + match v.sign() { + Sign::Minus => out.wrapping_neg(), + _ => out, + } } // _PyLong_AsUnsignedLongLongMask #[must_use] pub fn as_u64_mask(&self) -> u64 { let v = self.as_bigint(); - v.to_u64() - .or_else(|| v.to_i64().map(|i| i as u64)) - .unwrap_or_else(|| { - let mut digits = v.iter_u32_digits(); - let out = u64::from(digits.next().unwrap_or(0)) - | (u64::from(digits.next().unwrap_or(0)) << 32); - match v.sign() { - Sign::Minus => out.wrapping_neg(), - _ => out, - } - }) + let mut digits = v.iter_u32_digits(); + let out = + u64::from(digits.next().unwrap_or(0)) | (u64::from(digits.next().unwrap_or(0)) << 32); + match v.sign() { + Sign::Minus => out.wrapping_neg(), + _ => out, + } } pub fn try_to_primitive<'a, I>(&'a self, vm: &VirtualMachine) -> PyResult From 18f49293bd0d157d0ce42489e4fc720538d0e203 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Fri, 26 Jun 2026 09:14:01 +0200 Subject: [PATCH 3/3] Add `PyLong_FromNativeBytes` & `PyLong_FromUnsignedNativeBytes` --- .cspell.dict/cpython.txt | 1 + Cargo.lock | 1 + crates/capi/Cargo.toml | 1 + crates/capi/src/longobject.rs | 95 ++++++++++++++++++++++++++++++++++- 4 files changed, 97 insertions(+), 1 deletion(-) diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index e5b31b57f15..11440e30f8a 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -4,6 +4,7 @@ argdefs argtypes asdl asname +ASNATIVEBYTES atopen atext attro diff --git a/Cargo.lock b/Cargo.lock index e6a4194a056..740ce5f09fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3448,6 +3448,7 @@ dependencies = [ "bitflags 2.13.0", "itertools 0.14.0", "libc", + "malachite-bigint", "num-complex", "pyo3", "rustpython-pylib", diff --git a/crates/capi/Cargo.toml b/crates/capi/Cargo.toml index 878620ebe23..e408d7ab0fe 100644 --- a/crates/capi/Cargo.toml +++ b/crates/capi/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["cdylib", "rlib"] bitflags = { workspace = true } itertools = { workspace = true } libc = { 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 a2868feded7..0668f8df643 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -1,11 +1,13 @@ use crate::PyObject; use crate::object::define_py_check; use crate::pystate::with_vm; +use bitflags::bitflags; use core::ffi::{CStr, c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; +use malachite_bigint::{BigInt, Sign}; use rustpython_vm::builtins::{PyInt, try_bigint_to_f64, try_f64_to_bigint}; use rustpython_vm::common::int::bytes_to_int; use rustpython_vm::protocol::handle_bytes_to_int_err; -use rustpython_vm::{AsObject, PyResult}; +use rustpython_vm::{AsObject, PyResult, VirtualMachine}; define_py_check!(fn PyLong_Check, types.int_type); define_py_check!(exact fn PyLong_CheckExact, types.int_type); @@ -65,6 +67,97 @@ pub extern "C" fn PyLong_FromUInt64(value: u64) -> *mut PyObject { with_vm(|vm| vm.ctx.new_int(value)) } +bitflags! { + #[derive(Clone, Copy)] + struct AsNativeBytesFlags: c_int { + const BIG_ENDIAN = 0; + const LITTLE_ENDIAN = 1; + const NATIVE_ENDIAN = 3; + const UNSIGNED_BUFFER = 4; + const REJECT_NEGATIVE = 8; + const ALLOW_INDEX = 16; + } +} + +impl AsNativeBytesFlags { + #[inline] + fn is_little_endian(self) -> bool { + if self.contains(Self::NATIVE_ENDIAN) { + cfg!(target_endian = "little") + } else { + self.contains(Self::LITTLE_ENDIAN) + } + } + + fn from_bits_or_default(vm: &VirtualMachine, raw_flags: c_int) -> PyResult { + const PY_ASNATIVEBYTES_DEFAULTS: c_int = -1; + if raw_flags == PY_ASNATIVEBYTES_DEFAULTS { + return Ok(Self::default()); + }; + + let flags = Self::from_bits(raw_flags) + .ok_or_else(|| vm.new_value_error("Invalid NativeBytes flags"))?; + if flags.contains(Self::LITTLE_ENDIAN) & flags.contains(Self::NATIVE_ENDIAN) { + Err(vm.new_value_error("Cannot specify both LITTLE_ENDIAN and NATIVE_ENDIAN")) + } else { + Ok(flags) + } + } +} + +impl Default for AsNativeBytesFlags { + fn default() -> Self { + Self::NATIVE_ENDIAN | Self::UNSIGNED_BUFFER + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_FromNativeBytes( + buffer: *const c_void, + n_bytes: usize, + flags: c_int, +) -> *mut PyObject { + with_vm(|vm| { + let flags = AsNativeBytesFlags::from_bits_or_default(vm, flags)?; + let little_endian = flags.is_little_endian(); + let bytes = unsafe { core::slice::from_raw_parts(buffer.cast::(), n_bytes) }; + + let value = if flags.contains(AsNativeBytesFlags::UNSIGNED_BUFFER) { + if little_endian { + BigInt::from_bytes_le(Sign::Plus, bytes) + } else { + BigInt::from_bytes_be(Sign::Plus, bytes) + } + } else if little_endian { + BigInt::from_signed_bytes_le(bytes) + } else { + BigInt::from_signed_bytes_be(bytes) + }; + + Ok(vm.ctx.new_bigint(&value)) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyLong_FromUnsignedNativeBytes( + buffer: *const c_void, + n_bytes: usize, + flags: c_int, +) -> *mut PyObject { + with_vm(|vm| { + let flags = AsNativeBytesFlags::from_bits_or_default(vm, flags)?; + let bytes = unsafe { core::slice::from_raw_parts(buffer.cast::(), n_bytes) }; + + let value = if flags.is_little_endian() { + BigInt::from_bytes_le(Sign::Plus, bytes) + } else { + BigInt::from_bytes_be(Sign::Plus, bytes) + }; + + Ok(vm.ctx.new_bigint(&value)) + }) +} + #[unsafe(no_mangle)] pub extern "C" fn PyLong_FromVoidPtr(ptr: *mut c_void) -> *mut PyObject { with_vm(|vm| vm.ctx.new_int(ptr as usize))