Skip to content

Commit d1792fd

Browse files
Add more int functions to the c-api (#8159)
* Add more int functions to the c-api * Simplify as_mask logic * Add `PyLong_FromNativeBytes` & `PyLong_FromUnsignedNativeBytes`
1 parent dd9a477 commit d1792fd

6 files changed

Lines changed: 417 additions & 19 deletions

File tree

‎.cspell.dict/cpython.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ argdefs
44
argtypes
55
asdl
66
asname
7+
ASNATIVEBYTES
78
atopen
89
atext
910
attro

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎crates/capi/Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ crate-type = ["cdylib", "rlib"]
1515
bitflags = { workspace = true }
1616
itertools = { workspace = true }
1717
libc = { workspace = true }
18+
malachite-bigint = { workspace = true }
1819
num-complex = { workspace = true }
1920
rustpython-vm = { workspace = true, features = ["threading", "compiler", "importlib", "host_env"] }
2021
rustpython-stdlib = {workspace = true, features = ["threading"] }

‎crates/capi/src/longobject.rs‎

Lines changed: 323 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use crate::PyObject;
22
use crate::object::define_py_check;
33
use crate::pystate::with_vm;
4-
use core::ffi::{c_long, c_longlong, c_ulong, c_ulonglong};
5-
use rustpython_vm::PyResult;
6-
use rustpython_vm::builtins::PyInt;
4+
use bitflags::bitflags;
5+
use core::ffi::{CStr, c_char, c_double, c_int, c_long, c_longlong, c_ulong, c_ulonglong, c_void};
6+
use malachite_bigint::{BigInt, Sign};
7+
use rustpython_vm::builtins::{PyInt, try_bigint_to_f64, try_f64_to_bigint};
8+
use rustpython_vm::common::int::bytes_to_int;
9+
use rustpython_vm::protocol::handle_bytes_to_int_err;
10+
use rustpython_vm::{AsObject, PyResult, VirtualMachine};
711

812
define_py_check!(fn PyLong_Check, types.int_type);
913
define_py_check!(exact fn PyLong_CheckExact, types.int_type);
@@ -38,6 +42,150 @@ pub extern "C" fn PyLong_FromUnsignedLongLong(value: c_ulonglong) -> *mut PyObje
3842
with_vm(|vm| vm.ctx.new_int(value))
3943
}
4044

45+
#[unsafe(no_mangle)]
46+
pub extern "C" fn PyLong_FromDouble(value: c_double) -> *mut PyObject {
47+
with_vm(|vm| Ok(vm.ctx.new_bigint(&try_f64_to_bigint(value, vm)?)))
48+
}
49+
50+
#[unsafe(no_mangle)]
51+
pub extern "C" fn PyLong_FromInt32(value: i32) -> *mut PyObject {
52+
with_vm(|vm| vm.ctx.new_int(value))
53+
}
54+
55+
#[unsafe(no_mangle)]
56+
pub extern "C" fn PyLong_FromInt64(value: i64) -> *mut PyObject {
57+
with_vm(|vm| vm.ctx.new_int(value))
58+
}
59+
60+
#[unsafe(no_mangle)]
61+
pub extern "C" fn PyLong_FromUInt32(value: u32) -> *mut PyObject {
62+
with_vm(|vm| vm.ctx.new_int(value))
63+
}
64+
65+
#[unsafe(no_mangle)]
66+
pub extern "C" fn PyLong_FromUInt64(value: u64) -> *mut PyObject {
67+
with_vm(|vm| vm.ctx.new_int(value))
68+
}
69+
70+
bitflags! {
71+
#[derive(Clone, Copy)]
72+
struct AsNativeBytesFlags: c_int {
73+
const BIG_ENDIAN = 0;
74+
const LITTLE_ENDIAN = 1;
75+
const NATIVE_ENDIAN = 3;
76+
const UNSIGNED_BUFFER = 4;
77+
const REJECT_NEGATIVE = 8;
78+
const ALLOW_INDEX = 16;
79+
}
80+
}
81+
82+
impl AsNativeBytesFlags {
83+
#[inline]
84+
fn is_little_endian(self) -> bool {
85+
if self.contains(Self::NATIVE_ENDIAN) {
86+
cfg!(target_endian = "little")
87+
} else {
88+
self.contains(Self::LITTLE_ENDIAN)
89+
}
90+
}
91+
92+
fn from_bits_or_default(vm: &VirtualMachine, raw_flags: c_int) -> PyResult<Self> {
93+
const PY_ASNATIVEBYTES_DEFAULTS: c_int = -1;
94+
if raw_flags == PY_ASNATIVEBYTES_DEFAULTS {
95+
return Ok(Self::default());
96+
};
97+
98+
let flags = Self::from_bits(raw_flags)
99+
.ok_or_else(|| vm.new_value_error("Invalid NativeBytes flags"))?;
100+
if flags.contains(Self::LITTLE_ENDIAN) & flags.contains(Self::NATIVE_ENDIAN) {
101+
Err(vm.new_value_error("Cannot specify both LITTLE_ENDIAN and NATIVE_ENDIAN"))
102+
} else {
103+
Ok(flags)
104+
}
105+
}
106+
}
107+
108+
impl Default for AsNativeBytesFlags {
109+
fn default() -> Self {
110+
Self::NATIVE_ENDIAN | Self::UNSIGNED_BUFFER
111+
}
112+
}
113+
114+
#[unsafe(no_mangle)]
115+
pub unsafe extern "C" fn PyLong_FromNativeBytes(
116+
buffer: *const c_void,
117+
n_bytes: usize,
118+
flags: c_int,
119+
) -> *mut PyObject {
120+
with_vm(|vm| {
121+
let flags = AsNativeBytesFlags::from_bits_or_default(vm, flags)?;
122+
let little_endian = flags.is_little_endian();
123+
let bytes = unsafe { core::slice::from_raw_parts(buffer.cast::<u8>(), n_bytes) };
124+
125+
let value = if flags.contains(AsNativeBytesFlags::UNSIGNED_BUFFER) {
126+
if little_endian {
127+
BigInt::from_bytes_le(Sign::Plus, bytes)
128+
} else {
129+
BigInt::from_bytes_be(Sign::Plus, bytes)
130+
}
131+
} else if little_endian {
132+
BigInt::from_signed_bytes_le(bytes)
133+
} else {
134+
BigInt::from_signed_bytes_be(bytes)
135+
};
136+
137+
Ok(vm.ctx.new_bigint(&value))
138+
})
139+
}
140+
141+
#[unsafe(no_mangle)]
142+
pub unsafe extern "C" fn PyLong_FromUnsignedNativeBytes(
143+
buffer: *const c_void,
144+
n_bytes: usize,
145+
flags: c_int,
146+
) -> *mut PyObject {
147+
with_vm(|vm| {
148+
let flags = AsNativeBytesFlags::from_bits_or_default(vm, flags)?;
149+
let bytes = unsafe { core::slice::from_raw_parts(buffer.cast::<u8>(), n_bytes) };
150+
151+
let value = if flags.is_little_endian() {
152+
BigInt::from_bytes_le(Sign::Plus, bytes)
153+
} else {
154+
BigInt::from_bytes_be(Sign::Plus, bytes)
155+
};
156+
157+
Ok(vm.ctx.new_bigint(&value))
158+
})
159+
}
160+
161+
#[unsafe(no_mangle)]
162+
pub extern "C" fn PyLong_FromVoidPtr(ptr: *mut c_void) -> *mut PyObject {
163+
with_vm(|vm| vm.ctx.new_int(ptr as usize))
164+
}
165+
166+
#[unsafe(no_mangle)]
167+
pub unsafe extern "C" fn PyLong_FromString(
168+
str: *const c_char,
169+
pend: *mut *mut c_char,
170+
base: c_int,
171+
) -> *mut PyObject {
172+
with_vm(|vm| {
173+
let bytes = unsafe { CStr::from_ptr(str) }.to_bytes();
174+
let parsed = bytes_to_int(bytes, base as u32, vm.state.int_max_str_digits.load())
175+
.map(|value| vm.ctx.new_bigint(&value));
176+
177+
if let Some(pend) = unsafe { pend.as_mut() } {
178+
let end_offset = if parsed.is_ok() { bytes.len() } else { 0 };
179+
unsafe { *pend = bytes.as_ptr().add(end_offset).cast_mut().cast() };
180+
}
181+
182+
parsed.map_err(|err| {
183+
let obj = vm.ctx.new_bytes(bytes.to_vec());
184+
handle_bytes_to_int_err(err, obj.as_object(), vm)
185+
})
186+
})
187+
}
188+
41189
#[unsafe(no_mangle)]
42190
pub unsafe extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long {
43191
with_vm::<PyResult<c_long>, _>(|vm| {
@@ -50,12 +198,173 @@ pub unsafe extern "C" fn PyLong_AsLong(obj: *mut PyObject) -> c_long {
50198
})
51199
}
52200

201+
#[unsafe(no_mangle)]
202+
pub unsafe extern "C" fn PyLong_AsDouble(obj: *mut PyObject) -> c_double {
203+
with_vm::<PyResult<c_double>, _>(|vm| {
204+
let int = unsafe { &*obj }.try_downcast_ref::<PyInt>(vm)?;
205+
try_bigint_to_f64(int.as_bigint(), vm)
206+
})
207+
}
208+
209+
#[unsafe(no_mangle)]
210+
pub unsafe extern "C" fn PyLong_AsInt(obj: *mut PyObject) -> c_int {
211+
with_vm::<PyResult<c_int>, _>(|vm| {
212+
unsafe { &*obj }
213+
.to_owned()
214+
.try_index(vm)?
215+
.as_bigint()
216+
.try_into()
217+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C int"))
218+
})
219+
}
220+
221+
#[unsafe(no_mangle)]
222+
pub unsafe extern "C" fn PyLong_AsInt32(obj: *mut PyObject, out: *mut i32) -> c_int {
223+
with_vm(|vm| {
224+
let value: i32 = unsafe { &*obj }
225+
.to_owned()
226+
.try_index(vm)?
227+
.as_bigint()
228+
.try_into()
229+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to int32_t"))?;
230+
unsafe { *out = value };
231+
Ok(())
232+
})
233+
}
234+
235+
#[unsafe(no_mangle)]
236+
pub unsafe extern "C" fn PyLong_AsInt64(obj: *mut PyObject, out: *mut i64) -> c_int {
237+
with_vm(|vm| {
238+
let value: i64 = unsafe { &*obj }
239+
.to_owned()
240+
.try_index(vm)?
241+
.as_bigint()
242+
.try_into()
243+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to int64_t"))?;
244+
unsafe { *out = value };
245+
Ok(())
246+
})
247+
}
248+
249+
#[unsafe(no_mangle)]
250+
pub unsafe extern "C" fn PyLong_AsLongLong(obj: *mut PyObject) -> c_longlong {
251+
with_vm::<PyResult<c_longlong>, _>(|vm| {
252+
unsafe { &*obj }
253+
.to_owned()
254+
.try_index(vm)?
255+
.as_bigint()
256+
.try_into()
257+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C long long"))
258+
})
259+
}
260+
261+
#[unsafe(no_mangle)]
262+
pub unsafe extern "C" fn PyLong_AsSize_t(obj: *mut PyObject) -> usize {
263+
with_vm::<PyResult<usize>, _>(|vm| {
264+
let value: usize = unsafe { &*obj }
265+
.try_downcast_ref::<PyInt>(vm)?
266+
.as_bigint()
267+
.try_into()
268+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C size_t"))?;
269+
Ok(value)
270+
})
271+
}
272+
273+
#[unsafe(no_mangle)]
274+
pub unsafe extern "C" fn PyLong_AsSsize_t(obj: *mut PyObject) -> isize {
275+
with_vm::<PyResult<isize>, _>(|vm| {
276+
unsafe { &*obj }
277+
.try_downcast_ref::<PyInt>(vm)?
278+
.as_bigint()
279+
.try_into()
280+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C ssize_t"))
281+
})
282+
}
283+
284+
#[unsafe(no_mangle)]
285+
pub unsafe extern "C" fn PyLong_AsUInt32(obj: *mut PyObject, out: *mut u32) -> c_int {
286+
with_vm(|vm| {
287+
let value: u32 = unsafe { &*obj }
288+
.to_owned()
289+
.try_index(vm)?
290+
.as_bigint()
291+
.try_into()
292+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to uint32_t"))?;
293+
unsafe { *out = value };
294+
Ok(())
295+
})
296+
}
297+
298+
#[unsafe(no_mangle)]
299+
pub unsafe extern "C" fn PyLong_AsUInt64(obj: *mut PyObject, out: *mut u64) -> c_int {
300+
with_vm(|vm| {
301+
let value: u64 = unsafe { &*obj }
302+
.to_owned()
303+
.try_index(vm)?
304+
.as_bigint()
305+
.try_into()
306+
.map_err(|_| vm.new_overflow_error("Python int too large to convert to uint64_t"))?;
307+
unsafe { *out = value };
308+
Ok(())
309+
})
310+
}
311+
312+
#[unsafe(no_mangle)]
313+
pub unsafe extern "C" fn PyLong_AsUnsignedLong(obj: *mut PyObject) -> c_ulong {
314+
with_vm::<PyResult<c_ulong>, _>(|vm| {
315+
unsafe { &*obj }
316+
.try_downcast_ref::<PyInt>(vm)?
317+
.as_bigint()
318+
.try_into()
319+
.map_err(|_| {
320+
vm.new_overflow_error("Python int too large to convert to C unsigned long")
321+
})
322+
})
323+
}
324+
325+
#[unsafe(no_mangle)]
326+
pub unsafe extern "C" fn PyLong_AsUnsignedLongMask(obj: *mut PyObject) -> c_ulong {
327+
with_vm::<PyResult<c_ulong>, _>(|vm| {
328+
let int = unsafe { &*obj }.to_owned().try_index(vm)?;
329+
if const { c_ulong::BITS == 32 } {
330+
Ok(c_ulong::from(int.as_u32_mask()))
331+
} else {
332+
Ok(int.as_u64_mask() as c_ulong)
333+
}
334+
})
335+
}
336+
337+
#[unsafe(no_mangle)]
338+
pub unsafe extern "C" fn PyLong_AsUnsignedLongLongMask(obj: *mut PyObject) -> c_ulonglong {
339+
with_vm::<PyResult<c_ulonglong>, _>(|vm| {
340+
let int = unsafe { &*obj }.to_owned().try_index(vm)?;
341+
Ok(int.as_u64_mask())
342+
})
343+
}
344+
345+
#[unsafe(no_mangle)]
346+
pub unsafe extern "C" fn PyLong_AsVoidPtr(obj: *mut PyObject) -> *mut c_void {
347+
with_vm(|vm| {
348+
let value = unsafe { &*obj }.try_downcast_ref::<PyInt>(vm)?;
349+
350+
let unsigned: Result<usize, _> = value.as_bigint().try_into();
351+
if let Ok(v) = unsigned {
352+
return Ok(v as *mut c_void);
353+
}
354+
let signed: Result<isize, _> = value.as_bigint().try_into();
355+
if let Ok(v) = signed {
356+
return Ok((v as usize) as *mut c_void);
357+
}
358+
359+
Err(vm.new_overflow_error("int too large to convert to pointer"))
360+
})
361+
}
362+
53363
#[unsafe(no_mangle)]
54364
pub unsafe extern "C" fn PyLong_AsUnsignedLongLong(obj: *mut PyObject) -> c_ulonglong {
55365
with_vm::<PyResult<c_ulonglong>, _>(|vm| {
56366
unsafe { &*obj }
57-
.to_owned()
58-
.try_downcast::<PyInt>(vm)?
367+
.try_downcast_ref::<PyInt>(vm)?
59368
.as_bigint()
60369
.try_into()
61370
.map_err(|_| {
@@ -86,4 +395,13 @@ mod tests {
86395
assert_eq!(number.extract::<u64>().unwrap(), 123);
87396
})
88397
}
398+
399+
#[test]
400+
fn py_int_u128() {
401+
Python::attach(|py| {
402+
let value = 1u128 << 100;
403+
let number = PyInt::new(py, value);
404+
assert_eq!(number.extract::<u128>().unwrap(), value);
405+
})
406+
}
89407
}

0 commit comments

Comments
 (0)