Skip to content

Commit

Permalink
Create wasmtime::Config from toml
Browse files Browse the repository at this point in the history
  • Loading branch information
ludfjig committed Dec 13, 2024
1 parent ef9c954 commit 5427eeb
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 10 deletions.
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())?;
let mut common_options: CommonOptions = toml::from_str(&file_contents)?;
common_options.config(None)
}
}

#[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

0 comments on commit 5427eeb

Please sign in to comment.