#![allow(non_camel_case_types)] #![allow(unused_unsafe)] #![allow(unused)] use crate::hostcalls_impl::{ClockEventData, FdEventData}; use crate::memory::*; use crate::sys::host_impl; use crate::{wasi, wasi32, Error, Result}; use cpu_time::{ProcessTime, ThreadTime}; use lazy_static::lazy_static; use std::convert::TryInto; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; lazy_static! { static ref START_MONOTONIC: Instant = Instant::now(); static ref PERF_COUNTER_RES: u64 = get_perf_counter_resolution_ns(); } // Timer resolution on Windows is really hard. We may consider exposing the resolution of the respective // timers as an associated function in the future. pub(crate) fn clock_res_get(clock_id: wasi::__wasi_clockid_t) -> Result { Ok(match clock_id { // This is the best that we can do with std::time::SystemTime. // Rust uses GetSystemTimeAsFileTime, which is said to have the resolution of // 10ms or 55ms, [1] but MSDN doesn't confirm this in any way. // Even the MSDN article on high resolution timestamps doesn't even mention the precision // for this method. [3] // // The timer resolution can be queried using one of the functions: [2, 5] // * NtQueryTimerResolution, which is undocumented and thus not exposed by the winapi crate // * timeGetDevCaps, which returns the upper and lower bound for the precision, in ms. // While the upper bound seems like something we could use, it's typically too high to be meaningful. // For instance, the intervals return by the syscall are: // * [1, 65536] on Wine // * [1, 1000000] on Windows 10, which is up to (sic) 1000 seconds. // // It's possible to manually set the timer resolution, but this sounds like something which should // only be done temporarily. [5] // // Alternatively, we could possibly use GetSystemTimePreciseAsFileTime in clock_time_get, but // this syscall is only available starting from Windows 8. // (we could possibly emulate it on earlier versions of Windows, see [4]) // The MSDN are not clear on the resolution of GetSystemTimePreciseAsFileTime either, but a // Microsoft devblog entry [1] suggests that it kind of combines GetSystemTimeAsFileTime with // QueryPeformanceCounter, which probably means that those two should have the same resolution. // // See also this discussion about the use of GetSystemTimePreciseAsFileTime in Python stdlib, // which in particular contains some resolution benchmarks. // // [1] https://devblogs.microsoft.com/oldnewthing/20170921-00/?p=97057 // [2] http://www.windowstimestamp.com/description // [3] https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps?redirectedfrom=MSDN // [4] https://www.codeproject.com/Tips/1011902/High-Resolution-Time-For-Windows // [5] https://stackoverflow.com/questions/7685762/windows-7-timing-functions-how-to-use-getsystemtimeadjustment-correctly // [6] https://bugs.python.org/issue19007 wasi::__WASI_CLOCKID_REALTIME => 55_000_000, // std::time::Instant uses QueryPerformanceCounter & QueryPerformanceFrequency internally wasi::__WASI_CLOCKID_MONOTONIC => *PERF_COUNTER_RES, // The best we can do is to hardcode the value from the docs. // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => 100, // The best we can do is to hardcode the value from the docs. // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadtimes wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => 100, _ => return Err(Error::EINVAL), }) } pub(crate) fn clock_time_get(clock_id: wasi::__wasi_clockid_t) -> Result { let duration = match clock_id { wasi::__WASI_CLOCKID_REALTIME => get_monotonic_time(), wasi::__WASI_CLOCKID_MONOTONIC => get_realtime_time()?, wasi::__WASI_CLOCKID_PROCESS_CPUTIME_ID => get_proc_cputime()?, wasi::__WASI_CLOCKID_THREAD_CPUTIME_ID => get_thread_cputime()?, _ => return Err(Error::EINVAL), }; duration.as_nanos().try_into().map_err(Into::into) } fn stdin_nonempty() -> bool { use std::io::Read; std::io::stdin().bytes().peekable().peek().is_some() } fn make_read_event(event: &FdEventData, nbytes: Result) -> wasi::__wasi_event_t { use crate::error::WasiErrno; let error = nbytes.as_wasi_errno(); let nbytes = nbytes.unwrap_or_default(); wasi::__wasi_event_t { userdata: event.userdata, r#type: event.r#type, error, u: wasi::__wasi_event_u_t { fd_readwrite: wasi::__wasi_event_fd_readwrite_t { nbytes, flags: 0 }, }, } } fn make_timeout_event(event: &FdEventData, timeout: &ClockEventData) -> wasi::__wasi_event_t { wasi::__wasi_event_t { userdata: timeout.userdata, r#type: wasi::__WASI_EVENTTYPE_CLOCK, error: wasi::__WASI_ERRNO_SUCCESS, u: wasi::__wasi_event_u_t { fd_readwrite: wasi::__wasi_event_fd_readwrite_t { nbytes: 0, flags: 0, }, }, } } pub(crate) fn poll_oneoff( timeout: Option, fd_events: Vec, events: &mut Vec, ) -> Result<()> { use crate::fdentry::Descriptor; use std::fs::Metadata; use std::thread; let timeout_duration = timeout .map(|t| t.delay.try_into().map(Duration::from_nanos)) .transpose()?; // With no events to listen, poll_oneoff just becomes a sleep. if fd_events.is_empty() { match timeout_duration { Some(t) => { thread::sleep(t); return Ok(()); } None => { // The thread is not guanteed to remain parked forever, so we need to loop loop { thread::park(); } } } } // Currently WASI file support is only (a) regular files (b) directories (c) symlinks on Windows, // which are always ready to write on Unix. // // We need to consider stdin/stdout/stderr separately. // We treat stdout/stderr as always ready to write. I'm not sure if it's correct // on Windows but I have not find any way of checking if a write to stdout would block. // Therefore, we only poll the stdin. let mut stdin_events = vec![]; let mut immediate_events = vec![]; let mut stdin_ready = None; for event in fd_events { match event.descriptor { Descriptor::Stdin if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ => { // Cache the non-emptiness for better performance. let immediate = stdin_ready.get_or_insert_with(stdin_nonempty); if *immediate { immediate_events.push(event) } else { stdin_events.push(event) } } _ => immediate_events.push(event), } } // Process all the events that do not require waiting. if !immediate_events.is_empty() { for mut event in immediate_events { let size = match event.descriptor { Descriptor::OsHandle(os_handle) => { if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ { os_handle.metadata().map(|m| m.len()).map_err(Into::into) } else { // The spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and // the implementation on Unix just returns 0 here, so it's probably fine // to do the same on Windows for now. // cf. https://github.com/WebAssembly/WASI/issues/148 Ok(0) } } // We return the only universally correct lower bound, see the comment later in the function. Descriptor::Stdin => Ok(1), // On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows. Descriptor::Stdout | Descriptor::Stderr => Ok(0), }; let new_event = make_read_event(&event, size); events.push(new_event) } } // There are some stdin poll requests and there's no data available immediately if !stdin_events.is_empty() { // We are busy-polling the stdin with delay, unfortunately. // // We'd like to do the following: // (1) wait in a non-blocking way for data to be available in stdin, with timeout // (2) find out, how many bytes are there available to be read. // For one, using `WaitForSingleObject` on the stdin handle could possibly be one way of // achieving (1). // I don't know of any way to achieve (2). // // While both of these are not as trivial on Windows as they are on Linux, there's a much // more fundamental issue preventing us from achieving such behavior with the current // implementation of wasi-common. // // Precisely, in `fd_read` we are using `io::stdin` via the `BufRead` trait, which does // buffering on the libstd side. This means that even if there's still some unread data // in stdin, the Windows system calls may return false negatives, indicating that stdin is empty. // Therefore, avoiding the busy-poll here would require us to ditch libstd for the interaction // with stdin altogether. // // However, polling stdin is a relatively infrequent use case, so this hopefully won't be // a major issue. let poll_interval = Duration::from_millis(10); let poll_start = Instant::now(); let timeout_occurred: Option = loop { // Even though we assume that stdin is not ready, it's better to check it // sooner than later, as we're going to wait anyway if it's the case. if stdin_nonempty() { break None; } if let Some(timeout_duration) = timeout_duration { if poll_start.elapsed() >= timeout_duration { break timeout; } } thread::sleep(poll_interval); }; match timeout_occurred { Some(timeout_info) => { for event in stdin_events { let new_event = make_timeout_event(&event, &timeout_info); events.push(new_event); } } None => { // stdin became ready for reading for event in stdin_events { assert_eq!( event.r#type, wasi::__WASI_EVENTTYPE_FD_READ, "stdin was expected to be polled for reading" ); // Another limitation is that `std::io::BufRead` doesn't allow us // to find out the number bytes available in the buffer, // so we return the only universally correct lower bound, // which is 1 byte. let new_event = make_read_event(&event, Ok(1)); events.push(new_event); } } } } Ok(()) } fn get_monotonic_time() -> Duration { // We're circumventing the fact that we can't get a Duration from an Instant // The epoch of __WASI_CLOCKID_MONOTONIC is undefined, so we fix a time point once // and count relative to this time point. // // The alternative would be to copy over the implementation of std::time::Instant // to our source tree and add a conversion to std::time::Duration START_MONOTONIC.elapsed() } fn get_realtime_time() -> Result { SystemTime::now() .duration_since(UNIX_EPOCH) .map_err(|_| Error::EFAULT) } fn get_proc_cputime() -> Result { Ok(ProcessTime::try_now()?.as_duration()) } fn get_thread_cputime() -> Result { Ok(ThreadTime::try_now()?.as_duration()) } fn get_perf_counter_resolution_ns() -> u64 { use winx::time::perf_counter_frequency; const NANOS_PER_SEC: u64 = 1_000_000_000; // This should always succeed starting from Windows XP, so it's fine to panic in case of an error. let freq = perf_counter_frequency().expect("QueryPerformanceFrequency returned an error"); let epsilon = NANOS_PER_SEC / freq; epsilon }