Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 183 additions & 13 deletions crates/vm/src/stdlib/_testinternalcapi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
pub(crate) use _testinternalcapi::module_def;

mod pytime;

use core::sync::atomic::{AtomicUsize, Ordering};

static RARE_SET_CLASS: AtomicUsize = AtomicUsize::new(0);
Expand All @@ -26,6 +28,7 @@ pub(crate) fn note_func_modification() {

#[pymodule]
mod _testinternalcapi {
use super::pytime::Round;
use super::*;
use crate::{
AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
Expand All @@ -37,16 +40,21 @@ mod _testinternalcapi {
common::{hash::PyHash, lock::LazyLock},
dict_inner,
frame::FrameObject,
function::{OptionalArg, PyComparisonValue},
function::{FuncArgs, OptionalArg, PyComparisonValue},
object::{Traverse, TraverseFn},
protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods},
types::{
AsMapping, AsSequence, Comparable, IterNext, Iterable, PyComparisonOp, PyTypeFlags,
SelfIter,
},
};
#[cfg(any(unix, windows))]
use rustpython_host_env::time::TimeT;
use std::collections::HashMap;

#[cfg(not(any(unix, windows)))]
type TimeT = i64;

// ADAPTIVE_WARMUP_VALUE + 1
#[pyattr]
const SPECIALIZATION_THRESHOLD: usize = 2;
Expand All @@ -69,7 +77,7 @@ mod _testinternalcapi {
const SIZEOF_PYOBJECT: usize = core::mem::size_of::<crate::PyObject>();

#[pyattr]
const SIZEOF_TIME_T: usize = 8;
const SIZEOF_TIME_T: usize = core::mem::size_of::<TimeT>();

// JUMP_BACKWARD_INITIAL_VALUE + 1
#[pyattr]
Expand Down Expand Up @@ -486,24 +494,186 @@ mod _testinternalcapi {
}
}

fn pytime_args<'a, const N: usize>(
args: &'a FuncArgs,
name: &str,
vm: &VirtualMachine,
) -> PyResult<&'a [PyObjectRef; N]> {
// These CPython test helpers use METH_VARARGS, not keyword parsing.
if !args.kwargs.is_empty() {
return Err(vm.new_type_error(format!("{name}() takes no keyword arguments")));
}
args.args.as_slice().try_into().map_err(|_| {
vm.new_type_error(format!(
"function takes exactly {} argument{} ({} given)",
N,
if N == 1 { "" } else { "s" },
args.args.len()
))
})
}

fn pytime_c_int(object: &PyObject, vm: &VirtualMachine) -> PyResult<i32> {
// PyArg_ParseTuple's "i" converts to C long before checking C int bounds.
let value: core::ffi::c_long = object
.try_index(vm)?
.as_bigint()
.try_into()
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?;
num_traits::ToPrimitive::to_i32(&value).ok_or_else(|| {
vm.new_overflow_error(if value < 0 {
"signed integer is less than minimum"
} else {
"signed integer is greater than maximum"
})
})
}

fn pytime_i64(object: &PyObject, vm: &VirtualMachine) -> PyResult<i64> {
object
.try_index(vm)?
.as_bigint()
.try_into()
.map_err(|_| vm.new_overflow_error("Python int too large to convert to C int64_t"))
}

fn pytime_pair(
value: i64,
denominator: i64,
minimum: i64,
maximum: i64,
clamp: bool,
vm: &VirtualMachine,
) -> PyResult<(i64, i64)> {
let seconds = value.div_euclid(denominator);
if !(minimum..=maximum).contains(&seconds) {
if !clamp {
return Err(vm.new_overflow_error("timestamp out of range for platform time_t"));
}
return Ok((seconds.clamp(minimum, maximum), 0));
}
Ok((seconds, value.rem_euclid(denominator)))
}

#[pyfunction(name = "_PyTime_AsTimespec")]
fn pytime_as_timespec(ns: i64) -> (i64, i64) {
let sec = ns.div_euclid(1_000_000_000);
let nsec = ns.rem_euclid(1_000_000_000);
(sec, nsec)
fn pytime_as_timespec(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(i64, i64)> {
let [ns] = pytime_args::<1>(&args, "_PyTime_AsTimespec", vm)?;
pytime_pair(
pytime_i64(ns, vm)?,
1_000_000_000,
TimeT::MIN as _,
TimeT::MAX as _,
false,
vm,
)
}

#[pyfunction(name = "_PyTime_AsTimespec_clamp")]
fn pytime_as_timespec_clamp(ns: i64) -> (i64, i64) {
pytime_as_timespec(ns)
fn pytime_as_timespec_clamp(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(i64, i64)> {
let [ns] = pytime_args::<1>(&args, "_PyTime_AsTimespec_clamp", vm)?;
pytime_pair(
pytime_i64(ns, vm)?,
1_000_000_000,
TimeT::MIN as _,
TimeT::MAX as _,
true,
vm,
)
}

#[pyfunction(name = "_PyTime_AsTimeval_clamp")]
fn pytime_as_timeval_clamp(ns: i64, _rnd: OptionalArg<i32>) -> (i64, i64) {
let us = ns.div_euclid(1000);
let sec = us.div_euclid(1_000_000);
let usec = us.rem_euclid(1_000_000);
(sec, usec)
fn pytime_as_timeval_clamp(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(i64, i64)> {
let [ns, round] = pytime_args::<2>(&args, "_PyTime_AsTimeval_clamp", vm)?;
let round = Round::from_int(pytime_c_int(round, vm)?, vm)?;
// Validate rounding before converting the timestamp, as the C wrapper does.
pytime_timeval(pytime_i64(ns, vm)?, round, true, vm)
}

fn pytime_timeval(
ns: i64,
round: Round,
clamp: bool,
vm: &VirtualMachine,
) -> PyResult<(i64, i64)> {
let us = round.divide(ns, 1_000);
// Windows timeval uses C long even though the host time_t is 64-bit.
#[cfg(windows)]
let (minimum, maximum): (i64, i64) =
(core::ffi::c_long::MIN as _, core::ffi::c_long::MAX as _);
#[cfg(not(windows))]
let (minimum, maximum): (i64, i64) = (TimeT::MIN as _, TimeT::MAX as _);
pytime_pair(us, 1_000_000, minimum, maximum, clamp, vm)
}

#[pyfunction(name = "_PyTime_FromSeconds")]
fn pytime_from_seconds(args: FuncArgs, vm: &VirtualMachine) -> PyResult<i64> {
let [seconds] = pytime_args::<1>(&args, "_PyTime_FromSeconds", vm)?;
Ok(pytime::from_seconds(pytime_c_int(seconds, vm)?))
}

#[pyfunction(name = "_PyTime_FromSecondsObject")]
fn pytime_from_seconds_object(args: FuncArgs, vm: &VirtualMachine) -> PyResult<i64> {
let [object, round] = pytime_args::<2>(&args, "_PyTime_FromSecondsObject", vm)?;
let round = Round::from_int(pytime_c_int(round, vm)?, vm)?;
pytime::from_seconds_object(object, round, vm)
}

#[pyfunction(name = "_PyTime_AsTimeval")]
fn pytime_as_timeval(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(i64, i64)> {
let [ns, round] = pytime_args::<2>(&args, "_PyTime_AsTimeval", vm)?;
let round = Round::from_int(pytime_c_int(round, vm)?, vm)?;
pytime_timeval(pytime_i64(ns, vm)?, round, false, vm)
}

fn pytime_as_subseconds(
args: FuncArgs,
name: &str,
divisor: i64,
vm: &VirtualMachine,
) -> PyResult<i64> {
let [ns, round] = pytime_args::<2>(&args, name, vm)?;
let round = pytime_c_int(round, vm)?;
// These two C wrappers convert the timestamp before validating the mode.
let ns = pytime_i64(ns, vm)?;
Ok(Round::from_int(round, vm)?.divide(ns, divisor))
}

#[pyfunction(name = "_PyTime_AsMilliseconds")]
fn pytime_as_milliseconds(args: FuncArgs, vm: &VirtualMachine) -> PyResult<i64> {
pytime_as_subseconds(args, "_PyTime_AsMilliseconds", 1_000_000, vm)
}

#[pyfunction(name = "_PyTime_AsMicroseconds")]
fn pytime_as_microseconds(args: FuncArgs, vm: &VirtualMachine) -> PyResult<i64> {
pytime_as_subseconds(args, "_PyTime_AsMicroseconds", 1_000, vm)
}

#[pyfunction(name = "_PyTime_ObjectToTime_t")]
fn pytime_object_to_time_t(args: FuncArgs, vm: &VirtualMachine) -> PyResult<i64> {
let [object, round] = pytime_args::<2>(&args, "_PyTime_ObjectToTime_t", vm)?;
let round = Round::from_int(pytime_c_int(round, vm)?, vm)?;
pytime::object_to_time_t(object, round, vm)
}

fn pytime_object_to_denominator(
args: FuncArgs,
name: &str,
denominator: i64,
vm: &VirtualMachine,
) -> PyResult<(i64, i64)> {
let [object, round] = pytime_args::<2>(&args, name, vm)?;
let round = Round::from_int(pytime_c_int(round, vm)?, vm)?;
pytime::object_to_denominator(object, denominator, round, vm)
}

#[pyfunction(name = "_PyTime_ObjectToTimeval")]
fn pytime_object_to_timeval(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(i64, i64)> {
pytime_object_to_denominator(args, "_PyTime_ObjectToTimeval", 1_000_000, vm)
}

#[pyfunction(name = "_PyTime_ObjectToTimespec")]
fn pytime_object_to_timespec(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(i64, i64)> {
pytime_object_to_denominator(args, "_PyTime_ObjectToTimespec", 1_000_000_000, vm)
}

#[pyfunction]
Expand Down
Loading
Loading