Skip to content

Commit edfa10d

Browse files
abrownharaldhalexcrichton
authored
wasi-threads: an initial implementation (#5484)
This commit includes a set of changes that add initial support for `wasi-threads` to Wasmtime: * feat: remove mutability from the WasiCtx Table This patch adds interior mutability to the WasiCtx Table and the Table elements. Major pain points: * `File` only needs `RwLock<cap_std::fs::File>` to implement `File::set_fdflags()` on Windows, because of [1] * Because `File` needs a `RwLock` and `RwLock*Guard` cannot be hold across an `.await`, The `async` from `async fn num_ready_bytes(&self)` had to be removed * Because `File` needs a `RwLock` and `RwLock*Guard` cannot be dereferenced in `pollable`, the signature of `fn pollable(&self) -> Option<rustix::fd::BorrowedFd>` changed to `fn pollable(&self) -> Option<Arc<dyn AsFd + '_>>` [1] https://github.com/bytecodealliance/system-interface/blob/da238e324e752033f315f09c082ad9ce35d42696/src/fs/fd_flags.rs#L210-L217 * wasi-threads: add an initial implementation This change is a first step toward implementing `wasi-threads` in Wasmtime. We may find that it has some missing pieces, but the core functionality is there: when `wasi::thread_spawn` is called by a running WebAssembly module, a function named `wasi_thread_start` is found in the module's exports and called in a new instance. The shared memory of the original instance is reused in the new instance. This new WASI proposal is in its early stages and details are still being hashed out in the [spec] and [wasi-libc] repositories. Due to its experimental state, the `wasi-threads` functionality is hidden behind both a compile-time and runtime flag: one must build with `--features wasi-threads` but also run the Wasmtime CLI with `--wasm-features threads` and `--wasi-modules experimental-wasi-threads`. One can experiment with `wasi-threads` by running: ```console $ cargo run --features wasi-threads -- \ --wasm-features threads --wasi-modules experimental-wasi-threads \ <a threads-enabled module> ``` Threads-enabled Wasm modules are not yet easy to build. Hopefully this is resolved soon, but in the meantime see the use of `THREAD_MODEL=posix` in the [wasi-libc] repository for some clues on what is necessary. Wiggle complicates things by requiring the Wasm memory to be exported with a certain name and `wasi-threads` also expects that memory to be imported; this build-time obstacle can be overcome with the `--import-memory --export-memory` flags only available in the latest Clang tree. Due to all of this, the included tests are written directly in WAT--run these with: ```console $ cargo test --features wasi-threads -p wasmtime-cli -- cli_tests ``` [spec]: https://github.com/WebAssembly/wasi-threads [wasi-libc]: https://github.com/WebAssembly/wasi-libc This change does not protect the WASI implementations themselves from concurrent access. This is already complete in previous commits or left for future commits in certain cases (e.g., wasi-nn). * wasi-threads: factor out process exit logic As is being discussed [elsewhere], either calling `proc_exit` or trapping in any thread should halt execution of all threads. The Wasmtime CLI already has logic for adapting a WebAssembly error code to a code expected in each OS. This change factors out this logic to a new function, `maybe_exit_on_error`, for use within the `wasi-threads` implementation. This will work reasonably well for CLI users of Wasmtime + `wasi-threads`, but embedders will want something better in the future: when a `wasi-threads` threads fails, they may not want their application to exit. Handling this is tricky, because it will require cancelling the threads spawned by the `wasi-threads` implementation, something that is not trivial to do in Rust. With this change, we defer that work until later in order to provide a working implementation of `wasi-threads` for experimentation. [elsewhere]: WebAssembly/wasi-threads#17 * review: work around `fd_fdstat_set_flags` In order to make progress with wasi-threads, this change temporarily works around limitations induced by `wasi-common`'s `fd_fdstat_set_flags` to allow `&mut self` use in the implementation. Eventual resolution is tracked in #5643. This change makes several related helper functions (e.g., `set_fdflags`) take `&mut self` as well. * test: use `wait`/`notify` to improve `threads.wat` test Previously, the test simply executed in a loop for some hardcoded number of iterations. This changes uses `wait` and `notify` and atomic operations to keep track of when the spawned threads are done and join on the main thread appropriately. * various fixes and tweaks due to the PR review --------- Signed-off-by: Harald Hoyer <[email protected]> Co-authored-by: Harald Hoyer <[email protected]> Co-authored-by: Alex Crichton <[email protected]>
1 parent 2c84259 commit edfa10d

File tree

33 files changed

+863
-421
lines changed

33 files changed

+863
-421
lines changed

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ wasmtime-cli-flags = { workspace = true }
2828
wasmtime-cranelift = { workspace = true }
2929
wasmtime-environ = { workspace = true }
3030
wasmtime-wast = { workspace = true }
31-
wasmtime-wasi = { workspace = true }
31+
wasmtime-wasi = { workspace = true, features = ["exit"] }
3232
wasmtime-wasi-crypto = { workspace = true, optional = true }
3333
wasmtime-wasi-nn = { workspace = true, optional = true }
34+
wasmtime-wasi-threads = { workspace = true, optional = true }
3435
clap = { workspace = true, features = ["color", "suggestions", "derive"] }
3536
anyhow = { workspace = true }
3637
target-lexicon = { workspace = true }
37-
libc = "0.2.60"
3838
humantime = "2.0.0"
3939
once_cell = { workspace = true }
4040
listenfd = "1.0.0"
@@ -68,6 +68,7 @@ wasmtime-component-util = { workspace = true }
6868
component-macro-test = { path = "crates/misc/component-macro-test" }
6969
component-test-util = { workspace = true }
7070
bstr = "0.2.17"
71+
libc = "0.2.60"
7172

7273
[target.'cfg(windows)'.dev-dependencies]
7374
windows-sys = { workspace = true, features = ["Win32_System_Memory"] }
@@ -124,6 +125,7 @@ wasmtime-wast = { path = "crates/wast", version = "=7.0.0" }
124125
wasmtime-wasi = { path = "crates/wasi", version = "7.0.0" }
125126
wasmtime-wasi-crypto = { path = "crates/wasi-crypto", version = "7.0.0" }
126127
wasmtime-wasi-nn = { path = "crates/wasi-nn", version = "7.0.0" }
128+
wasmtime-wasi-threads = { path = "crates/wasi-threads", version = "7.0.0" }
127129
wasmtime-component-util = { path = "crates/component-util", version = "=7.0.0" }
128130
wasmtime-component-macro = { path = "crates/component-macro", version = "=7.0.0" }
129131
wasmtime-asm-macros = { path = "crates/asm-macros", version = "=7.0.0" }
@@ -205,6 +207,7 @@ jitdump = ["wasmtime/jitdump"]
205207
vtune = ["wasmtime/vtune"]
206208
wasi-crypto = ["dep:wasmtime-wasi-crypto"]
207209
wasi-nn = ["dep:wasmtime-wasi-nn"]
210+
wasi-threads = ["dep:wasmtime-wasi-threads"]
208211
pooling-allocator = ["wasmtime/pooling-allocator", "wasmtime-cli-flags/pooling-allocator"]
209212
all-arch = ["wasmtime/all-arch"]
210213
posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"]

ci/run-tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
cargo test \
44
--features "test-programs/test_programs" \
5+
--features wasi-threads \
56
--workspace \
67
--exclude 'wasmtime-wasi-*' \
78
--exclude wasi-crypto \

crates/cli-flags/src/lib.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,17 @@ pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[
5050
"wasi-common",
5151
"enables support for the WASI common APIs, see https://github.com/WebAssembly/WASI",
5252
),
53+
(
54+
"experimental-wasi-crypto",
55+
"enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto",
56+
),
5357
(
5458
"experimental-wasi-nn",
5559
"enables support for the WASI neural network API (experimental), see https://github.com/WebAssembly/wasi-nn",
5660
),
5761
(
58-
"experimental-wasi-crypto",
59-
"enables support for the WASI cryptography APIs (experimental), see https://github.com/WebAssembly/wasi-crypto",
62+
"experimental-wasi-threads",
63+
"enables support for the WASI threading API (experimental), see https://github.com/WebAssembly/wasi-threads",
6064
),
6165
];
6266

@@ -466,8 +470,9 @@ fn parse_wasi_modules(modules: &str) -> Result<WasiModules> {
466470
let mut set = |module: &str, enable: bool| match module {
467471
"" => Ok(()),
468472
"wasi-common" => Ok(wasi_modules.wasi_common = enable),
469-
"experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable),
470473
"experimental-wasi-crypto" => Ok(wasi_modules.wasi_crypto = enable),
474+
"experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = enable),
475+
"experimental-wasi-threads" => Ok(wasi_modules.wasi_threads = enable),
471476
"default" => bail!("'default' cannot be specified with other WASI modules"),
472477
_ => bail!("unsupported WASI module '{}'", module),
473478
};
@@ -494,19 +499,23 @@ pub struct WasiModules {
494499
/// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.).
495500
pub wasi_common: bool,
496501

497-
/// Enable the experimental wasi-nn implementation
502+
/// Enable the experimental wasi-crypto implementation.
503+
pub wasi_crypto: bool,
504+
505+
/// Enable the experimental wasi-nn implementation.
498506
pub wasi_nn: bool,
499507

500-
/// Enable the experimental wasi-crypto implementation
501-
pub wasi_crypto: bool,
508+
/// Enable the experimental wasi-threads implementation.
509+
pub wasi_threads: bool,
502510
}
503511

504512
impl Default for WasiModules {
505513
fn default() -> Self {
506514
Self {
507515
wasi_common: true,
508-
wasi_nn: false,
509516
wasi_crypto: false,
517+
wasi_nn: false,
518+
wasi_threads: false,
510519
}
511520
}
512521
}
@@ -518,6 +527,7 @@ impl WasiModules {
518527
wasi_common: false,
519528
wasi_nn: false,
520529
wasi_crypto: false,
530+
wasi_threads: false,
521531
}
522532
}
523533
}
@@ -663,8 +673,9 @@ mod test {
663673
options.wasi_modules.unwrap(),
664674
WasiModules {
665675
wasi_common: true,
676+
wasi_crypto: false,
666677
wasi_nn: false,
667-
wasi_crypto: false
678+
wasi_threads: false
668679
}
669680
);
670681
}
@@ -676,8 +687,9 @@ mod test {
676687
options.wasi_modules.unwrap(),
677688
WasiModules {
678689
wasi_common: true,
690+
wasi_crypto: false,
679691
wasi_nn: false,
680-
wasi_crypto: false
692+
wasi_threads: false
681693
}
682694
);
683695
}
@@ -693,8 +705,9 @@ mod test {
693705
options.wasi_modules.unwrap(),
694706
WasiModules {
695707
wasi_common: false,
708+
wasi_crypto: false,
696709
wasi_nn: true,
697-
wasi_crypto: false
710+
wasi_threads: false
698711
}
699712
);
700713
}
@@ -707,8 +720,9 @@ mod test {
707720
options.wasi_modules.unwrap(),
708721
WasiModules {
709722
wasi_common: false,
723+
wasi_crypto: false,
710724
wasi_nn: false,
711-
wasi_crypto: false
725+
wasi_threads: false
712726
}
713727
);
714728
}

crates/wasi-common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ tracing = { workspace = true }
2626
cap-std = { workspace = true }
2727
cap-rand = { workspace = true }
2828
bitflags = { workspace = true }
29+
log = { workspace = true }
2930

3031
[target.'cfg(unix)'.dependencies]
3132
rustix = { workspace = true, features = ["fs"] }

crates/wasi-common/cap-std-sync/src/file.rs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,23 @@ impl WasiFile for File {
3131
fn pollable(&self) -> Option<rustix::fd::BorrowedFd> {
3232
Some(self.0.as_fd())
3333
}
34-
3534
#[cfg(windows)]
3635
fn pollable(&self) -> Option<io_extras::os::windows::RawHandleOrSocket> {
3736
Some(self.0.as_raw_handle_or_socket())
3837
}
39-
async fn datasync(&mut self) -> Result<(), Error> {
38+
async fn datasync(&self) -> Result<(), Error> {
4039
self.0.sync_data()?;
4140
Ok(())
4241
}
43-
async fn sync(&mut self) -> Result<(), Error> {
42+
async fn sync(&self) -> Result<(), Error> {
4443
self.0.sync_all()?;
4544
Ok(())
4645
}
47-
async fn get_filetype(&mut self) -> Result<FileType, Error> {
46+
async fn get_filetype(&self) -> Result<FileType, Error> {
4847
let meta = self.0.metadata()?;
4948
Ok(filetype_from(&meta.file_type()))
5049
}
51-
async fn get_fdflags(&mut self) -> Result<FdFlags, Error> {
50+
async fn get_fdflags(&self) -> Result<FdFlags, Error> {
5251
let fdflags = get_fd_flags(&self.0)?;
5352
Ok(fdflags)
5453
}
@@ -64,7 +63,7 @@ impl WasiFile for File {
6463
self.0.set_fd_flags(set_fd_flags)?;
6564
Ok(())
6665
}
67-
async fn get_filestat(&mut self) -> Result<Filestat, Error> {
66+
async fn get_filestat(&self) -> Result<Filestat, Error> {
6867
let meta = self.0.metadata()?;
6968
Ok(Filestat {
7069
device_id: meta.dev(),
@@ -77,62 +76,62 @@ impl WasiFile for File {
7776
ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None),
7877
})
7978
}
80-
async fn set_filestat_size(&mut self, size: u64) -> Result<(), Error> {
79+
async fn set_filestat_size(&self, size: u64) -> Result<(), Error> {
8180
self.0.set_len(size)?;
8281
Ok(())
8382
}
84-
async fn advise(&mut self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
83+
async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> {
8584
self.0.advise(offset, len, convert_advice(advice))?;
8685
Ok(())
8786
}
88-
async fn allocate(&mut self, offset: u64, len: u64) -> Result<(), Error> {
87+
async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> {
8988
self.0.allocate(offset, len)?;
9089
Ok(())
9190
}
9291
async fn set_times(
93-
&mut self,
92+
&self,
9493
atime: Option<wasi_common::SystemTimeSpec>,
9594
mtime: Option<wasi_common::SystemTimeSpec>,
9695
) -> Result<(), Error> {
9796
self.0
9897
.set_times(convert_systimespec(atime), convert_systimespec(mtime))?;
9998
Ok(())
10099
}
101-
async fn read_vectored<'a>(&mut self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
100+
async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result<u64, Error> {
102101
let n = self.0.read_vectored(bufs)?;
103102
Ok(n.try_into()?)
104103
}
105104
async fn read_vectored_at<'a>(
106-
&mut self,
105+
&self,
107106
bufs: &mut [io::IoSliceMut<'a>],
108107
offset: u64,
109108
) -> Result<u64, Error> {
110109
let n = self.0.read_vectored_at(bufs, offset)?;
111110
Ok(n.try_into()?)
112111
}
113-
async fn write_vectored<'a>(&mut self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
112+
async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result<u64, Error> {
114113
let n = self.0.write_vectored(bufs)?;
115114
Ok(n.try_into()?)
116115
}
117116
async fn write_vectored_at<'a>(
118-
&mut self,
117+
&self,
119118
bufs: &[io::IoSlice<'a>],
120119
offset: u64,
121120
) -> Result<u64, Error> {
122121
let n = self.0.write_vectored_at(bufs, offset)?;
123122
Ok(n.try_into()?)
124123
}
125-
async fn seek(&mut self, pos: std::io::SeekFrom) -> Result<u64, Error> {
124+
async fn seek(&self, pos: std::io::SeekFrom) -> Result<u64, Error> {
126125
Ok(self.0.seek(pos)?)
127126
}
128-
async fn peek(&mut self, buf: &mut [u8]) -> Result<u64, Error> {
127+
async fn peek(&self, buf: &mut [u8]) -> Result<u64, Error> {
129128
let n = self.0.peek(buf)?;
130129
Ok(n.try_into()?)
131130
}
132-
async fn num_ready_bytes(&self) -> Result<u64, Error> {
131+
fn num_ready_bytes(&self) -> Result<u64, Error> {
133132
Ok(self.0.num_ready_bytes()?)
134133
}
135-
fn isatty(&mut self) -> bool {
134+
fn isatty(&self) -> bool {
136135
self.0.is_terminal()
137136
}
138137
}

crates/wasi-common/cap-std-sync/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,15 @@ impl WasiCtxBuilder {
9494
}
9595
Ok(self)
9696
}
97-
pub fn stdin(mut self, f: Box<dyn WasiFile>) -> Self {
97+
pub fn stdin(self, f: Box<dyn WasiFile>) -> Self {
9898
self.0.set_stdin(f);
9999
self
100100
}
101-
pub fn stdout(mut self, f: Box<dyn WasiFile>) -> Self {
101+
pub fn stdout(self, f: Box<dyn WasiFile>) -> Self {
102102
self.0.set_stdout(f);
103103
self
104104
}
105-
pub fn stderr(mut self, f: Box<dyn WasiFile>) -> Self {
105+
pub fn stderr(self, f: Box<dyn WasiFile>) -> Self {
106106
self.0.set_stderr(f);
107107
self
108108
}
@@ -118,12 +118,12 @@ impl WasiCtxBuilder {
118118
pub fn inherit_stdio(self) -> Self {
119119
self.inherit_stdin().inherit_stdout().inherit_stderr()
120120
}
121-
pub fn preopened_dir(mut self, dir: Dir, guest_path: impl AsRef<Path>) -> Result<Self, Error> {
121+
pub fn preopened_dir(self, dir: Dir, guest_path: impl AsRef<Path>) -> Result<Self, Error> {
122122
let dir = Box::new(crate::dir::Dir::from_cap_std(dir));
123123
self.0.push_preopened_dir(dir, guest_path)?;
124124
Ok(self)
125125
}
126-
pub fn preopened_socket(mut self, fd: u32, socket: impl Into<Socket>) -> Result<Self, Error> {
126+
pub fn preopened_socket(self, fd: u32, socket: impl Into<Socket>) -> Result<Self, Error> {
127127
let socket: Socket = socket.into();
128128
let file: Box<dyn WasiFile> = socket.into();
129129

0 commit comments

Comments
 (0)