From 5c916a4acd44d3b8b052d3437af1c4a3f232c3e7 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 24 Jun 2026 13:50:51 +0200 Subject: [PATCH] Add more int functions to the c-api --- crates/capi/src/lib.rs | 1 + crates/capi/src/longobject.rs | 200 +++++++++++++++++++++++++++++++++- crates/capi/src/util.rs | 83 +++++++++++++- crates/vm/src/builtins/int.rs | 22 +++- 4 files changed, 298 insertions(+), 8 deletions(-) diff --git a/crates/capi/src/lib.rs b/crates/capi/src/lib.rs index fb3bc687f4d..24490cc6504 100644 --- a/crates/capi/src/lib.rs +++ b/crates/capi/src/lib.rs @@ -7,6 +7,7 @@ use rustpython_vm::{Context, Interpreter}; use std::sync::MutexGuard; extern crate alloc; +extern crate core; pub mod abstract_; pub mod boolobject; diff --git a/crates/capi/src/longobject.rs b/crates/capi/src/longobject.rs index 8c9fe5e1acb..b3ebfe251a7 100644 --- a/crates/capi/src/longobject.rs +++ b/crates/capi/src/longobject.rs @@ -1,9 +1,9 @@ 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_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void}; use rustpython_vm::PyResult; -use rustpython_vm::builtins::PyInt; +use rustpython_vm::builtins::{PyInt, try_f64_to_bigint}; define_py_check!(fn PyLong_Check, types.int_type); define_py_check!(exact fn PyLong_CheckExact, types.int_type); @@ -38,6 +38,36 @@ 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_AsLong(obj: *mut PyObject) -> c_long { with_vm::, _>(|vm| { @@ -50,6 +80,163 @@ pub unsafe extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long { }) } +#[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 } + .to_owned() + .try_index(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 } + .to_owned() + .try_index(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 } + .to_owned() + .try_index(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 }.to_owned().try_index(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| { @@ -86,4 +273,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 24d6d3d40e9..1fbc02a17ea 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 { @@ -101,6 +101,31 @@ impl FfiResult for usize { } } +#[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 isize { + const ERR_VALUE: Self = -1; + + fn into_output(self, _vm: &VirtualMachine) -> Self { + self + } +} + impl FfiResult for c_long { const ERR_VALUE: Self = -1; @@ -109,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 { @@ -159,3 +202,39 @@ where ) } } + +#[cfg(test)] +mod tests { + use super::*; + use core::fmt::Debug; + use std::any::type_name; + use std::ffi::{c_longlong, c_ulonglong}; + + #[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, } })