mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
wasi: support monotonic / perf time (#8385)
Assisted-by: Codex:GPT-5.6 Sol Signed-off-by: Jiwoo Ahn <ikwydls1314@gmail.com>
This commit is contained in:
1 parent
9bf458cb46
commit
bf8bca8039
4 files changed
+107
-40
No files matched your search
@@ -801,7 +801,9 @@ jobs:
|
||||
- name: build rustpython
|
||||
run: cargo build --profile wasm-release --target wasm32-wasip1 --no-default-features --features freeze-stdlib,stdlib,stdio,importlib,host_env --verbose
|
||||
- name: run snippets
|
||||
run: wasmer run --dir "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/stdlib_random.py"
|
||||
run: |
|
||||
wasmer run --dir "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/stdlib_random.py"
|
||||
wasmer run --dir "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/extra_tests/snippets/stdlib_time.py"
|
||||
- name: run cpython unittest
|
||||
run: wasmer run --dir "$(pwd)" target/wasm32-wasip1/wasm-release/rustpython.wasm -- "$(pwd)/Lib/test/test_int.py"
|
||||
|
||||
|
||||
@@ -227,11 +227,13 @@ pub fn process_times() -> std::io::Result<ProcessTimes> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
// WASI libc represents clockid_t as an opaque pointer type without Eq or PartialEq.
|
||||
#[cfg_attr(unix, derive(Eq, PartialEq))]
|
||||
pub struct ClockId(libc::clockid_t);
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(any(unix, target_os = "wasi"))]
|
||||
impl ClockId {
|
||||
pub const fn from_raw(raw: libc::clockid_t) -> Self {
|
||||
Self(raw)
|
||||
@@ -259,6 +261,7 @@ impl ClockId {
|
||||
target_os = "solaris",
|
||||
target_os = "openbsd",
|
||||
target_os = "redox",
|
||||
target_os = "wasi",
|
||||
)))]
|
||||
pub const CLOCK_THREAD_CPUTIME_ID: Self = Self(libc::CLOCK_THREAD_CPUTIME_ID);
|
||||
}
|
||||
@@ -275,6 +278,20 @@ pub fn clock_gettime(id: ClockId) -> std::io::Result<Duration> {
|
||||
.map_err(std::io::Error::from)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
pub fn clock_gettime(id: ClockId) -> std::io::Result<Duration> {
|
||||
let mut ts = core::mem::MaybeUninit::<libc::timespec>::uninit();
|
||||
|
||||
let ret = unsafe { libc::clock_gettime(id.as_raw(), ts.as_mut_ptr()) };
|
||||
if ret != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let ts = unsafe { ts.assume_init() };
|
||||
|
||||
Ok(Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32))
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "redox")))]
|
||||
pub fn clock_getres(id: ClockId) -> std::io::Result<Duration> {
|
||||
nix::time::clock_getres(nix_clock_id(id))
|
||||
|
||||
@@ -31,6 +31,8 @@ mod decl {
|
||||
naive::{NaiveDate, NaiveDateTime, NaiveTime},
|
||||
};
|
||||
use core::time::Duration;
|
||||
#[cfg(target_os = "wasi")]
|
||||
use rustpython_host_env::time::ClockId;
|
||||
#[cfg(any(unix, windows))]
|
||||
use rustpython_host_env::time::asctime_from_tm;
|
||||
use rustpython_host_env::time::{self as host_time};
|
||||
@@ -60,14 +62,27 @@ mod decl {
|
||||
#[pyattr]
|
||||
pub const _STRUCT_TM_ITEMS: usize = 11;
|
||||
|
||||
// TODO: implement proper monotonic time for wasm/wasi.
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
#[cfg(target_os = "wasi")]
|
||||
fn get_clock_time(id: ClockId, vm: &VirtualMachine) -> PyResult<Duration> {
|
||||
host_time::clock_gettime(id).map_err(|err| vm.new_os_error(err.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
|
||||
get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
|
||||
get_clock_time(ClockId::CLOCK_MONOTONIC, vm)
|
||||
}
|
||||
|
||||
#[cfg(not(any(unix, windows, target_os = "wasi")))]
|
||||
fn get_monotonic_time(vm: &VirtualMachine) -> PyResult<Duration> {
|
||||
duration_since_system_now(vm)
|
||||
}
|
||||
|
||||
// TODO: implement proper perf time for wasm/wasi.
|
||||
#[cfg(not(any(unix, windows)))]
|
||||
#[cfg(not(any(unix, windows, target_os = "wasi")))]
|
||||
fn get_perf_time(vm: &VirtualMachine) -> PyResult<Duration> {
|
||||
duration_since_system_now(vm)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import sys
|
||||
import time
|
||||
|
||||
x = time.gmtime(1000)
|
||||
@@ -11,41 +12,73 @@ s = time.strftime("%Y-%m-%d-%H-%M-%S", x)
|
||||
# print(s)
|
||||
assert s == "1970-01-01-00-16-40"
|
||||
|
||||
x2 = time.strptime(s, "%Y-%m-%d-%H-%M-%S")
|
||||
assert x2.tm_min == 16
|
||||
if sys.platform != "wasi":
|
||||
# _strptime depends on time.tzname, which is not available on WASI yet.
|
||||
x2 = time.strptime(s, "%Y-%m-%d-%H-%M-%S")
|
||||
assert x2.tm_min == 16
|
||||
|
||||
# TODO: WASI currently does not raise OverflowError for some out-of-range
|
||||
# struct_time values in asctime() and strftime().
|
||||
# Re-enable this regression on WASI once the non-Unix time conversion path is fixed.
|
||||
|
||||
# Regression test for RustPython issue #4938:
|
||||
# struct_time field overflow should raise OverflowError (matching CPython),
|
||||
# not TypeError. Covers mktime, asctime, and strftime.
|
||||
I32_MAX_PLUS_1 = 2147483648
|
||||
overflow_cases = [
|
||||
(I32_MAX_PLUS_1, 1, 1, 0, 0, 0, 0, 0, 0), # i32 overflow in year
|
||||
(2024, I32_MAX_PLUS_1, 1, 0, 0, 0, 0, 0, 0), # i32 overflow in month
|
||||
(2024, 1, I32_MAX_PLUS_1, 0, 0, 0, 0, 0, 0), # i32 overflow in mday
|
||||
(2024, 1, 1, 0, 0, I32_MAX_PLUS_1, 0, 0, 0), # i32 overflow in sec
|
||||
(88888888888,) * 9, # multi-field i32 overflow
|
||||
]
|
||||
|
||||
for case in overflow_cases:
|
||||
for func_name, call in [
|
||||
("mktime", lambda c=case: time.mktime(c)),
|
||||
("asctime", lambda c=case: time.asctime(c)),
|
||||
("strftime", lambda c=case: time.strftime("%Y", c)),
|
||||
]:
|
||||
try:
|
||||
call()
|
||||
except OverflowError:
|
||||
pass # expected, matches CPython
|
||||
except TypeError as e:
|
||||
raise AssertionError(
|
||||
f"{func_name}({case}) raised TypeError (should be OverflowError): {e}"
|
||||
) from e
|
||||
else:
|
||||
raise AssertionError(
|
||||
f"{func_name}({case}) did not raise — expected OverflowError"
|
||||
)
|
||||
|
||||
s = time.asctime(x)
|
||||
# print(s)
|
||||
assert s == "Thu Jan 1 00:16:40 1970"
|
||||
|
||||
# Monotonic and performance clocks should advance with elapsed time.
|
||||
monotonic_before = time.monotonic()
|
||||
monotonic_ns = time.monotonic_ns()
|
||||
monotonic_after = time.monotonic()
|
||||
|
||||
# Regression test for RustPython issue #4938:
|
||||
# struct_time field overflow should raise OverflowError (matching CPython),
|
||||
# not TypeError. Covers mktime, asctime, and strftime.
|
||||
I32_MAX_PLUS_1 = 2147483648
|
||||
overflow_cases = [
|
||||
(I32_MAX_PLUS_1, 1, 1, 0, 0, 0, 0, 0, 0), # i32 overflow in year
|
||||
(2024, I32_MAX_PLUS_1, 1, 0, 0, 0, 0, 0, 0), # i32 overflow in month
|
||||
(2024, 1, I32_MAX_PLUS_1, 0, 0, 0, 0, 0, 0), # i32 overflow in mday
|
||||
(2024, 1, 1, 0, 0, I32_MAX_PLUS_1, 0, 0, 0), # i32 overflow in sec
|
||||
(88888888888,) * 9, # multi-field i32 overflow
|
||||
]
|
||||
assert isinstance(monotonic_before, float)
|
||||
assert isinstance(monotonic_ns, int)
|
||||
assert monotonic_before <= monotonic_ns / 1_000_000_000 <= monotonic_after
|
||||
|
||||
for case in overflow_cases:
|
||||
for func_name, call in [
|
||||
("mktime", lambda c=case: time.mktime(c)),
|
||||
("asctime", lambda c=case: time.asctime(c)),
|
||||
("strftime", lambda c=case: time.strftime("%Y", c)),
|
||||
]:
|
||||
try:
|
||||
call()
|
||||
except OverflowError:
|
||||
pass # expected, matches CPython
|
||||
except TypeError as e:
|
||||
raise AssertionError(
|
||||
f"{func_name}({case}) raised TypeError (should be OverflowError): {e}"
|
||||
) from e
|
||||
else:
|
||||
raise AssertionError(
|
||||
f"{func_name}({case}) did not raise — expected OverflowError"
|
||||
)
|
||||
perf_before = time.perf_counter()
|
||||
perf_ns = time.perf_counter_ns()
|
||||
perf_after = time.perf_counter()
|
||||
|
||||
assert isinstance(perf_before, float)
|
||||
assert isinstance(perf_ns, int)
|
||||
assert perf_before <= perf_ns / 1_000_000_000 <= perf_after
|
||||
|
||||
monotonic_start = time.monotonic()
|
||||
perf_start = time.perf_counter()
|
||||
|
||||
time.sleep(0.02)
|
||||
|
||||
monotonic_elapsed = time.monotonic() - monotonic_start
|
||||
perf_elapsed = time.perf_counter() - perf_start
|
||||
|
||||
assert monotonic_elapsed >= 0.01
|
||||
assert perf_elapsed >= 0.01
|
||||
Reference in new issue
Block a user