Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create wasmtime::Config from toml #9811

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/cli-flags/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ tracing-subscriber = { workspace = true, optional = true }
rayon = { version = "1.5.0", optional = true }
wasmtime = { workspace = true, features = ["gc"] }
humantime = { workspace = true }
serde = { workspace = true }
serde_derive = { workspace = true }
toml = { workspace = true }

[features]
async = ["wasmtime/async"]
Expand Down
192 changes: 185 additions & 7 deletions crates/cli-flags/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

use anyhow::Result;
use clap::Parser;
use std::time::Duration;
use serde::Deserialize;
use std::{fs, path::Path, time::Duration};
use wasmtime::Config;

pub mod opt;
Expand Down Expand Up @@ -37,12 +38,17 @@ fn init_file_per_thread_logger(prefix: &'static str) {
}

wasmtime_option_group! {
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OptimizeOptions {
/// Optimization level of generated code (0-2, s; default: 2)
#[serde(default)]
#[serde(deserialize_with = "crate::opt::deserialize_func")]
pub opt_level: Option<wasmtime::OptLevel>,

/// Register allocator algorithm choice.
#[serde(default)]
#[serde(deserialize_with = "crate::opt::deserialize_func")]
pub regalloc_algorithm: Option<wasmtime::RegallocAlgorithm>,

/// Do not allow Wasm linear memories to move in the host process's
Expand Down Expand Up @@ -189,12 +195,15 @@ wasmtime_option_group! {
}

wasmtime_option_group! {
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CodegenOptions {
/// Either `cranelift` or `winch`.
///
/// Currently only `cranelift` and `winch` are supported, but not all
/// builds of Wasmtime have both built in.
#[serde(default)]
#[serde(deserialize_with = "crate::opt::deserialize_func")]
pub compiler: Option<wasmtime::Strategy>,
/// Which garbage collector to use: `drc` or `null`.
///
Expand All @@ -205,6 +214,8 @@ wasmtime_option_group! {
///
/// Note that not all builds of Wasmtime will have support for garbage
/// collection included.
#[serde(default)]
#[serde(deserialize_with = "crate::opt::deserialize_func")]
pub collector: Option<wasmtime::Collector>,
/// Enable Cranelift's internal debug verifier (expensive)
pub cranelift_debug_verifier: Option<bool>,
Expand All @@ -221,6 +232,7 @@ wasmtime_option_group! {
pub native_unwind_info: Option<bool>,

#[prefixed = "cranelift"]
#[serde(default)]
/// Set a cranelift-specific option. Use `wasmtime settings` to see
/// all.
pub cranelift: Vec<(String, Option<String>)>,
Expand All @@ -232,7 +244,8 @@ wasmtime_option_group! {
}

wasmtime_option_group! {
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DebugOptions {
/// Enable generation of DWARF debug information in compiled code.
pub debug_info: Option<bool>,
Expand All @@ -252,7 +265,8 @@ wasmtime_option_group! {
}

wasmtime_option_group! {
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WasmOptions {
/// Enable canonicalization of all NaN values.
pub nan_canonicalization: Option<bool>,
Expand Down Expand Up @@ -361,7 +375,8 @@ wasmtime_option_group! {
}

wasmtime_option_group! {
#[derive(PartialEq, Clone)]
#[derive(PartialEq, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WasiOptions {
/// Enable support for WASI CLI APIs, including filesystems, sockets, clocks, and random.
pub cli: Option<bool>,
Expand Down Expand Up @@ -390,6 +405,7 @@ wasmtime_option_group! {
/// systemd listen fd specification (UNIX only)
pub listenfd: Option<bool>,
/// Grant access to the given TCP listen socket
#[serde(default)]
pub tcplisten: Vec<String>,
/// Implement WASI Preview1 using new Preview2 implementation (true, default) or legacy
/// implementation (false)
Expand All @@ -402,6 +418,7 @@ wasmtime_option_group! {
/// an OpenVINO model named `bar`. Note that which model encodings are
/// available is dependent on the backends implemented in the
/// `wasmtime_wasi_nn` crate.
#[serde(skip)]
pub nn_graph: Vec<WasiNnGraph>,
/// Flag for WASI preview2 to inherit the host's network within the
/// guest so it has full access to all addresses/ports/etc.
Expand All @@ -421,8 +438,10 @@ wasmtime_option_group! {
/// This option can be further overwritten with `--env` flags.
pub inherit_env: Option<bool>,
/// Pass a wasi config variable to the program.
#[serde(skip)]
pub config_var: Vec<KeyValuePair>,
/// Preset data for the In-Memory provider of WASI key-value API.
#[serde(skip)]
pub keyvalue_in_memory_data: Vec<KeyValuePair>,
}

Expand All @@ -444,7 +463,8 @@ pub struct KeyValuePair {
}

/// Common options for commands that translate WebAssembly modules
#[derive(Parser, Clone)]
#[derive(Parser, Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CommonOptions {
// These options groups are used to parse `-O` and such options but aren't
// the raw form consumed by the CLI. Instead they're pushed into the `pub`
Expand All @@ -456,42 +476,59 @@ pub struct CommonOptions {
/// Optimization and tuning related options for wasm performance, `-O help` to
/// see all.
#[arg(short = 'O', long = "optimize", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
opts_raw: Vec<opt::CommaSeparated<Optimize>>,

/// Codegen-related configuration options, `-C help` to see all.
#[arg(short = 'C', long = "codegen", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
codegen_raw: Vec<opt::CommaSeparated<Codegen>>,

/// Debug-related configuration options, `-D help` to see all.
#[arg(short = 'D', long = "debug", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
debug_raw: Vec<opt::CommaSeparated<Debug>>,

/// Options for configuring semantic execution of WebAssembly, `-W help` to see
/// all.
#[arg(short = 'W', long = "wasm", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
wasm_raw: Vec<opt::CommaSeparated<Wasm>>,

/// Options for configuring WASI and its proposals, `-S help` to see all.
#[arg(short = 'S', long = "wasi", value_name = "KEY[=VAL[,..]]")]
#[serde(skip)]
wasi_raw: Vec<opt::CommaSeparated<Wasi>>,

// These fields are filled in by the `configure` method below via the
// options parsed from the CLI above. This is what the CLI should use.
#[arg(skip)]
#[serde(skip)]
configured: bool,

#[arg(skip)]
#[serde(rename = "optimize", default)]
pub opts: OptimizeOptions,

#[arg(skip)]
#[serde(rename = "codegen", default)]
pub codegen: CodegenOptions,

#[arg(skip)]
#[serde(rename = "debug", default)]
pub debug: DebugOptions,

#[arg(skip)]
#[serde(rename = "wasm", default)]
pub wasm: WasmOptions,

#[arg(skip)]
#[serde(rename = "wasi", default)]
pub wasi: WasiOptions,

/// The target triple; default is the host triple
#[arg(long, value_name = "TARGET")]
#[serde(skip)]
pub target: Option<String>,
}

Expand Down Expand Up @@ -924,3 +961,144 @@ impl CommonOptions {
Ok(())
}
}

pub trait FromFile {
fn from_file<P: AsRef<Path>>(path: P) -> Result<Self>
where
Self: Sized;
}

impl FromFile for Config {
fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let file_contents = fs::read_to_string(path.as_ref())?;
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved
let mut common_options: CommonOptions = toml::from_str(&file_contents)?;
common_options.config(None)
}
}
alexcrichton marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(test)]
mod tests {
use wasmtime::{OptLevel, RegallocAlgorithm};

use super::*;

#[test]
fn from_toml() {
// empty toml
let empty_toml = "";
let mut common_options: CommonOptions = toml::from_str(empty_toml).unwrap();
common_options.config(None).unwrap();

// basic toml
let basic_toml = r#"
[optimize]
[codegen]
[debug]
[wasm]
[wasi]
"#;
let mut common_options: CommonOptions = toml::from_str(basic_toml).unwrap();
common_options.config(None).unwrap();

// toml with custom deserialization to match CLI flag parsing
for (opt_value, expected) in [
("0", Some(OptLevel::None)),
("1", Some(OptLevel::Speed)),
("2", Some(OptLevel::Speed)),
("\"s\"", Some(OptLevel::SpeedAndSize)),
("\"hello\"", None), // should fail
("3", None), // should fail
] {
let toml = format!(
r#"
[optimize]
opt_level = {opt_value}
"#,
);
let parsed_opt_level = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.opts.opt_level);

assert_eq!(
parsed_opt_level, expected,
"Mismatch for input '{}'. Parsed: {:?}, Expected: {:?}",
opt_value, parsed_opt_level, expected
);
}

// Regalloc algorithm
for (regalloc_value, expected) in [
("\"backtracking\"", Some(RegallocAlgorithm::Backtracking)),
("\"single-pass\"", Some(RegallocAlgorithm::SinglePass)),
("\"hello\"", None), // should fail
("3", None), // should fail
("true", None), // should fail
] {
let toml = format!(
r#"
[optimize]
regalloc_algorithm = {regalloc_value}
"#,
);
let parsed_regalloc_algorithm = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.opts.regalloc_algorithm);
assert_eq!(
parsed_regalloc_algorithm, expected,
"Mismatch for input '{}'. Parsed: {:?}, Expected: {:?}",
regalloc_value, parsed_regalloc_algorithm, expected
);
}

// Strategy
for (strategy_value, expected) in [
("\"cranelift\"", Some(wasmtime::Strategy::Cranelift)),
("\"winch\"", Some(wasmtime::Strategy::Winch)),
("\"hello\"", None), // should fail
("5", None), // should fail
("true", None), // should fail
] {
let toml = format!(
r#"
[codegen]
compiler = {strategy_value}
"#,
);
let parsed_strategy = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.codegen.compiler);
assert_eq!(
parsed_strategy, expected,
"Mismatch for input '{}'. Parsed: {:?}, Expected: {:?}",
strategy_value, parsed_strategy, expected
);
}

// Collector
for (collector_value, expected) in [
(
"\"drc\"",
Some(wasmtime::Collector::DeferredReferenceCounting),
),
("\"null\"", Some(wasmtime::Collector::Null)),
("\"hello\"", None), // should fail
("5", None), // should fail
("true", None), // should fail
] {
let toml = format!(
r#"
[codegen]
collector = {collector_value}
"#,
);
let parsed_collector = toml::from_str::<CommonOptions>(&toml)
.ok()
.and_then(|common_options| common_options.codegen.collector);
assert_eq!(
parsed_collector, expected,
"Mismatch for input '{}'. Parsed: {:?}, Expected: {:?}",
collector_value, parsed_collector, expected
);
}
}
}
Loading
Loading