Skip to content

Commit

Permalink
Update to wasmtime 22 (#824)
Browse files Browse the repository at this point in the history
This updates us to the latest version of wasmtime. The WASI API changed
a lot, because WASI 0.2 got released since then. We still only support
WASI 0.1 until there's practical toolchains for targeting WASI 0.2, so
we can experiment with it.

Unfortunately the new API does not allow us to define custom file system
logic anymore, so mapping to Windows device paths is not possible
anymore.
  • Loading branch information
CryZe authored Jul 8, 2024
1 parent bb8cb41 commit 8d5b0f6
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 188 deletions.
8 changes: 4 additions & 4 deletions crates/livesplit-auto-splitting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ sysinfo = { version = "0.30.0", default-features = false, features = [
"multithread",
] }
time = { version = "0.3.3", default-features = false }
wasmtime = { version = "17.0.0", default-features = false, features = [
wasmtime = { version = "22.0.0", default-features = false, features = [
"cranelift",
"parallel-compilation",
"runtime",
] }
wasmtime-wasi = { version = "17.0.0", default-features = false, features = [
"sync",
wasmtime-wasi = { version = "22.0.0", default-features = false, features = [
"preview1",
] }
wasi-common = "17.0.0"

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.52.0", features = ["Win32_Storage_FileSystem"] }
Expand Down
5 changes: 2 additions & 3 deletions crates/livesplit-auto-splitting/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ extern "C" {
list_ptr: *mut ProcessId,
list_len_ptr: *mut usize,
) -> bool;
/// Checks whether is a process is still open. You should detach from a
/// Checks whether a process is still open. You should detach from a
/// process and stop using it if this returns `false`.
pub fn process_is_open(process: Process) -> bool;
/// Reads memory from a process at the address given. This will write
Expand Down Expand Up @@ -534,8 +534,7 @@ support:
nothing.
- The file system is currently almost entirely empty. The host's file system is
accessible through `/mnt`. It is entirely read-only. Windows paths are mapped
to `/mnt/c`, `/mnt/d`, etc. to match WSL. Additionally `/mnt/device` maps to
`\\?\` on Windows to access additional paths.
to `/mnt/c`, `/mnt/d`, etc. to match WSL.
- There are no environment variables.
- There are no command line arguments.
- There is no networking.
Expand Down
5 changes: 2 additions & 3 deletions crates/livesplit-auto-splitting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
//! list_ptr: *mut ProcessId,
//! list_len_ptr: *mut usize,
//! ) -> bool;
//! /// Checks whether is a process is still open. You should detach from a
//! /// Checks whether a process is still open. You should detach from a
//! /// process and stop using it if this returns `false`.
//! pub fn process_is_open(process: Process) -> bool;
//! /// Reads memory from a process at the address given. This will write
Expand Down Expand Up @@ -534,8 +534,7 @@
//! nothing.
//! - The file system is currently almost entirely empty. The host's file system
//! is accessible through `/mnt`. It is entirely read-only. Windows paths are
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL. Additionally
//! `/mnt/device` maps to `\\?\` on Windows to access additional paths.
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL.
//! - There are no environment variables.
//! - There are no command line arguments.
//! - There is no networking.
Expand Down
185 changes: 19 additions & 166 deletions crates/livesplit-auto-splitting/src/runtime/api/wasi.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
use std::{
path::{Path, PathBuf},
str,
};
use std::{path::Path, str};

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Run benchmarks

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / Check clippy lints

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux x86_64)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux x86_64 musl)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux aarch64 musl)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux riscv64gc)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux s390x)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (macOS aarch64)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (macOS x86_64)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux Beta)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (Linux Nightly)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (macOS Beta)

unused import: `str`

Check warning on line 1 in crates/livesplit-auto-splitting/src/runtime/api/wasi.rs

View workflow job for this annotation

GitHub Actions / build (macOS Nightly)

unused import: `str`

use wasi_common::{
dir::{OpenResult, ReaddirCursor, ReaddirEntity},
file::{FdFlags, Filestat, OFlags},
ErrorExt, WasiCtx, WasiDir,
};
use wasmtime_wasi::{ambient_authority, WasiCtxBuilder};
use wasmtime_wasi::{preview1::WasiP1Ctx, DirPerms, FilePerms, WasiCtxBuilder};

use crate::wasi_path;

pub fn build(script_path: Option<&Path>) -> WasiCtx {
let mut wasi = WasiCtxBuilder::new().build();
pub fn build(script_path: Option<&Path>) -> WasiP1Ctx {
let mut wasi = WasiCtxBuilder::new();

if let Some(script_path) = script_path {
if let Some(path) = wasi_path::from_native(script_path) {
let _ = wasi.push_env("SCRIPT_PATH", &path);
wasi.env("SCRIPT_PATH", &path);
}
}

Expand All @@ -32,164 +24,25 @@ pub fn build(script_path: Option<&Path>) -> WasiCtx {
}
drives &= !(1 << drive_idx);
let drive = drive_idx as u8 + b'a';
if let Ok(path) = wasmtime_wasi::Dir::open_ambient_dir(
// Unfortunate if this fails, but we should still continue.
let _ = wasi.preopened_dir(
str::from_utf8(&[b'\\', b'\\', b'?', b'\\', drive, b':', b'\\']).unwrap(),
ambient_authority(),
) {
wasi.push_dir(
Box::new(ReadOnlyDir(wasmtime_wasi::dir::Dir::from_cap_std(path))),
PathBuf::from(str::from_utf8(&[b'/', b'm', b'n', b't', b'/', drive]).unwrap()),
)
.unwrap();
}
str::from_utf8(&[b'/', b'm', b'n', b't', b'/', drive]).unwrap(),
DirPerms::READ,
FilePerms::READ,
);
}

wasi.push_dir(Box::new(DeviceDir), PathBuf::from("/mnt/device"))
.unwrap();
// FIXME: Unfortunately wasmtime doesn't support us defining our own
// file system logic anymore.

// wasi.push_dir(Box::new(DeviceDir), PathBuf::from("/mnt/device"))
// .unwrap();
}
#[cfg(not(windows))]
{
if let Ok(path) = wasmtime_wasi::Dir::open_ambient_dir("/", ambient_authority()) {
wasi.push_dir(
Box::new(ReadOnlyDir(wasmtime_wasi::dir::Dir::from_cap_std(path))),
PathBuf::from("/mnt"),
)
.unwrap();
}
}
wasi
}

struct ReadOnlyDir(wasmtime_wasi::dir::Dir);

#[async_trait::async_trait]
impl WasiDir for ReadOnlyDir {
fn as_any(&self) -> &dyn std::any::Any {
self
}

async fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
read: bool,
write: bool,
fdflags: FdFlags,
) -> Result<OpenResult, wasi_common::Error> {
// We whitelist the OFlags and FdFlags to not accidentally allow
// ways to modify the file system.
const WHITELISTED_O_FLAGS: OFlags = OFlags::DIRECTORY;
const WHITELISTED_FD_FLAGS: FdFlags = FdFlags::NONBLOCK;

if write || !WHITELISTED_O_FLAGS.contains(oflags) || !WHITELISTED_FD_FLAGS.contains(fdflags)
{
return Err(wasi_common::Error::not_supported());
}

Ok(
match self
.0
.open_file_(symlink_follow, path, oflags, read, write, fdflags)?
{
wasmtime_wasi::dir::OpenResult::Dir(d) => OpenResult::Dir(Box::new(ReadOnlyDir(d))),
// We assume that wrapping the file type itself is not
// necessary, because we ensure that the open flags don't allow
// for any modifications anyway.
wasmtime_wasi::dir::OpenResult::File(f) => OpenResult::File(Box::new(f)),
},
)
}

async fn readdir(
&self,
cursor: ReaddirCursor,
) -> Result<
Box<dyn Iterator<Item = Result<ReaddirEntity, wasi_common::Error>> + Send>,
wasi_common::Error,
> {
self.0.readdir(cursor).await
}

async fn read_link(&self, path: &str) -> Result<PathBuf, wasi_common::Error> {
self.0.read_link(path).await
}

async fn get_filestat(&self) -> Result<Filestat, wasi_common::Error> {
// FIXME: Make sure this says it's readonly, if it ever contains the
// permissions.
self.0.get_filestat().await
// Unfortunate if this fails, but we should still continue.
let _ = wasi.preopened_dir("/", "/mnt", DirPerms::READ, FilePerms::READ);
}

async fn get_path_filestat(
&self,
path: &str,
follow_symlinks: bool,
) -> Result<Filestat, wasi_common::Error> {
// FIXME: Make sure this says it's readonly, if it ever contains the
// permissions.
self.0.get_path_filestat(path, follow_symlinks).await
}
}

#[cfg(windows)]
struct DeviceDir;

#[cfg(windows)]
#[async_trait::async_trait]
impl WasiDir for DeviceDir {
fn as_any(&self) -> &dyn std::any::Any {
self
}

async fn open_file(
&self,
symlink_follow: bool,
path: &str,
oflags: OFlags,
read: bool,
write: bool,
fdflags: FdFlags,
) -> Result<OpenResult, wasi_common::Error> {
let (dir, file) = device_path(path)?;
dir.open_file(symlink_follow, file, oflags, read, write, fdflags)
.await
}

// FIXME: cap-primitives/src/windows/fs/get_path tries to strip `\\?\`,
// which breaks paths that aren't valid without it, such as UNC paths:
// https://github.com/bytecodealliance/cap-std/issues/348

async fn read_link(&self, path: &str) -> Result<PathBuf, wasi_common::Error> {
let (dir, file) = device_path(path)?;
dir.read_link(file).await
}

async fn get_path_filestat(
&self,
path: &str,
follow_symlinks: bool,
) -> Result<Filestat, wasi_common::Error> {
let (dir, file) = device_path(path)?;
dir.get_path_filestat(file, follow_symlinks).await
}
}

#[cfg(windows)]
fn device_path(path: &str) -> Result<(ReadOnlyDir, &str), wasi_common::Error> {
let (parent, file) = path
.strip_suffix('/')
.unwrap_or(path)
.rsplit_once('/')
.ok_or_else(wasi_common::Error::not_supported)?;

let parent = wasi_path::to_native(&format!("/mnt/device/{parent}"), true)
.ok_or_else(wasi_common::Error::not_supported)?;

let dir = wasmtime_wasi::dir::Dir::from_cap_std(
wasmtime_wasi::Dir::open_ambient_dir(parent, ambient_authority())
.map_err(|_| wasi_common::Error::not_supported())?,
);

Ok((ReadOnlyDir(dir), file))
wasi.build_p1()
}
11 changes: 4 additions & 7 deletions crates/livesplit-auto-splitting/src/runtime/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
use wasmtime::{
Engine, Extern, Linker, Memory, Module, OptLevel, Store, TypedFunc, WasmBacktraceDetails,
};
use wasmtime_wasi::WasiCtx;
use wasmtime_wasi::preview1::WasiP1Ctx;

mod api;

Expand Down Expand Up @@ -89,7 +89,7 @@ pub struct Context<T> {
timer: T,
memory: Option<Memory>,
process_list: ProcessList,
wasi: WasiCtx,
wasi: WasiP1Ctx,
}

/// A thread-safe handle used to interrupt the execution of the script.
Expand Down Expand Up @@ -396,11 +396,8 @@ impl CompiledAutoSplitter {
.any(|import| import.module() == "wasi_snapshot_preview1");

if uses_wasi {
wasmtime_wasi::snapshots::preview_1::add_wasi_snapshot_preview1_to_linker(
&mut linker,
|ctx| &mut ctx.wasi,
)
.map_err(|source| CreationError::Wasi { source })?;
wasmtime_wasi::preview1::add_to_linker_sync(&mut linker, |ctx| &mut ctx.wasi)
.map_err(|source| CreationError::Wasi { source })?;
}

let instance = linker
Expand Down
2 changes: 1 addition & 1 deletion crates/livesplit-auto-splitting/src/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub enum TimerState {
}

/// A timer that can be controlled by an auto splitter.
pub trait Timer {
pub trait Timer: Send {
/// Returns the current state of the timer.
fn state(&self) -> TimerState;
/// Starts the timer.
Expand Down
7 changes: 3 additions & 4 deletions src/auto_splitting/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,7 @@
//! nothing.
//! - The file system is currently almost entirely empty. The host's file system
//! is accessible through `/mnt`. It is entirely read-only. Windows paths are
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL. Additionally
//! `/mnt/device` maps to `\\?\` on Windows to access additional paths.
//! mapped to `/mnt/c`, `/mnt/d`, etc. to match WSL.
//! - There are no environment variables.
//! - There are no command line arguments.
//! - There is no networking.
Expand Down Expand Up @@ -719,7 +718,7 @@ impl<T: event::CommandSink + TimerQuery + Send + 'static> Runtime<T> {
// is an Arc<RwLock<T>>, so we can't implement the trait directly on it.
struct Timer<E>(E);

impl<E: event::CommandSink + TimerQuery> AutoSplitTimer for Timer<E> {
impl<E: event::CommandSink + TimerQuery + Send> AutoSplitTimer for Timer<E> {
fn state(&self) -> TimerState {
match self.0.get_timer().current_phase() {
TimerPhase::NotRunning => TimerState::NotRunning,
Expand Down Expand Up @@ -770,7 +769,7 @@ impl<E: event::CommandSink + TimerQuery> AutoSplitTimer for Timer<E> {
}
}

async fn run<T: event::CommandSink + TimerQuery>(
async fn run<T: event::CommandSink + TimerQuery + Send>(
mut auto_splitter: watch::Receiver<Option<AutoSplitter<Timer<T>>>>,
timeout_sender: watch::Sender<Option<Instant>>,
interrupt_sender: watch::Sender<Option<InterruptHandle>>,
Expand Down

0 comments on commit 8d5b0f6

Please sign in to comment.