Hi.
Thank you guys for your great project.
I am trying wasm32-unknown-unknown and got an error(panic) in std::time::SystemTime::now().
Here is a reproduction code.
#[no_mangle]
pub fn test() -> u32 {
use std::time;
let now = time::SystemTime::now();
return 1;
}
<html lang="en">
<head>
<meta charset="utf-8">
<script>
fetch('wasm_time.wasm')
.then((response) => response.arrayBuffer())
.then((bytes) => WebAssembly.instantiate(bytes, {}))
.then((results) => {
const instance = results.instance;
console.log(instance.exports.test());
});
</script>
</head>
<body>
</body>
</html>
The error on Chrome on macOS. Similar error on Safari on macOS.
wasm-000873fa-78:2 Uncaught (in promise) RuntimeError: unreachable
at __rust_start_panic (wasm-function[78]:1)
at rust_panic (wasm-function[19]:40)
at _ZN3std9panicking20rust_panic_with_hook17h4d23d2414abbb417E (wasm-function[14]:708)
at _ZN3std9panicking11begin_panic17ha318dfc1360611dcE (wasm-function[11]:298)
at _ZN3std3sys4wasm11TimeSysCall7perform17hfcfe4d7cbd5bdf2dE (wasm-function[69]:31)
at _ZN3std3sys4wasm4time10SystemTime3now17h259757a3c249e0c6E (wasm-function[71]:20)
at _ZN3std4time10SystemTime3now17hd79e83558f3b295bE (wasm-function[70]:1)
at test (wasm-function[0]:38)
at fetch.then.then.then (http://localhost:8000/:10:40)
at <anonymous>
__rust_start_panic @ wasm-000873fa-78:2
rust_panic @ wasm-000873fa-19:21
_ZN3std9panicking20rust_panic_with_hook17h4d23d2414abbb417E @ wasm-000873fa-14:346
_ZN3std9panicking11begin_panic17ha318dfc1360611dcE @ wasm-000873fa-11:146
_ZN3std3sys4wasm11TimeSysCall7perform17hfcfe4d7cbd5bdf2dE @ wasm-000873fa-69:16
_ZN3std3sys4wasm4time10SystemTime3now17h259757a3c249e0c6E @ wasm-000873fa-71:12
_ZN3std4time10SystemTime3now17hd79e83558f3b295bE @ wasm-000873fa-70:2
test @ wasm-000873fa-0:20
fetch.then.then.then @ (index):10
Promise.then (async)
(anonymous) @ (index):8
My environment is,
OS : macOS 10.13.3
Rust: rustc 1.26.0-nightly (322d7f7b9 2018-02-25)
Chrome: 64.0.3282.167
Sorry, I have no idea to confirm the version of wasm32-unknown-unknown.
Cheers.
It's weird that it seemingly panics with unreachable!() when, as far as I can tell, that macro was never used even when the time code was stubbed out with panics. Getting the time should actually work now unless the syscall interface is wrong.
Thanks for the report! This is unfortunately, for now, an intended implementation detail of the standard library. The unreachable is actually an "illegal instruction" for wasm which is what happens when Rust panics, and Rust currently panics for modules in libstd that don't actually map to wasm (like Instant)
@alexcrichton , isn't this commit from Jan 30th should have resolved this issue completely?
@hodlbank unfortunately that just added the possibility of another backend, but by default this still panics
There is currently an implementation for SystemTime and Instant (which do not panic) in libstd/sys/wasm/time.rs. Shouldn't this suffice?
Still panics on nightly. Poking at this there's 3 issues, if I'm understanding right:
1) The wasm_syscall feature is off by default (would require an imports object if enabled, which sounds like a breaking change...?)
2) wasm32-shim.js isn't distributed with rust installs, nor anything else like wasm-bindgen (but versioning with the rust stdlib seems to make the most sense anyways...?)
3) Most of the shim syscalls assume a node-like environment and will fail in the browser, although the time one should work.
@MaulingMonkey currently things are working as intended today. We're unlikely to ever stabilize the wasm_syscall feature and wasm32-shim.js is only intended for the test suite. Currently it's expected that the standard library's implementation of these primitives panics on the wasm32-unknown-unknown target since there's no way to implement them. For the wasm32-wasi target though they are implemented.
Sweet, I'll try out WASI at some point. For anyone else who stumbles across this issue but isn't quite ready to switch away from wasm32-unknown-unknown yet, here's a quick, poorly tested drop-in replacement for std::time you can plop into your project:
#![allow(dead_code, unused_imports)]
use wasm_bindgen::prelude::*;
use std::convert::{TryInto};
use std::ops::{Add, Sub, AddAssign, SubAssign};
pub use std::time::*;
#[cfg(not(target_arch = "wasm32"))] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Instant(std::time::Instant);
#[cfg(not(target_arch = "wasm32"))] impl Instant {
pub fn now() -> Self { Self(std::time::Instant::now()) }
pub fn duration_since(&self, earlier: Instant) -> Duration { self.0.duration_since(earlier.0) }
pub fn elapsed(&self) -> Duration { self.0.elapsed() }
pub fn checked_add(&self, duration: Duration) -> Option<Self> { self.0.checked_add(duration).map(|i| Self(i)) }
pub fn checked_sub(&self, duration: Duration) -> Option<Self> { self.0.checked_sub(duration).map(|i| Self(i)) }
}
#[cfg(target_arch = "wasm32")] #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = Date, js_name = now)] fn date_now() -> f64; }
#[cfg(target_arch = "wasm32")] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Instant(u64);
#[cfg(target_arch = "wasm32")] impl Instant {
pub fn now() -> Self { Self(date_now() as u64) }
pub fn duration_since(&self, earlier: Instant) -> Duration { Duration::from_millis(self.0 - earlier.0) }
pub fn elapsed(&self) -> Duration { Self::now().duration_since(*self) }
pub fn checked_add(&self, duration: Duration) -> Option<Self> {
match duration.as_millis().try_into() {
Ok(duration) => self.0.checked_add(duration).map(|i| Self(i)),
Err(_) => None,
}
}
pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
match duration.as_millis().try_into() {
Ok(duration) => self.0.checked_sub(duration).map(|i| Self(i)),
Err(_) => None,
}
}
}
impl Add<Duration> for Instant { type Output = Instant; fn add(self, other: Duration) -> Instant { self.checked_add(other).unwrap() } }
impl Sub<Duration> for Instant { type Output = Instant; fn sub(self, other: Duration) -> Instant { self.checked_sub(other).unwrap() } }
impl Sub<Instant> for Instant { type Output = Duration; fn sub(self, other: Instant) -> Duration { self.duration_since(other) } }
impl AddAssign<Duration> for Instant { fn add_assign(&mut self, other: Duration) { *self = *self + other; } }
impl SubAssign<Duration> for Instant { fn sub_assign(&mut self, other: Duration) { *self = *self - other; } }
So it compiles, but for sure doesn't run... and I stress again Rust compiles it.
I just spent days debugging coming to this. What is the workaround? Is it not possible to get the current timestamp in WASM?
@Tails You have to use the code from https://github.com/rust-lang/rust/issues/48564#issuecomment-505114709 though I wonder why it's not a crate...
I can see where getting the time differs from wasm runtime to runtime, but I also see the target being used is wasm32-unknown-unknown, where something like wasm32-unknown-html5, wasm32-unknown-node could be used to enable getting the time on those 'systems'.
Why does std::time::SystemTime::now() not fail when the target is wasm32-unknown-emscripten, but it does fail when the target is wasm32-unknown-unknown ?
@crawlingChaos I think it's because the system type is emscripten and as such the API is known... emscripten has a libc/POSIX API, right? Where as with an unknown system designation the API is literally unknown.
The code in https://github.com/rust-lang/rust/issues/48564#issuecomment-505114709 is great, but missing an important detail. Date.now() breaks the requirement that std::time::Instant be monotonic. Right now, it's more like std::time::SystemTime. A better replacement can use performance.now().
https://github.com/rust-lang/rust/issues/48564#issuecomment-505114709 rewritten to use performance.now:
#![allow(dead_code, unused_imports)]
use wasm_bindgen::prelude::*;
use std::convert::{TryInto};
use std::ops::{Add, Sub, AddAssign, SubAssign};
pub use std::time::*;
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(std::time::Instant);
#[cfg(not(target_arch = "wasm32"))]
impl Instant {
pub fn now() -> Self { Self(std::time::Instant::now()) }
pub fn duration_since(&self, earlier: Instant) -> Duration { self.0.duration_since(earlier.0) }
pub fn elapsed(&self) -> Duration { self.0.elapsed() }
pub fn checked_add(&self, duration: Duration) -> Option<Self> { self.0.checked_add(duration).map(|i| Self(i)) }
pub fn checked_sub(&self, duration: Duration) -> Option<Self> { self.0.checked_sub(duration).map(|i| Self(i)) }
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(inline_js = r#"
export function performance_now() {
return performance.now();
}"#)]
extern "C" {
fn performance_now() -> f64;
}
#[cfg(target_arch = "wasm32")]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(u64);
#[cfg(target_arch = "wasm32")]
impl Instant {
pub fn now() -> Self { Self((performance_now() * 1000.0) as u64) }
pub fn duration_since(&self, earlier: Instant) -> Duration { Duration::from_micros(self.0 - earlier.0) }
pub fn elapsed(&self) -> Duration { Self::now().duration_since(*self) }
pub fn checked_add(&self, duration: Duration) -> Option<Self> {
match duration.as_micros().try_into() {
Ok(duration) => self.0.checked_add(duration).map(|i| Self(i)),
Err(_) => None,
}
}
pub fn checked_sub(&self, duration: Duration) -> Option<Self> {
match duration.as_micros().try_into() {
Ok(duration) => self.0.checked_sub(duration).map(|i| Self(i)),
Err(_) => None,
}
}
}
impl Add<Duration> for Instant { type Output = Instant; fn add(self, other: Duration) -> Instant { self.checked_add(other).unwrap() } }
impl Sub<Duration> for Instant { type Output = Instant; fn sub(self, other: Duration) -> Instant { self.checked_sub(other).unwrap() } }
impl Sub<Instant> for Instant { type Output = Duration; fn sub(self, other: Instant) -> Duration { self.duration_since(other) } }
impl AddAssign<Duration> for Instant { fn add_assign(&mut self, other: Duration) { *self = *self + other; } }
impl SubAssign<Duration> for Instant { fn sub_assign(&mut self, other: Duration) { *self = *self - other; } }
Note: If you're targeting Node, it needs to be at least v8.5.0.
Most helpful comment
Sweet, I'll try out WASI at some point. For anyone else who stumbles across this issue but isn't quite ready to switch away from
wasm32-unknown-unknownyet, here's a quick, poorly tested drop-in replacement for std::time you can plop into your project: