diff --git a/Cargo.toml b/Cargo.toml index f290f501facf..7dccf676fa3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -366,10 +366,6 @@ default = [ # cost, so allow disabling this through disabling of our own `default` # feature. "clap/default", - - # By default include compatibility with the "old" CLI from Wasmtime 13 and - # prior. - "old-cli", ] # ======================================== @@ -411,9 +407,6 @@ debug-builtins = ["wasmtime/debug-builtins"] threads = ["wasmtime-cli-flags/threads"] gc = ["wasmtime-cli-flags/gc"] -# Enables compatibility shims with Wasmtime 13 and prior's CLI. -old-cli = [] - # CLI subcommands for the `wasmtime` executable. See `wasmtime $cmd --help` # for more information on each subcommand. serve = ["wasi-http", "component-model", "dep:http-body-util", "dep:http"] diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 27aaf9f59634..38202fd8fef8 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -116,10 +116,6 @@ impl Wasmtime { } fn main() -> Result<()> { - #[cfg(feature = "old-cli")] - return old_cli::main(); - - #[cfg(not(feature = "old-cli"))] return Wasmtime::parse().execute(); } @@ -128,186 +124,3 @@ fn verify_cli() { use clap::CommandFactory; Wasmtime::command().debug_assert() } - -#[cfg(feature = "old-cli")] -mod old_cli { - use crate::Wasmtime; - use anyhow::{bail, Result}; - use clap::error::ErrorKind; - use clap::Parser; - use wasmtime_cli::old_cli as old; - - enum WhichCli { - Old, - New, - Unspecified, - } - - const DEFAULT_OLD_BEHAVIOR: bool = true; - - fn which_cli() -> Result { - Ok(match std::env::var("WASMTIME_NEW_CLI") { - Ok(s) if s == "0" => WhichCli::Old, - Ok(s) if s == "1" => WhichCli::New, - Ok(_) => bail!("the `WASMTIME_NEW_CLI` should either be `0` or `1`"), - Err(_) => WhichCli::Unspecified, - }) - } - - pub fn main() -> Result<()> { - match which_cli()? { - // If the old or the new CLI is explicitly selected, then run that - // variant no questions asked. - WhichCli::New => return Wasmtime::parse().execute(), - WhichCli::Old => return try_parse_old().unwrap_or_else(|e| e.exit()).execute(), - - // fall through below to run an unspecified version of the CLI - WhichCli::Unspecified => {} - } - - // Here it's not specified which version of the CLI should be used, so - // both the old and the new CLI parsers are used and depending on the - // results an execution happens. - let new = Wasmtime::try_parse(); - let old = try_parse_old(); - match (new, old) { - // Both parsers succeeded. This means that it's likely that no - // options to configure execution, e.g. `--disable-logging`, were - // used in the old parser or the new. - // - // The result of parsing can still differ though. For example: - // - // wasmtime foo.wasm --invoke foo - // - // would previously be parsed as passing `--invoke` to Wasmtime but - // the new parser instead parses that as passing the flag to - // `foo.wasm`. - // - // In this situation use the `DEFAULT_OLD_BEHAVIOR` constant to - // dictate which wins and additionally print a warning message. - (Ok(new), Ok(old)) => { - if new == old { - return new.execute(); - } - if DEFAULT_OLD_BEHAVIOR { - eprintln!( - "\ -warning: this CLI invocation of Wasmtime will be parsed differently in future - Wasmtime versions -- see this online issue for more information: - https://github.com/bytecodealliance/wasmtime/issues/7384 - - Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, - however this behavior can also be temporarily configured with an - environment variable: - - - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or - - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and use the latest behavior\ -" - ); - old.execute() - } else { - // this error message is not statically reachable due to - // `DEFAULT_OLD_BEHAVIOR=true` at this time, but when that - // changes this should be updated to have a more accurate - // set of text. - assert!(false); - eprintln!( - "\ -warning: this CLI invocation of Wasmtime is parsed differently than it was - previously -- see this online issue for more information: - https://github.com/bytecodealliance/wasmtime/issues/7384 - - Wasmtime will now execute with the new (>= Wasmtime XXX) CLI parsing, - however this behavior can also be temporarily configured with an - environment variable: - - - WASMTIME_NEW_CLI=0 to indicate old semantics are desired instead of the new, or - - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and silences this warning\ -" - ); - new.execute() - } - } - - // Here the new parser succeeded where the old one failed. This - // could indicate for example that new options are being passed on - // the CLI. - // - // In this situation assume that the new parser is what's intended - // so execute those semantics. - (Ok(new), Err(_old)) => new.execute(), - - // Here the new parser failed and the old parser succeeded. This - // could indicate for example passing old CLI flags. - // - // Here assume that the old semantics are desired but emit a warning - // indicating that this will change in the future. - (Err(_new), Ok(old)) => { - eprintln!( - "\ -warning: this CLI invocation of Wasmtime is going to break in the future -- for - more information see this issue online: - https://github.com/bytecodealliance/wasmtime/issues/7384 - - Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, - however this behavior can also be temporarily configured with an - environment variable: - - - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or - - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and see the error\ -" - ); - old.execute() - } - - // Both parsers failed to parse the CLI invocation. - // - // This could mean that someone manually passed an old flag - // incorrectly. This could also mean that a new flag was passed - // incorrectly. Clap also models `--help` requests as errors here so - // this could also mean that a `--help` flag was passed. - // - // The current assumption in any case is that there's a human - // interacting with the CLI at this point. They may or may not be - // aware of the old CLI vs new CLI but if we're going to print an - // error message then now's probably as good a time as any to nudge - // them towards the new CLI. Any preexisting scripts which parsed - // the old CLI should not hit this case which means that all old - // successful parses will not go through here. - // - // In any case, display the error for the new CLI, including new - // help text. - (Err(new), Err(_old)) => new.exit(), - } - } - - fn try_parse_old() -> clap::error::Result { - match old::Wasmtime::try_parse() { - Ok(old) => Ok(convert(old)), - Err(e) => { - if let ErrorKind::InvalidSubcommand | ErrorKind::UnknownArgument = e.kind() { - if let Ok(run) = old::RunCommand::try_parse() { - return Ok(Wasmtime { - subcommand: None, - run: run.convert(), - }); - } - } - Err(e) - } - } - } - - fn convert(old: old::Wasmtime) -> Wasmtime { - let subcommand = match old { - old::Wasmtime::Compile(c) => crate::Subcommand::Compile(c.convert()), - old::Wasmtime::Run(c) => crate::Subcommand::Run(c.convert()), - }; - let mut run = wasmtime_cli::commands::RunCommand::parse_from::<_, &str>(["x", "y"]); - run.module_and_args = Vec::new(); - Wasmtime { - subcommand: Some(subcommand), - run, - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 731c7f81c2a5..296c75d85853 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,3 @@ pub mod commands; #[cfg(feature = "run")] pub(crate) mod common; - -#[cfg(feature = "old-cli")] -pub mod old_cli; diff --git a/src/old_cli.rs b/src/old_cli.rs deleted file mode 100644 index 8445cad984d7..000000000000 --- a/src/old_cli.rs +++ /dev/null @@ -1,997 +0,0 @@ -#![allow(missing_docs)] - -use anyhow::{bail, Result}; -use clap::builder::{OsStringValueParser, TypedValueParser}; -use clap::Parser; -use std::collections::HashMap; -use std::ffi::OsString; -use std::path::PathBuf; -use std::time::Duration; - -/// Wasmtime WebAssembly Runtime -#[derive(Parser)] -#[command( - version, - after_help = "If a subcommand is not provided, the `run` subcommand will be used.\n\ - \n\ - Usage examples:\n\ - \n\ - Running a WebAssembly module with a start function:\n\ - \n \ - wasmtime example.wasm - \n\ - Passing command line arguments to a WebAssembly module:\n\ - \n \ - wasmtime example.wasm arg1 arg2 arg3\n\ - \n\ - Invoking a specific function (e.g. `add`) in a WebAssembly module:\n\ - \n \ - wasmtime example.wasm --invoke add 1 2\n" -)] -pub enum Wasmtime { - /// Compiles a WebAssembly module. - Compile(CompileCommand), - /// Runs a WebAssembly module - Run(RunCommand), -} - -#[test] -fn verify_cli() { - use clap::CommandFactory; - Wasmtime::command().debug_assert() -} - -#[derive(Parser)] -pub enum Subcommand { - /// Compiles a WebAssembly module. - Compile(CompileCommand), - /// Runs a WebAssembly module - Run(RunCommand), -} - -#[derive(Parser)] -#[command(trailing_var_arg = true)] -pub struct RunCommand { - #[command(flatten)] - common: CommonOptions, - - /// Allow unknown exports when running commands. - #[arg(long = "allow-unknown-exports")] - allow_unknown_exports: bool, - - /// Allow the main module to import unknown functions, using an - /// implementation that immediately traps, when running commands. - #[arg(long = "trap-unknown-imports")] - trap_unknown_imports: bool, - - /// Allow the main module to import unknown functions, using an - /// implementation that returns default values, when running commands. - #[arg(long = "default-values-unknown-imports")] - default_values_unknown_imports: bool, - - /// Allow executing precompiled WebAssembly modules as `*.cwasm` files. - /// - /// Note that this option is not safe to pass if the module being passed in - /// is arbitrary user input. Only `wasmtime`-precompiled modules generated - /// via the `wasmtime compile` command or equivalent should be passed as an - /// argument with this option specified. - #[arg(long = "allow-precompiled")] - allow_precompiled: bool, - - /// Inherit environment variables and file descriptors following the - /// systemd listen fd specification (UNIX only) - #[arg(long = "listenfd")] - listenfd: bool, - - /// Grant access to the given TCP listen socket - #[arg( - long = "tcplisten", - number_of_values = 1, - value_name = "SOCKET ADDRESS" - )] - tcplisten: Vec, - - /// Grant access to the given host directory - #[arg(long = "dir", number_of_values = 1, value_name = "DIRECTORY")] - dirs: Vec, - - /// Pass an environment variable to the program. - /// - /// The `--env FOO=BAR` form will set the environment variable named `FOO` - /// to the value `BAR` for the guest program using WASI. The `--env FOO` - /// form will set the environment variable named `FOO` to the same value it - /// has in the calling process for the guest, or in other words it will - /// cause the environment variable `FOO` to be inherited. - #[arg(long = "env", number_of_values = 1, value_name = "NAME[=VAL]", value_parser = parse_env_var)] - vars: Vec<(String, Option)>, - - /// The name of the function to run - #[arg(long, value_name = "FUNCTION")] - invoke: Option, - - /// Grant access to a guest directory mapped as a host directory - #[arg(long = "mapdir", number_of_values = 1, value_name = "GUEST_DIR::HOST_DIR", value_parser = parse_map_dirs)] - map_dirs: Vec<(String, String)>, - - /// Pre-load machine learning graphs (i.e., models) for use by wasi-nn. - /// - /// Each use of the flag will preload a ML model from the host directory - /// using the given model encoding. The model will be mapped to the - /// directory name: e.g., `--wasi-nn-graph openvino:/foo/bar` will preload - /// an OpenVINO model named `bar`. Note that which model encodings are - /// available is dependent on the backends implemented in the - /// `wasmtime_wasi_nn` crate. - #[arg(long = "wasi-nn-graph", value_name = "FORMAT::HOST_DIR", value_parser = parse_graphs)] - graphs: Vec<(String, String)>, - - /// The path of the WebAssembly module to run - #[arg( - required = true, - value_name = "MODULE", - value_parser = OsStringValueParser::new().try_map(parse_module), - )] - module: PathBuf, - - /// Load the given WebAssembly module before the main module - #[arg( - long = "preload", - number_of_values = 1, - value_name = "NAME=MODULE_PATH", - value_parser = parse_preloads, - )] - preloads: Vec<(String, PathBuf)>, - - /// Maximum execution time of wasm code before timing out (1, 2s, 100ms, etc) - #[arg( - long = "wasm-timeout", - value_name = "TIME", - value_parser = parse_dur, - )] - wasm_timeout: Option, - - /// Profiling strategy (valid options are: perfmap, jitdump, vtune, guest) - /// - /// The perfmap, jitdump, and vtune profiling strategies integrate Wasmtime - /// with external profilers such as `perf`. The guest profiling strategy - /// enables in-process sampling and will write the captured profile to - /// `wasmtime-guest-profile.json` by default which can be viewed at - /// . - /// - /// The `guest` option can be additionally configured as: - /// - /// --profile=guest[,path[,interval]] - /// - /// where `path` is where to write the profile and `interval` is the - /// duration between samples. When used with `--wasm-timeout` the timeout - /// will be rounded up to the nearest multiple of this interval. - #[arg( - long, - value_name = "STRATEGY", - value_parser = parse_profile, - )] - profile: Option, - - /// Enable coredump generation after a WebAssembly trap. - #[arg(long = "coredump-on-trap", value_name = "PATH")] - coredump_on_trap: Option, - - // NOTE: this must come last for trailing varargs - /// The arguments to pass to the module - #[arg(value_name = "ARGS")] - module_args: Vec, - - /// Maximum size, in bytes, that a linear memory is allowed to reach. - /// - /// Growth beyond this limit will cause `memory.grow` instructions in - /// WebAssembly modules to return -1 and fail. - #[arg(long, value_name = "BYTES")] - max_memory_size: Option, - - /// Maximum size, in table elements, that a table is allowed to reach. - #[arg(long)] - max_table_elements: Option, - - /// Maximum number of WebAssembly instances allowed to be created. - #[arg(long)] - max_instances: Option, - - /// Maximum number of WebAssembly tables allowed to be created. - #[arg(long)] - max_tables: Option, - - /// Maximum number of WebAssembly linear memories allowed to be created. - #[arg(long)] - max_memories: Option, - - /// Force a trap to be raised on `memory.grow` and `table.grow` failure - /// instead of returning -1 from these instructions. - /// - /// This is not necessarily a spec-compliant option to enable but can be - /// useful for tracking down a backtrace of what is requesting so much - /// memory, for example. - #[arg(long)] - trap_on_grow_failure: bool, - - /// Indicates that the implementation of WASI preview1 should be backed by - /// the preview2 implementation for components. - /// - /// This will become the default in the future and this option will be - /// removed. For now this is primarily here for testing. - #[arg(long)] - preview2: bool, - - /// Enables memory error checking. - /// - /// See wmemcheck.md for documentation on how to use. - #[arg(long)] - wmemcheck: bool, - - /// Flag for WASI preview2 to inherit the host's network within the guest so - /// it has full access to all addresses/ports/etc. - #[arg(long)] - inherit_network: bool, -} -fn parse_module(s: OsString) -> anyhow::Result { - // Do not accept wasmtime subcommand names as the module name - match s.to_str() { - Some("help") | Some("run") | Some("compile") | Some("serve") | Some("explore") - | Some("settings") | Some("wast") | Some("config") => { - bail!("module name cannot be the same as a subcommand") - } - _ => Ok(s.into()), - } -} - -#[derive(Clone)] -pub enum Profile { - Native(wasmtime::ProfilingStrategy), - Guest { path: String, interval: Duration }, -} - -fn parse_env_var(s: &str) -> Result<(String, Option)> { - let mut parts = s.splitn(2, '='); - Ok(( - parts.next().unwrap().to_string(), - parts.next().map(|s| s.to_string()), - )) -} - -fn parse_map_dirs(s: &str) -> Result<(String, String)> { - let parts: Vec<&str> = s.split("::").collect(); - if parts.len() != 2 { - bail!("must contain exactly one double colon ('::')"); - } - Ok((parts[0].into(), parts[1].into())) -} - -fn parse_graphs(s: &str) -> Result<(String, String)> { - let parts: Vec<&str> = s.split("::").collect(); - if parts.len() != 2 { - bail!("must contain exactly one double colon ('::')"); - } - Ok((parts[0].into(), parts[1].into())) -} - -fn parse_dur(s: &str) -> Result { - // assume an integer without a unit specified is a number of seconds ... - if let Ok(val) = s.parse() { - return Ok(Duration::from_secs(val)); - } - // ... otherwise try to parse it with units such as `3s` or `300ms` - let dur = humantime::parse_duration(s)?; - Ok(dur) -} - -fn parse_preloads(s: &str) -> Result<(String, PathBuf)> { - let parts: Vec<&str> = s.splitn(2, '=').collect(); - if parts.len() != 2 { - bail!("must contain exactly one equals character ('=')"); - } - Ok((parts[0].into(), parts[1].into())) -} - -fn parse_profile(s: &str) -> Result { - let parts = s.split(',').collect::>(); - match &parts[..] { - ["perfmap"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::PerfMap)), - ["jitdump"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::JitDump)), - ["vtune"] => Ok(Profile::Native(wasmtime::ProfilingStrategy::VTune)), - ["guest"] => Ok(Profile::Guest { - path: "wasmtime-guest-profile.json".to_string(), - interval: Duration::from_millis(10), - }), - ["guest", path] => Ok(Profile::Guest { - path: path.to_string(), - interval: Duration::from_millis(10), - }), - ["guest", path, dur] => Ok(Profile::Guest { - path: path.to_string(), - interval: parse_dur(dur)?, - }), - _ => bail!("unknown profiling strategy: {s}"), - } -} - -/// Compiles a WebAssembly module. -#[derive(Parser)] -pub struct CompileCommand { - #[command(flatten)] - pub common: CommonOptions, - - /// The target triple; default is the host triple - #[arg(long, value_name = "TARGET")] - pub target: Option, - - /// The path of the output compiled module; defaults to `.cwasm` - #[arg(short = 'o', long, value_name = "OUTPUT")] - pub output: Option, - - /// The directory path to write clif files into, one clif file per wasm function. - #[arg(long = "emit-clif", value_name = "PATH")] - pub emit_clif: Option, - - /// The path of the WebAssembly to compile - #[arg(index = 1, value_name = "MODULE")] - pub module: PathBuf, -} - -/// Common options for commands that translate WebAssembly modules -#[derive(Parser)] -#[cfg_attr(test, derive(Debug, PartialEq))] -pub struct CommonOptions { - /// Use specified configuration file - #[arg(long, value_name = "CONFIG_PATH")] - pub config: Option, - - /// Disable logging - #[arg(long, conflicts_with = "log_to_files")] - pub disable_logging: bool, - - /// Log to per-thread log files instead of stderr - #[arg(long)] - pub log_to_files: bool, - - /// Generate debug information - #[arg(short = 'g')] - pub debug_info: bool, - - /// Disable cache system - #[arg(long)] - pub disable_cache: bool, - - /// Disable parallel compilation - #[arg(long)] - pub disable_parallel_compilation: bool, - - /// Enable or disable WebAssembly features - #[arg(long, value_name = "FEATURE,FEATURE,...", value_parser = parse_wasm_features)] - pub wasm_features: Option, - - /// Enable or disable WASI modules - #[arg(long, value_name = "MODULE,MODULE,...", value_parser = parse_wasi_modules)] - pub wasi_modules: Option, - - /// Generate jitdump file (supported on --features=profiling build) - /// Run optimization passes on translated functions, on by default - #[arg(short = 'O', long)] - pub optimize: bool, - - /// Optimization level for generated functions - /// Supported levels: 0 (none), 1, 2 (most), or s (size); default is "most" - #[arg( - long, - value_name = "LEVEL", - value_parser = parse_opt_level, - verbatim_doc_comment, - )] - pub opt_level: Option, - - /// Set a Cranelift setting to a given value. - /// Use `wasmtime settings` to list Cranelift settings for a target. - #[arg( - long = "cranelift-set", - value_name = "NAME=VALUE", - number_of_values = 1, - verbatim_doc_comment, - value_parser = parse_cranelift_flag, - )] - pub cranelift_set: Vec<(String, String)>, - - /// Enable a Cranelift boolean setting or preset. - /// Use `wasmtime settings` to list Cranelift settings for a target. - #[arg( - long, - value_name = "SETTING", - number_of_values = 1, - verbatim_doc_comment - )] - pub cranelift_enable: Vec, - - /// Maximum size in bytes of wasm memory before it becomes dynamically - /// relocatable instead of up-front-reserved. - #[arg(long, value_name = "MAXIMUM")] - pub static_memory_maximum_size: Option, - - /// Force using a "static" style for all wasm memories - #[arg(long)] - pub static_memory_forced: bool, - - /// Byte size of the guard region after static memories are allocated - #[arg(long, value_name = "SIZE")] - pub static_memory_guard_size: Option, - - /// Byte size of the guard region after dynamic memories are allocated - #[arg(long, value_name = "SIZE")] - pub dynamic_memory_guard_size: Option, - - /// Bytes to reserve at the end of linear memory for growth for dynamic - /// memories. - #[arg(long, value_name = "SIZE")] - pub dynamic_memory_reserved_for_growth: Option, - - /// Enable Cranelift's internal debug verifier (expensive) - #[arg(long)] - pub enable_cranelift_debug_verifier: bool, - - /// Enable Cranelift's internal NaN canonicalization - #[arg(long)] - pub enable_cranelift_nan_canonicalization: bool, - - /// Enable execution fuel with N units fuel, where execution will trap after - /// running out of fuel. - /// - /// Most WebAssembly instructions consume 1 unit of fuel. Some instructions, - /// such as `nop`, `drop`, `block`, and `loop`, consume 0 units, as any - /// execution cost associated with them involves other instructions which do - /// consume fuel. - #[arg(long, value_name = "N")] - pub fuel: Option, - - /// Executing wasm code will yield when a global epoch counter - /// changes, allowing for async operation without blocking the - /// executor. - #[arg(long)] - pub epoch_interruption: bool, - - /// Disable the on-by-default address map from native code to wasm code - #[arg(long)] - pub disable_address_map: bool, - - /// Disable the default of attempting to initialize linear memory via a - /// copy-on-write mapping - #[arg(long)] - pub disable_memory_init_cow: bool, - - /// Enable the pooling allocator, in place of the on-demand - /// allocator. - #[cfg(feature = "pooling-allocator")] - #[arg(long)] - pub pooling_allocator: bool, - - /// Maximum stack size, in bytes, that wasm is allowed to consume before a - /// stack overflow is reported. - #[arg(long)] - pub max_wasm_stack: Option, - - /// Whether or not to force deterministic and host-independent behavior of - /// the relaxed-simd instructions. - /// - /// By default these instructions may have architecture-specific behavior as - /// allowed by the specification, but this can be used to force the behavior - /// of these instructions to match the deterministic behavior classified in - /// the specification. Note that enabling this option may come at a - /// performance cost. - #[arg(long)] - pub relaxed_simd_deterministic: bool, - - /// Explicitly specify the name of the compiler to use for WebAssembly. - /// - /// Currently only `cranelift` and `winch` are supported, but not all builds - /// of Wasmtime have both built in. - #[arg(long)] - pub compiler: Option, -} - -fn parse_opt_level(opt_level: &str) -> Result { - match opt_level { - "s" => Ok(wasmtime::OptLevel::SpeedAndSize), - "0" => Ok(wasmtime::OptLevel::None), - "1" => Ok(wasmtime::OptLevel::Speed), - "2" => Ok(wasmtime::OptLevel::Speed), - other => bail!( - "unknown optimization level `{}`, only 0,1,2,s accepted", - other - ), - } -} - -#[derive(Default, Clone, Copy)] -#[cfg_attr(test, derive(Debug, PartialEq))] -pub struct WasmFeatures { - pub reference_types: Option, - pub multi_value: Option, - pub bulk_memory: Option, - pub simd: Option, - pub relaxed_simd: Option, - pub tail_call: Option, - pub threads: Option, - pub multi_memory: Option, - pub memory64: Option, - pub component_model: Option, - pub function_references: Option, -} - -const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ - ("all", "enables all supported WebAssembly features"), - ( - "bulk-memory", - "enables support for bulk memory instructions", - ), - ( - "multi-memory", - "enables support for the multi-memory proposal", - ), - ("multi-value", "enables support for multi-value functions"), - ("reference-types", "enables support for reference types"), - ("simd", "enables support for proposed SIMD instructions"), - ( - "relaxed-simd", - "enables support for the relaxed simd proposal", - ), - ("tail-call", "enables support for WebAssembly tail calls"), - ("threads", "enables support for WebAssembly threads"), - ("memory64", "enables support for 64-bit memories"), - ("component-model", "enables support for the component model"), - ( - "function-references", - "enables support for typed function references", - ), -]; - -fn parse_wasm_features(features: &str) -> Result { - let features = features.trim(); - - let mut all = None; - let mut values: HashMap<_, _> = SUPPORTED_WASM_FEATURES - .iter() - .map(|(name, _)| (name.to_string(), None)) - .collect(); - - if features == "all" { - all = Some(true); - } else if features == "-all" { - all = Some(false); - } else { - for feature in features.split(',') { - let feature = feature.trim(); - - if feature.is_empty() { - continue; - } - - let (feature, value) = if feature.starts_with('-') { - (&feature[1..], false) - } else { - (feature, true) - }; - - if feature == "all" { - bail!("'all' cannot be specified with other WebAssembly features"); - } - - match values.get_mut(feature) { - Some(v) => *v = Some(value), - None => bail!("unsupported WebAssembly feature '{}'", feature), - } - } - } - - Ok(WasmFeatures { - reference_types: all.or(values["reference-types"]), - multi_value: all.or(values["multi-value"]), - bulk_memory: all.or(values["bulk-memory"]), - simd: all.or(values["simd"]), - relaxed_simd: all.or(values["relaxed-simd"]), - tail_call: all.or(values["tail-call"]), - threads: all.or(values["threads"]), - multi_memory: all.or(values["multi-memory"]), - memory64: all.or(values["memory64"]), - #[cfg(feature = "component-model")] - component_model: all.or(values["component-model"]), - function_references: all.or(values["function-references"]), - }) -} - -/// Select which WASI modules are available at runtime for use by Wasm programs. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct WasiModules { - /// Enable the wasi-common implementation; eventually this should be split into its separate - /// parts once the implementation allows for it (e.g. wasi-fs, wasi-clocks, etc.). - pub wasi_common: Option, - - /// Enable the experimental wasi-nn implementation. - pub wasi_nn: Option, - - /// Enable the experimental wasi-threads implementation. - pub wasi_threads: Option, - - /// Enable the experimental wasi-http implementation - pub wasi_http: Option, -} - -fn parse_wasi_modules(modules: &str) -> Result { - let modules = modules.trim(); - match modules { - "default" => Ok(WasiModules::default()), - "-default" => Ok(WasiModules::none()), - _ => { - // Starting from the default set of WASI modules, enable or disable a list of - // comma-separated modules. - let mut wasi_modules = WasiModules::default(); - let mut set = |module: &str, enable: bool| match module { - "" => Ok(()), - "wasi-common" => Ok(wasi_modules.wasi_common = Some(enable)), - "experimental-wasi-nn" => Ok(wasi_modules.wasi_nn = Some(enable)), - "experimental-wasi-threads" => Ok(wasi_modules.wasi_threads = Some(enable)), - "experimental-wasi-http" => Ok(wasi_modules.wasi_http = Some(enable)), - "default" => bail!("'default' cannot be specified with other WASI modules"), - _ => bail!("unsupported WASI module '{}'", module), - }; - - for module in modules.split(',') { - let module = module.trim(); - let (module, value) = if module.starts_with('-') { - (&module[1..], false) - } else { - (module, true) - }; - set(module, value)?; - } - - Ok(wasi_modules) - } - } -} - -impl Default for WasiModules { - fn default() -> Self { - Self { - wasi_common: None, - wasi_nn: None, - wasi_threads: None, - wasi_http: None, - } - } -} - -impl WasiModules { - /// Enable no modules. - fn none() -> Self { - Self { - wasi_common: Some(false), - wasi_nn: Some(false), - wasi_threads: Some(false), - wasi_http: Some(false), - } - } -} - -fn parse_cranelift_flag(name_and_value: &str) -> Result<(String, String)> { - let mut split = name_and_value.splitn(2, '='); - let name = if let Some(name) = split.next() { - name.to_string() - } else { - bail!("missing name in cranelift flag"); - }; - let value = if let Some(value) = split.next() { - value.to_string() - } else { - bail!("missing value in cranelift flag"); - }; - Ok((name, value)) -} - -impl CompileCommand { - pub fn convert(self) -> crate::commands::CompileCommand { - let CompileCommand { - common, - target, - output, - emit_clif, - module, - } = self; - - crate::commands::CompileCommand { - common: common.convert(), - target, - output, - emit_clif, - module, - } - } -} - -impl RunCommand { - pub fn convert(self) -> crate::commands::RunCommand { - let RunCommand { - common, - allow_unknown_exports, - trap_unknown_imports, - default_values_unknown_imports, - allow_precompiled, - listenfd, - tcplisten, - dirs: old_dirs, - vars, - invoke, - map_dirs, - graphs, - preloads, - wasm_timeout, - profile, - coredump_on_trap, - max_memory_size, - max_table_elements, - max_instances, - max_tables, - max_memories, - trap_on_grow_failure, - wmemcheck, - module, - module_args, - preview2, - inherit_network, - } = self; - - let mut common = common.convert(); - - let mut dirs = Vec::new(); - - for host in old_dirs { - let mut parts = host.splitn(2, "::"); - let host = parts.next().unwrap(); - let guest = parts.next().unwrap_or(host); - dirs.push((host.to_string(), guest.to_string())); - } - - if preview2 { - common.wasi.preview2 = Some(true); - } - if wmemcheck { - common.wasm.wmemcheck = Some(true); - } - if trap_on_grow_failure { - common.wasm.trap_on_grow_failure = Some(true); - } - if let Some(max) = max_memories { - common.wasm.max_memories = Some(max); - } - if let Some(max) = max_tables { - common.wasm.max_tables = Some(max); - } - if let Some(max) = max_instances { - common.wasm.max_instances = Some(max); - } - if let Some(max) = max_memory_size { - common.wasm.max_memory_size = Some(max); - } - if let Some(max) = max_table_elements { - common.wasm.max_table_elements = Some(max); - } - if let Some(path) = coredump_on_trap { - common.debug.coredump = Some(path); - } - if let Some(timeout) = wasm_timeout { - common.wasm.timeout = Some(timeout); - } - if trap_unknown_imports { - common.wasm.unknown_imports_trap = Some(true); - } - if default_values_unknown_imports { - common.wasm.unknown_imports_default = Some(true); - } - common.wasi.tcplisten = tcplisten; - if listenfd { - common.wasi.listenfd = Some(true); - } - if allow_unknown_exports { - common.wasm.unknown_exports_allow = Some(true); - } - if inherit_network { - common.wasi.inherit_network = Some(true); - } - - for (format, dir) in graphs { - common - .wasi - .nn_graph - .push(wasmtime_cli_flags::WasiNnGraph { format, dir }); - } - - for (guest, host) in map_dirs { - dirs.push((host, guest)); - } - - let run = crate::common::RunCommon { - common, - allow_precompiled, - profile: profile.map(|p| p.convert()), - dirs, - vars, - }; - - let mut module_and_args = vec![module.into()]; - module_and_args.extend(module_args.into_iter().map(|s| s.into())); - crate::commands::RunCommand { - run, - invoke, - preloads, - module_and_args, - } - } -} - -impl CommonOptions { - pub fn convert(self) -> wasmtime_cli_flags::CommonOptions { - let CommonOptions { - config, - disable_logging, - log_to_files, - debug_info, - disable_cache, - disable_parallel_compilation, - wasm_features, - wasi_modules, - optimize, - opt_level, - cranelift_set, - cranelift_enable, - static_memory_maximum_size, - static_memory_forced, - static_memory_guard_size, - dynamic_memory_guard_size, - dynamic_memory_reserved_for_growth, - enable_cranelift_debug_verifier, - enable_cranelift_nan_canonicalization, - fuel, - epoch_interruption, - disable_address_map, - disable_memory_init_cow, - pooling_allocator, - max_wasm_stack, - relaxed_simd_deterministic, - compiler, - } = self; - - let mut ret = wasmtime_cli_flags::CommonOptions::parse_from::<_, String>([]); - match compiler.as_deref() { - Some("cranelift") => ret.codegen.compiler = Some(wasmtime::Strategy::Cranelift), - Some("winch") => ret.codegen.compiler = Some(wasmtime::Strategy::Winch), - - // Plumbing an error up from this point is a bit onerous. Let's - // just hope that no one was using this from the old CLI and passing - // invalid values. - Some(_) => {} - - None => {} - } - if relaxed_simd_deterministic { - ret.wasm.relaxed_simd_deterministic = Some(true); - } - ret.wasm.max_wasm_stack = max_wasm_stack; - if pooling_allocator { - ret.opts.pooling_allocator = Some(true); - } - if disable_memory_init_cow { - ret.opts.memory_init_cow = Some(false); - } - if disable_address_map { - ret.debug.address_map = Some(false); - } - if epoch_interruption { - ret.wasm.epoch_interruption = Some(true); - } - if enable_cranelift_debug_verifier { - ret.codegen.cranelift_debug_verifier = Some(true); - } - if enable_cranelift_nan_canonicalization { - ret.wasm.nan_canonicalization = Some(true); - } - if let Some(fuel) = fuel { - ret.wasm.fuel = Some(fuel); - } - if let Some(amt) = dynamic_memory_reserved_for_growth { - ret.opts.dynamic_memory_reserved_for_growth = Some(amt); - } - if let Some(amt) = dynamic_memory_guard_size { - ret.opts.dynamic_memory_guard_size = Some(amt); - } - if let Some(amt) = static_memory_guard_size { - ret.opts.static_memory_guard_size = Some(amt); - } - if static_memory_forced { - ret.opts.static_memory_forced = Some(true); - } - if let Some(amt) = static_memory_maximum_size { - ret.opts.static_memory_maximum_size = Some(amt); - } - if let Some(level) = opt_level { - ret.opts.opt_level = Some(level); - } - if disable_cache { - ret.codegen.cache = Some(false); - } - if debug_info { - ret.debug.debug_info = Some(true); - } - if optimize { - ret.opts.opt_level = Some(wasmtime::OptLevel::Speed); - } - if disable_parallel_compilation { - ret.codegen.parallel_compilation = Some(false); - } - if log_to_files { - ret.debug.log_to_files = Some(true); - } - if disable_logging { - ret.debug.logging = Some(false); - } - if let Some(path) = config { - ret.codegen.cache_config = Some(path.display().to_string()); - } - for (key, val) in cranelift_set { - ret.codegen.cranelift.push((key, Some(val))); - } - for key in cranelift_enable { - ret.codegen.cranelift.push((key, None)); - } - if let Some(features) = wasm_features { - let WasmFeatures { - reference_types, - multi_value, - bulk_memory, - simd, - relaxed_simd, - tail_call, - threads, - multi_memory, - memory64, - component_model, - function_references, - } = features; - ret.wasm.reference_types = reference_types; - ret.wasm.multi_value = multi_value; - ret.wasm.bulk_memory = bulk_memory; - ret.wasm.simd = simd; - ret.wasm.relaxed_simd = relaxed_simd; - ret.wasm.tail_call = tail_call; - ret.wasm.threads = threads; - ret.wasm.multi_memory = multi_memory; - ret.wasm.memory64 = memory64; - ret.wasm.component_model = component_model; - ret.wasm.function_references = function_references; - } - if let Some(modules) = wasi_modules { - let WasiModules { - wasi_common, - wasi_nn, - wasi_threads, - wasi_http, - } = modules; - ret.wasi.http = wasi_http; - ret.wasi.nn = wasi_nn; - ret.wasi.threads = wasi_threads; - ret.wasi.cli = wasi_common; - } - ret - } -} - -impl Profile { - pub fn convert(self) -> crate::common::Profile { - match self { - Profile::Native(s) => crate::common::Profile::Native(s), - Profile::Guest { path, interval } => crate::common::Profile::Guest { path, interval }, - } - } -} diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs index 7f76087d4397..9d3b610db297 100644 --- a/tests/all/cli_tests.rs +++ b/tests/all/cli_tests.rs @@ -1058,169 +1058,6 @@ fn preview2_stdin() -> Result<()> { Ok(()) } -#[test] -fn old_cli_warn_if_ambiguous_flags() -> Result<()> { - // This is accepted in the old CLI parser and the new but it's interpreted - // differently so a warning should be printed. - let output = get_wasmtime_command()? - .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) - .output()?; - assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); - assert_eq!( - String::from_utf8_lossy(&output.stderr), - "\ -warning: this CLI invocation of Wasmtime will be parsed differently in future - Wasmtime versions -- see this online issue for more information: - https://github.com/bytecodealliance/wasmtime/issues/7384 - - Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, - however this behavior can also be temporarily configured with an - environment variable: - - - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or - - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and use the latest behavior -warning: using `--invoke` with a function that returns values is experimental and may break in the future -" - ); - - // Test disabling the warning - let output = get_wasmtime_command()? - .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) - .env("WASMTIME_NEW_CLI", "0") - .output()?; - assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); - assert_eq!( - String::from_utf8_lossy(&output.stderr), - "\ -warning: using `--invoke` with a function that returns values is experimental and may break in the future -" - ); - - // Test forcing the new behavior where nothing happens because the file is - // invoked with `--invoke` as its own argument. - let output = get_wasmtime_command()? - .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) - .env("WASMTIME_NEW_CLI", "1") - .output()?; - assert_eq!(String::from_utf8_lossy(&output.stdout), ""); - assert_eq!(String::from_utf8_lossy(&output.stderr), ""); - - // This is unambiguous - let output = get_wasmtime_command()? - .args(&["--invoke", "get_f32", "tests/all/cli_tests/simple.wat"]) - .output()?; - assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); - assert_eq!( - String::from_utf8_lossy(&output.stderr), - "\ -warning: using `--invoke` with a function that returns values is experimental and may break in the future -" - ); - - // This fails to parse in the old but succeeds in the new, so it should run - // under the new semantics with no warning. - let output = get_wasmtime_command()? - .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"]) - .output()?; - assert_eq!( - String::from_utf8_lossy(&output.stdout), - "print-arguments.wat\n--arg\n" - ); - assert_eq!(String::from_utf8_lossy(&output.stderr), ""); - - // Old behavior can be forced however - let output = get_wasmtime_command()? - .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"]) - .env("WASMTIME_NEW_CLI", "0") - .output()?; - assert!(!output.status.success()); - - // This works in both the old and the new, so no warnings - let output = get_wasmtime_command()? - .args(&["run", "tests/all/cli_tests/print-arguments.wat", "arg"]) - .output()?; - assert_eq!( - String::from_utf8_lossy(&output.stdout), - "print-arguments.wat\narg\n" - ); - assert_eq!(String::from_utf8_lossy(&output.stderr), ""); - - // This works in both the old and the new, so no warnings - let output = get_wasmtime_command()? - .args(&[ - "run", - "--", - "tests/all/cli_tests/print-arguments.wat", - "--arg", - ]) - .output()?; - assert_eq!( - String::from_utf8_lossy(&output.stdout), - "print-arguments.wat\n--arg\n" - ); - assert_eq!(String::from_utf8_lossy(&output.stderr), ""); - - // Old flags still work, but with a warning - let output = get_wasmtime_command()? - .args(&[ - "run", - "--max-wasm-stack", - "1000000", - "tests/all/cli_tests/print-arguments.wat", - ]) - .output()?; - assert_eq!( - String::from_utf8_lossy(&output.stdout), - "print-arguments.wat\n" - ); - assert_eq!( - String::from_utf8_lossy(&output.stderr), - "\ -warning: this CLI invocation of Wasmtime is going to break in the future -- for - more information see this issue online: - https://github.com/bytecodealliance/wasmtime/issues/7384 - - Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, - however this behavior can also be temporarily configured with an - environment variable: - - - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or - - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and see the error -" - ); - - // Old flags warning is suppressible. - let output = get_wasmtime_command()? - .args(&[ - "run", - "--max-wasm-stack", - "1000000", - "tests/all/cli_tests/print-arguments.wat", - ]) - .env("WASMTIME_NEW_CLI", "0") - .output()?; - assert_eq!( - String::from_utf8_lossy(&output.stdout), - "print-arguments.wat\n" - ); - assert_eq!(String::from_utf8_lossy(&output.stderr), ""); - - // the `--dir` flag prints no warning when used with `::` - let dir = tempfile::tempdir()?; - std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; - let output = get_wasmtime_command()? - .args(&[ - "run", - &format!("--dir={}::/", dir.path().to_str().unwrap()), - test_programs_artifacts::CLI_FILE_READ, - ]) - .output()?; - assert_eq!(String::from_utf8_lossy(&output.stdout), ""); - assert_eq!(String::from_utf8_lossy(&output.stderr), ""); - - Ok(()) -} - #[test] fn float_args() -> Result<()> { let result = run_wasmtime(&[