Update thread and futex APIs to work with Emscripten

This updates the thread and futex APIs in `std` to match the APIs exposed by
Emscripten. This allows threads to run on `wasm32-unknown-emscripten` and the
thread parker to compile without errors related to the missing `futex` module.

To make use of this, Rust code must be compiled with `-C target-feature=atomics`
and Emscripten must link with `-pthread`.

I have confirmed this works well locally when building multithreaded crates.
Attempting to enable `std` thread tests currently fails for seemingly obscure
reasons and Emscripten is currently disabled in CI, so further work is needed to
have proper test coverage here.
This commit is contained in:
J. Ryan Stinnett 2020-11-11 22:25:48 +00:00
parent e3051d8c24
commit 951576051b
2 changed files with 43 additions and 20 deletions

View file

@ -1,10 +1,17 @@
#![cfg(any(target_os = "linux", target_os = "android"))]
#![cfg(any(
target_os = "linux",
target_os = "android",
all(target_os = "emscripten", target_feature = "atomics")
))]
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::convert::TryInto;
#[cfg(any(target_os = "linux", target_os = "android"))]
use crate::ptr::null;
use crate::sync::atomic::AtomicI32;
use crate::time::Duration;
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn futex_wait(futex: &AtomicI32, expected: i32, timeout: Option<Duration>) {
let timespec = timeout.and_then(|d| {
Some(libc::timespec {
@ -25,6 +32,29 @@ pub fn futex_wait(futex: &AtomicI32, expected: i32, timeout: Option<Duration>) {
}
}
#[cfg(target_os = "emscripten")]
pub fn futex_wait(futex: &AtomicI32, expected: i32, timeout: Option<Duration>) {
extern "C" {
fn emscripten_futex_wait(
addr: *const AtomicI32,
val: libc::c_uint,
max_wait_ms: libc::c_double,
) -> libc::c_int;
}
let timeout_ms = timeout.map(|d| d.as_millis());
unsafe {
emscripten_futex_wait(
futex as *const AtomicI32,
// `val` is declared unsigned to match the Emscripten headers, but since it's used as
// an opaque value, we can ignore the meaning of signed vs. unsigned and cast here.
expected as libc::c_uint,
timeout_ms.map_or(crate::f64::INFINITY, |d| d as libc::c_double),
);
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn futex_wake(futex: &AtomicI32) {
unsafe {
libc::syscall(
@ -35,3 +65,14 @@ pub fn futex_wake(futex: &AtomicI32) {
);
}
}
#[cfg(target_os = "emscripten")]
pub fn futex_wake(futex: &AtomicI32) {
extern "C" {
fn emscripten_futex_wake(addr: *const AtomicI32, count: libc::c_int) -> libc::c_int;
}
unsafe {
emscripten_futex_wake(futex as *const AtomicI32, 1);
}
}

View file

@ -22,24 +22,6 @@ pub struct Thread {
unsafe impl Send for Thread {}
unsafe impl Sync for Thread {}
// The pthread_attr_setstacksize symbol doesn't exist in the emscripten libc,
// so we have to not link to it to satisfy emcc's ERROR_ON_UNDEFINED_SYMBOLS.
#[cfg(not(target_os = "emscripten"))]
unsafe fn pthread_attr_setstacksize(
attr: *mut libc::pthread_attr_t,
stack_size: libc::size_t,
) -> libc::c_int {
libc::pthread_attr_setstacksize(attr, stack_size)
}
#[cfg(target_os = "emscripten")]
unsafe fn pthread_attr_setstacksize(
_attr: *mut libc::pthread_attr_t,
_stack_size: libc::size_t,
) -> libc::c_int {
panic!()
}
impl Thread {
// unsafe: see thread::Builder::spawn_unchecked for safety requirements
pub unsafe fn new(stack: usize, p: Box<dyn FnOnce()>) -> io::Result<Thread> {
@ -50,7 +32,7 @@ impl Thread {
let stack_size = cmp::max(stack, min_stack_size(&attr));
match pthread_attr_setstacksize(&mut attr, stack_size) {
match libc::pthread_attr_setstacksize(&mut attr, stack_size) {
0 => {}
n => {
assert_eq!(n, libc::EINVAL);