Use structopt instead of docopt.
This commit refactors the Wasmtime CLI tools to use `structopt` instead of `docopt`. The `wasmtime` tool now has the following subcommands: * `config new` - creates a new Wasmtime configuration file. * `run` - runs a WebAssembly module. * `wasm2obj` - translates a Wasm module to native object file. * `wast` - runs a test script file. If no subcommand is specified, the `run` subcommand is used. Thus, `wasmtime foo.wasm` should continue to function as expected. The `wasm2obj` and `wast` tools still exist, but delegate to the same implementation as the `wasmtime` subcommands. The standalone `wasm2obj` and `wast` tools may be removed in the future in favor of simply using `wasmtime`. Included in this commit is a breaking change to the default Wasmtime configuration file: it has been renamed from `wasmtime-cache-config.toml` to simply `config.toml`. The new name is less specific which will allow for additional (non-cache-related) settings in the future. There are some breaking changes to improve command line UX: * The `--cache-config` option has been renamed to `--config`. * The `--create-config-file` option has moved to the `config new` subcommand. As a result, the `wasm2obj` and `wast` tools cannot be used to create a new config file. * The short form of the `--optimize` option has changed from `-o` to `-O` for consistency. * The `wasm2obj` command takes the output object file as a required positional argument rather than the former required output *option* (e.g. `wasmtime wasm2obj foo.wasm foo.obj`).
This commit is contained in:
53
Cargo.lock
generated
53
Cargo.lock
generated
@@ -316,7 +316,7 @@ dependencies = [
|
|||||||
"ansi_term",
|
"ansi_term",
|
||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"strsim 0.8.0",
|
"strsim",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"vec_map",
|
"vec_map",
|
||||||
@@ -550,18 +550,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "docopt"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"strsim 0.9.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dynasm"
|
name = "dynasm"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -1185,6 +1173,17 @@ dependencies = [
|
|||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-hack"
|
name = "proc-macro-hack"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
@@ -1622,10 +1621,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "structopt"
|
||||||
version = "0.9.2"
|
version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6"
|
checksum = "30b3a3e93f5ad553c38b3301c8a0a0cec829a36783f6a0c467fc4bf553a5f5bf"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"structopt-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "structopt-derive"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea692d40005b3ceba90a9fe7a78fa8d4b82b0ce627eebbffc329aab850f3410e"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
@@ -1950,12 +1966,10 @@ name = "wasmtime"
|
|||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"docopt",
|
|
||||||
"file-per-thread-logger",
|
"file-per-thread-logger",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rayon",
|
"rayon",
|
||||||
"region",
|
"region",
|
||||||
"serde",
|
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"wasi-common",
|
"wasi-common",
|
||||||
@@ -1973,14 +1987,13 @@ name = "wasmtime-cli"
|
|||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"docopt",
|
|
||||||
"faerie",
|
"faerie",
|
||||||
"file-per-thread-logger",
|
"file-per-thread-logger",
|
||||||
"libc",
|
"libc",
|
||||||
"more-asserts",
|
"more-asserts",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rayon",
|
"rayon",
|
||||||
"serde",
|
"structopt",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"test-programs",
|
"test-programs",
|
||||||
"wasi-common",
|
"wasi-common",
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ wasmtime-wast = { path = "crates/wast" }
|
|||||||
wasmtime-wasi = { path = "crates/wasi" }
|
wasmtime-wasi = { path = "crates/wasi" }
|
||||||
wasmtime-wasi-c = { path = "crates/wasi-c", optional = true }
|
wasmtime-wasi-c = { path = "crates/wasi-c", optional = true }
|
||||||
wasi-common = { path = "crates/wasi-common" }
|
wasi-common = { path = "crates/wasi-common" }
|
||||||
docopt = "1.0.1"
|
structopt = { version = "0.3.5", features = ["color", "suggestions"] }
|
||||||
serde = { "version" = "1.0.94", features = ["derive"] }
|
|
||||||
faerie = "0.13.0"
|
faerie = "0.13.0"
|
||||||
anyhow = "1.0.19"
|
anyhow = "1.0.19"
|
||||||
target-lexicon = { version = "0.9.0", default-features = false }
|
target-lexicon = { version = "0.9.0", default-features = false }
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ region = "2.0.0"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# for wasmtime.rs
|
# for wasmtime.rs
|
||||||
wasi-common = { path = "../wasi-common" }
|
wasi-common = { path = "../wasi-common" }
|
||||||
docopt = "1.0.1"
|
|
||||||
serde = { "version" = "1.0.94", features = ["derive"] }
|
|
||||||
pretty_env_logger = "0.3.0"
|
pretty_env_logger = "0.3.0"
|
||||||
wasmtime-wast = { path = "../wast" }
|
wasmtime-wast = { path = "../wast" }
|
||||||
wasmtime-wasi = { path = "../wasi" }
|
wasmtime-wasi = { path = "../wasi" }
|
||||||
|
|||||||
4
crates/environ/src/cache/config.rs
vendored
4
crates/environ/src/cache/config.rs
vendored
@@ -151,7 +151,7 @@ pub fn create_new_config<P: AsRef<Path> + Debug>(config_file: Option<P>) -> Resu
|
|||||||
|
|
||||||
if config_file.exists() {
|
if config_file.exists() {
|
||||||
bail!(
|
bail!(
|
||||||
"Specified config file already exists! Path: {}",
|
"Configuration file '{}' already exists.",
|
||||||
config_file.display()
|
config_file.display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -193,7 +193,7 @@ lazy_static! {
|
|||||||
ProjectDirs::from("", "BytecodeAlliance", "wasmtime");
|
ProjectDirs::from("", "BytecodeAlliance", "wasmtime");
|
||||||
static ref DEFAULT_CONFIG_PATH: Result<PathBuf, String> = PROJECT_DIRS
|
static ref DEFAULT_CONFIG_PATH: Result<PathBuf, String> = PROJECT_DIRS
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|proj_dirs| proj_dirs.config_dir().join("wasmtime-cache-config.toml"))
|
.map(|proj_dirs| proj_dirs.config_dir().join("config.toml"))
|
||||||
.ok_or_else(|| "Config file not specified and failed to get the default".to_string());
|
.ok_or_else(|| "Config file not specified and failed to get the default".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Cache Configuration of `wasmtime`
|
# Cache Configuration of `wasmtime`
|
||||||
|
|
||||||
The cache configuration file uses the [toml] format.
|
The configuration file uses the [toml] format.
|
||||||
You can create a configuration file at the default location with:
|
You can create a configuration file at the default location with:
|
||||||
```
|
```
|
||||||
$ wasmtime --create-cache-config
|
$ wasmtime config new
|
||||||
```
|
```
|
||||||
It will print the location regardless of the success.
|
It will print the location regardless of the success.
|
||||||
Please refer to the `--help` message for using a custom location.
|
Please refer to the `--help` message for using a custom location.
|
||||||
|
|||||||
@@ -1,291 +1,12 @@
|
|||||||
//! Translation from wasm to native object files.
|
//! The `wasm2obj` command line tool.
|
||||||
//!
|
//!
|
||||||
//! Reads a Wasm binary file, translates the functions' code to Cranelift
|
//! Translates WebAssembly modules to object files.
|
||||||
//! IL, then translates it to native code, and writes it out to a native
|
//! See `wasm2obj --help` for usage.
|
||||||
//! object file with relocations.
|
|
||||||
|
|
||||||
#![deny(
|
use anyhow::Result;
|
||||||
missing_docs,
|
use structopt::StructOpt;
|
||||||
trivial_numeric_casts,
|
use wasmtime_cli::commands::WasmToObjCommand;
|
||||||
unused_extern_crates,
|
|
||||||
unstable_features
|
|
||||||
)]
|
|
||||||
#![warn(unused_import_braces)]
|
|
||||||
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))]
|
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
|
||||||
#![cfg_attr(
|
|
||||||
feature = "cargo-clippy",
|
|
||||||
warn(
|
|
||||||
clippy::float_arithmetic,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::nonminimal_bool,
|
|
||||||
clippy::option_map_unwrap_or,
|
|
||||||
clippy::option_map_unwrap_or_else,
|
|
||||||
clippy::unicode_not_nfc,
|
|
||||||
clippy::use_self
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
fn main() -> Result<()> {
|
||||||
use docopt::Docopt;
|
WasmToObjCommand::from_args().execute()
|
||||||
use faerie::Artifact;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use std::{process, str};
|
|
||||||
use target_lexicon::Triple;
|
|
||||||
use wasmtime::Strategy;
|
|
||||||
use wasmtime_cli::pick_compilation_strategy;
|
|
||||||
use wasmtime_debug::{emit_debugsections, read_debuginfo};
|
|
||||||
use wasmtime_environ::entity::EntityRef;
|
|
||||||
use wasmtime_environ::settings;
|
|
||||||
use wasmtime_environ::settings::Configurable;
|
|
||||||
use wasmtime_environ::wasm::DefinedMemoryIndex;
|
|
||||||
#[cfg(feature = "lightbeam")]
|
|
||||||
use wasmtime_environ::Lightbeam;
|
|
||||||
use wasmtime_environ::{
|
|
||||||
cache_create_new_config, cache_init, Compiler, Cranelift, ModuleEnvironment, ModuleVmctxInfo,
|
|
||||||
Tunables, VMOffsets,
|
|
||||||
};
|
|
||||||
use wasmtime_jit::native;
|
|
||||||
use wasmtime_obj::emit_module;
|
|
||||||
|
|
||||||
const USAGE: &str = "
|
|
||||||
Wasm to native object translation utility.
|
|
||||||
Takes a binary WebAssembly module into a native object file.
|
|
||||||
The translation is dependent on the environment chosen.
|
|
||||||
The default is a dummy environment that produces placeholder values.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
wasm2obj [--target TARGET] [-Odg] [--disable-cache | --cache-config=<cache_config_file>] \
|
|
||||||
[--enable-simd] [--lightbeam | --cranelift] <file> -o <output>
|
|
||||||
wasm2obj --create-cache-config [--cache-config=<cache_config_file>]
|
|
||||||
wasm2obj --help | --version
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-v, --verbose displays the module and translated functions
|
|
||||||
-h, --help print this help message
|
|
||||||
--target <TARGET> build for the target triple; default is the host machine
|
|
||||||
-g generate debug information
|
|
||||||
--disable-cache disables cache system
|
|
||||||
--cache-config=<cache_config_file>
|
|
||||||
use specified cache configuration;
|
|
||||||
can be used with --create-cache-config to specify custom file
|
|
||||||
--create-cache-config
|
|
||||||
creates default configuration and writes it to the disk,
|
|
||||||
use with --cache-config to specify custom config file
|
|
||||||
instead of default one
|
|
||||||
--lightbeam use Lightbeam for all compilation
|
|
||||||
--cranelift use Cranelift for all compilation
|
|
||||||
--enable-simd enable proposed SIMD instructions
|
|
||||||
-O, --optimize runs optimization passes on the translated functions
|
|
||||||
--version print the Cranelift version
|
|
||||||
-d, --debug enable debug output on stderr/stdout
|
|
||||||
";
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
struct Args {
|
|
||||||
arg_file: String,
|
|
||||||
arg_output: String,
|
|
||||||
arg_target: Option<String>,
|
|
||||||
flag_g: bool,
|
|
||||||
flag_debug: bool,
|
|
||||||
flag_disable_cache: bool,
|
|
||||||
flag_cache_config: Option<String>,
|
|
||||||
flag_create_cache_config: bool,
|
|
||||||
flag_enable_simd: bool,
|
|
||||||
flag_lightbeam: bool,
|
|
||||||
flag_cranelift: bool,
|
|
||||||
flag_optimize: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
|
||||||
let args: Args = Docopt::new(USAGE)
|
|
||||||
.and_then(|d| {
|
|
||||||
d.help(true)
|
|
||||||
.version(Some(String::from(version)))
|
|
||||||
.deserialize()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|e| e.exit());
|
|
||||||
|
|
||||||
let log_config = if args.flag_debug {
|
|
||||||
pretty_env_logger::init();
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let prefix = "wasm2obj.dbg.";
|
|
||||||
wasmtime_cli::init_file_per_thread_logger(prefix);
|
|
||||||
Some(prefix)
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.flag_create_cache_config {
|
|
||||||
match cache_create_new_config(args.flag_cache_config) {
|
|
||||||
Ok(path) => {
|
|
||||||
println!(
|
|
||||||
"Successfully created new configuation file at {}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Error: {}", err);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let errors = cache_init(
|
|
||||||
!args.flag_disable_cache,
|
|
||||||
args.flag_cache_config.as_ref(),
|
|
||||||
log_config,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
eprintln!("Cache initialization failed. Errors:");
|
|
||||||
for e in errors {
|
|
||||||
eprintln!("-> {}", e);
|
|
||||||
}
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = Path::new(&args.arg_file);
|
|
||||||
match handle_module(
|
|
||||||
path.to_path_buf(),
|
|
||||||
&args.arg_target,
|
|
||||||
&args.arg_output,
|
|
||||||
args.flag_g,
|
|
||||||
args.flag_enable_simd,
|
|
||||||
args.flag_optimize,
|
|
||||||
args.flag_cranelift,
|
|
||||||
args.flag_lightbeam,
|
|
||||||
) {
|
|
||||||
Ok(()) => {}
|
|
||||||
Err(message) => {
|
|
||||||
println!(" error: {}", message);
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_module(
|
|
||||||
path: PathBuf,
|
|
||||||
target: &Option<String>,
|
|
||||||
output: &str,
|
|
||||||
generate_debug_info: bool,
|
|
||||||
enable_simd: bool,
|
|
||||||
enable_optimize: bool,
|
|
||||||
cranelift: bool,
|
|
||||||
lightbeam: bool,
|
|
||||||
) -> Result<()> {
|
|
||||||
let data = wat::parse_file(path)?;
|
|
||||||
|
|
||||||
let isa_builder = match *target {
|
|
||||||
Some(ref target) => {
|
|
||||||
let target =
|
|
||||||
Triple::from_str(&target).map_err(|_| anyhow!("could not parse --target"))?;
|
|
||||||
native::lookup(target)?
|
|
||||||
}
|
|
||||||
None => native::builder(),
|
|
||||||
};
|
|
||||||
let mut flag_builder = settings::builder();
|
|
||||||
|
|
||||||
// There are two possible traps for division, and this way
|
|
||||||
// we get the proper one if code traps.
|
|
||||||
flag_builder.enable("avoid_div_traps").unwrap();
|
|
||||||
|
|
||||||
if enable_simd {
|
|
||||||
flag_builder.enable("enable_simd").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if enable_optimize {
|
|
||||||
flag_builder.set("opt_level", "speed").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
|
||||||
|
|
||||||
let mut obj = Artifact::new(isa.triple().clone(), String::from(output));
|
|
||||||
|
|
||||||
// TODO: Expose the tunables as command-line flags.
|
|
||||||
let tunables = Tunables::default();
|
|
||||||
|
|
||||||
// Decide how to compile.
|
|
||||||
let strategy = pick_compilation_strategy(cranelift, lightbeam)?;
|
|
||||||
|
|
||||||
let (
|
|
||||||
module,
|
|
||||||
module_translation,
|
|
||||||
lazy_function_body_inputs,
|
|
||||||
lazy_data_initializers,
|
|
||||||
target_config,
|
|
||||||
) = {
|
|
||||||
let environ = ModuleEnvironment::new(isa.frontend_config(), tunables);
|
|
||||||
|
|
||||||
let translation = environ.translate(&data)?;
|
|
||||||
(
|
|
||||||
translation.module,
|
|
||||||
translation.module_translation.unwrap(),
|
|
||||||
translation.function_body_inputs,
|
|
||||||
translation.data_initializers,
|
|
||||||
translation.target_config,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: use the traps information
|
|
||||||
let (compilation, relocations, address_transform, value_ranges, stack_slots, _traps) =
|
|
||||||
match strategy {
|
|
||||||
Strategy::Auto | Strategy::Cranelift => Cranelift::compile_module(
|
|
||||||
&module,
|
|
||||||
&module_translation,
|
|
||||||
lazy_function_body_inputs,
|
|
||||||
&*isa,
|
|
||||||
generate_debug_info,
|
|
||||||
)?,
|
|
||||||
#[cfg(feature = "lightbeam")]
|
|
||||||
Strategy::Lightbeam => Lightbeam::compile_module(
|
|
||||||
&module,
|
|
||||||
&module_translation,
|
|
||||||
lazy_function_body_inputs,
|
|
||||||
&*isa,
|
|
||||||
generate_debug_info,
|
|
||||||
)?,
|
|
||||||
#[cfg(not(feature = "lightbeam"))]
|
|
||||||
Strategy::Lightbeam => bail!("lightbeam support not enabled"),
|
|
||||||
other => bail!("unsupported compilation strategy {:?}", other),
|
|
||||||
};
|
|
||||||
|
|
||||||
let module_vmctx_info = {
|
|
||||||
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module);
|
|
||||||
let memory_offset = ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)) as i64;
|
|
||||||
ModuleVmctxInfo {
|
|
||||||
memory_offset,
|
|
||||||
stack_slots,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
emit_module(
|
|
||||||
&mut obj,
|
|
||||||
&module,
|
|
||||||
&compilation,
|
|
||||||
&relocations,
|
|
||||||
&lazy_data_initializers,
|
|
||||||
&target_config,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if generate_debug_info {
|
|
||||||
let debug_data = read_debuginfo(&data);
|
|
||||||
emit_debugsections(
|
|
||||||
&mut obj,
|
|
||||||
&module_vmctx_info,
|
|
||||||
target_config,
|
|
||||||
&debug_data,
|
|
||||||
&address_transform,
|
|
||||||
&value_ranges,
|
|
||||||
)?
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Make the format a parameter.
|
|
||||||
let file = ::std::fs::File::create(Path::new(output))?;
|
|
||||||
obj.write(file)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,442 +1,73 @@
|
|||||||
//! CLI tool to use the functions provided by the [wasmtime](../wasmtime/index.html)
|
//! The `wasmtime` command line tool.
|
||||||
//! crate.
|
|
||||||
//!
|
//!
|
||||||
//! Reads Wasm binary files (one Wasm module per file), translates the functions' code to Cranelift
|
//! Primarily used to run WebAssembly modules.
|
||||||
//! IL. Can also execute the `start` function of the module by laying out the memories, globals
|
//! See `wasmtime --help` for usage.
|
||||||
//! and tables, then emitting the translated code with hardcoded addresses to memory.
|
|
||||||
|
|
||||||
#![deny(
|
use anyhow::Result;
|
||||||
missing_docs,
|
use structopt::{clap::AppSettings, clap::ErrorKind, StructOpt};
|
||||||
trivial_numeric_casts,
|
use wasmtime_cli::commands::{
|
||||||
unused_extern_crates,
|
ConfigCommand, RunCommand, WasmToObjCommand, WastCommand, WASM2OBJ_AFTER_HELP,
|
||||||
unstable_features
|
};
|
||||||
|
|
||||||
|
/// Wasmtime WebAssembly Runtime
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
#[structopt(
|
||||||
|
name = "wasmtime",
|
||||||
|
version = env!("CARGO_PKG_VERSION"),
|
||||||
|
global_settings = &[
|
||||||
|
AppSettings::VersionlessSubcommands,
|
||||||
|
AppSettings::ColoredHelp
|
||||||
|
],
|
||||||
|
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"
|
||||||
)]
|
)]
|
||||||
#![warn(unused_import_braces)]
|
enum WasmtimeApp {
|
||||||
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))]
|
// !!! IMPORTANT: if subcommands are added or removed, update `parse_module` in `src/commands/run.rs`. !!!
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
/// Controls Wasmtime configuration settings
|
||||||
#![cfg_attr(
|
Config(ConfigCommand),
|
||||||
feature = "cargo-clippy",
|
/// Runs a WebAssembly module
|
||||||
warn(
|
Run(RunCommand),
|
||||||
clippy::float_arithmetic,
|
/// Translates a WebAssembly module to native object file
|
||||||
clippy::mut_mut,
|
#[structopt(name = "wasm2obj", after_help = WASM2OBJ_AFTER_HELP)]
|
||||||
clippy::nonminimal_bool,
|
WasmToObj(WasmToObjCommand),
|
||||||
clippy::option_map_unwrap_or,
|
/// Runs a WebAssembly test script file
|
||||||
clippy::option_map_unwrap_or_else,
|
Wast(WastCommand),
|
||||||
clippy::unicode_not_nfc,
|
|
||||||
clippy::use_self
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
|
|
||||||
use anyhow::{bail, Context as _, Result};
|
|
||||||
use docopt::Docopt;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::path::{Component, Path};
|
|
||||||
use std::{collections::HashMap, ffi::OsStr, fs::File, process::exit};
|
|
||||||
use wasi_common::preopen_dir;
|
|
||||||
use wasmtime::{Config, Engine, HostRef, Instance, Module, Store};
|
|
||||||
use wasmtime_cli::pick_compilation_strategy;
|
|
||||||
use wasmtime_environ::{cache_create_new_config, cache_init};
|
|
||||||
use wasmtime_interface_types::ModuleData;
|
|
||||||
use wasmtime_wasi::create_wasi_instance;
|
|
||||||
use wasmtime_wasi::old::snapshot_0::create_wasi_instance as create_wasi_instance_snapshot_0;
|
|
||||||
#[cfg(feature = "wasi-c")]
|
|
||||||
use wasmtime_wasi_c::instantiate_wasi_c;
|
|
||||||
|
|
||||||
const USAGE: &str = "
|
|
||||||
Wasm runner.
|
|
||||||
|
|
||||||
Takes a binary (wasm) or text (wat) WebAssembly module and instantiates it,
|
|
||||||
including calling the start function if one is present. Additional functions
|
|
||||||
given with --invoke are then called.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
wasmtime [-odg] [--enable-simd] [--wasi-c] [--disable-cache | \
|
|
||||||
--cache-config=<cache_config_file>] [--preload=<wasm>...] [--env=<env>...] [--dir=<dir>...] \
|
|
||||||
[--mapdir=<mapping>...] [--lightbeam | --cranelift] <file> [<arg>...]
|
|
||||||
wasmtime [-odg] [--enable-simd] [--wasi-c] [--disable-cache | \
|
|
||||||
--cache-config=<cache_config_file>] [--env=<env>...] [--dir=<dir>...] \
|
|
||||||
[--mapdir=<mapping>...] --invoke=<fn> [--lightbeam | --cranelift] <file> [<arg>...]
|
|
||||||
wasmtime --create-cache-config [--cache-config=<cache_config_file>]
|
|
||||||
wasmtime --help | --version
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--invoke=<fn> name of function to run
|
|
||||||
-o, --optimize runs optimization passes on the translated functions
|
|
||||||
--disable-cache disables cache system
|
|
||||||
--cache-config=<cache_config_file>
|
|
||||||
use specified cache configuration;
|
|
||||||
can be used with --create-cache-config to specify custom file
|
|
||||||
--create-cache-config
|
|
||||||
creates default configuration and writes it to the disk,
|
|
||||||
use with --cache-config to specify custom config file
|
|
||||||
instead of default one
|
|
||||||
-g generate debug information
|
|
||||||
-d, --debug enable debug output on stderr/stdout
|
|
||||||
--lightbeam use Lightbeam for all compilation
|
|
||||||
--cranelift use Cranelift for all compilation
|
|
||||||
--enable-simd enable proposed SIMD instructions
|
|
||||||
--wasi-c enable the wasi-c implementation of `wasi_unstable`
|
|
||||||
--preload=<wasm> load an additional wasm module before loading the main module
|
|
||||||
--env=<env> pass an environment variable (\"key=value\") to the program
|
|
||||||
--dir=<dir> grant access to the given host directory
|
|
||||||
--mapdir=<mapping> where <mapping> has the form <wasmdir>::<hostdir>, grant access to
|
|
||||||
the given host directory with the given wasm directory name
|
|
||||||
-h, --help print this help message
|
|
||||||
--version print the Cranelift version
|
|
||||||
";
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
struct Args {
|
|
||||||
arg_file: String,
|
|
||||||
arg_arg: Vec<String>,
|
|
||||||
flag_optimize: bool,
|
|
||||||
flag_disable_cache: bool,
|
|
||||||
flag_cache_config: Option<String>,
|
|
||||||
flag_create_cache_config: bool,
|
|
||||||
flag_debug: bool,
|
|
||||||
flag_g: bool,
|
|
||||||
flag_enable_simd: bool,
|
|
||||||
flag_lightbeam: bool,
|
|
||||||
flag_cranelift: bool,
|
|
||||||
flag_invoke: Option<String>,
|
|
||||||
flag_preload: Vec<String>,
|
|
||||||
flag_env: Vec<String>,
|
|
||||||
flag_dir: Vec<String>,
|
|
||||||
flag_mapdir: Vec<String>,
|
|
||||||
flag_wasi_c: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_preopen_dirs(flag_dir: &[String], flag_mapdir: &[String]) -> Vec<(String, File)> {
|
impl WasmtimeApp {
|
||||||
let mut preopen_dirs = Vec::new();
|
/// Executes the command.
|
||||||
|
pub fn execute(&self) -> Result<()> {
|
||||||
for dir in flag_dir {
|
match self {
|
||||||
let preopen_dir = preopen_dir(dir).unwrap_or_else(|err| {
|
Self::Config(c) => c.execute(),
|
||||||
println!("error while pre-opening directory {}: {}", dir, err);
|
Self::Run(c) => c.execute(),
|
||||||
exit(1);
|
Self::WasmToObj(c) => c.execute(),
|
||||||
});
|
Self::Wast(c) => c.execute(),
|
||||||
preopen_dirs.push((dir.clone(), preopen_dir));
|
|
||||||
}
|
|
||||||
|
|
||||||
for mapdir in flag_mapdir {
|
|
||||||
let parts: Vec<&str> = mapdir.split("::").collect();
|
|
||||||
if parts.len() != 2 {
|
|
||||||
println!(
|
|
||||||
"--mapdir argument must contain exactly one double colon ('::'), separating a \
|
|
||||||
guest directory name and a host directory name"
|
|
||||||
);
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
let (key, value) = (parts[0], parts[1]);
|
|
||||||
let preopen_dir = preopen_dir(value).unwrap_or_else(|err| {
|
|
||||||
println!("error while pre-opening directory {}: {}", value, err);
|
|
||||||
exit(1);
|
|
||||||
});
|
|
||||||
preopen_dirs.push((key.to_string(), preopen_dir));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preopen_dirs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the argv array values.
|
|
||||||
fn compute_argv(argv0: &str, arg_arg: &[String]) -> Vec<String> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
// Add argv[0], which is the program name. Only include the base name of the
|
|
||||||
// main wasm module, to avoid leaking path information.
|
|
||||||
result.push(
|
|
||||||
Path::new(argv0)
|
|
||||||
.components()
|
|
||||||
.next_back()
|
|
||||||
.map(Component::as_os_str)
|
|
||||||
.and_then(OsStr::to_str)
|
|
||||||
.unwrap_or("")
|
|
||||||
.to_owned(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add the remaining arguments.
|
|
||||||
for arg in arg_arg {
|
|
||||||
result.push(arg.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the environ array values.
|
|
||||||
fn compute_environ(flag_env: &[String]) -> Vec<(String, String)> {
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
// Add the environment variables, which must be of the form "key=value".
|
|
||||||
for env in flag_env {
|
|
||||||
let split = env.splitn(2, '=').collect::<Vec<_>>();
|
|
||||||
if split.len() != 2 {
|
|
||||||
println!(
|
|
||||||
"environment variables must be of the form \"key=value\"; got \"{}\"",
|
|
||||||
env
|
|
||||||
);
|
|
||||||
}
|
|
||||||
result.push((split[0].to_owned(), split[1].to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
WasmtimeApp::from_iter_safe(std::env::args())
|
||||||
let args: Args = Docopt::new(USAGE)
|
.unwrap_or_else(|e| match e.kind {
|
||||||
.and_then(|d| {
|
ErrorKind::HelpDisplayed
|
||||||
d.help(true)
|
| ErrorKind::VersionDisplayed
|
||||||
.version(Some(String::from(version)))
|
| ErrorKind::MissingArgumentOrSubcommand => e.exit(),
|
||||||
.deserialize()
|
_ => WasmtimeApp::Run(
|
||||||
|
RunCommand::from_iter_safe(std::env::args()).unwrap_or_else(|_| e.exit()),
|
||||||
|
),
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|e| e.exit());
|
.execute()
|
||||||
|
|
||||||
let log_config = if args.flag_debug {
|
|
||||||
pretty_env_logger::init();
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let prefix = "wasmtime.dbg.";
|
|
||||||
wasmtime_cli::init_file_per_thread_logger(prefix);
|
|
||||||
Some(prefix)
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.flag_create_cache_config {
|
|
||||||
match cache_create_new_config(args.flag_cache_config) {
|
|
||||||
Ok(path) => {
|
|
||||||
println!(
|
|
||||||
"Successfully created new configuation file at {}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Error: {}", err);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let errors = cache_init(
|
|
||||||
!args.flag_disable_cache,
|
|
||||||
args.flag_cache_config.as_ref(),
|
|
||||||
log_config,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
eprintln!("Cache initialization failed. Errors:");
|
|
||||||
for e in errors {
|
|
||||||
eprintln!("-> {}", e);
|
|
||||||
}
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut config = Config::new();
|
|
||||||
|
|
||||||
// Enable/disable producing of debug info.
|
|
||||||
config.debug_info(args.flag_g);
|
|
||||||
|
|
||||||
// Enable verifier passes in debug mode.
|
|
||||||
config.cranelift_debug_verifier(cfg!(debug_assertions));
|
|
||||||
|
|
||||||
// Enable SIMD if requested
|
|
||||||
if args.flag_enable_simd {
|
|
||||||
config.wasm_simd(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable optimization if requested.
|
|
||||||
if args.flag_optimize {
|
|
||||||
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decide how to compile.
|
|
||||||
config.strategy(pick_compilation_strategy(
|
|
||||||
args.flag_cranelift,
|
|
||||||
args.flag_lightbeam,
|
|
||||||
)?)?;
|
|
||||||
let engine = Engine::new(&config);
|
|
||||||
let store = HostRef::new(Store::new(&engine));
|
|
||||||
|
|
||||||
let mut module_registry = HashMap::new();
|
|
||||||
|
|
||||||
// Make wasi available by default.
|
|
||||||
let preopen_dirs = compute_preopen_dirs(&args.flag_dir, &args.flag_mapdir);
|
|
||||||
let argv = compute_argv(&args.arg_file, &args.arg_arg);
|
|
||||||
let environ = compute_environ(&args.flag_env);
|
|
||||||
|
|
||||||
let wasi_unstable = HostRef::new(if args.flag_wasi_c {
|
|
||||||
#[cfg(feature = "wasi-c")]
|
|
||||||
{
|
|
||||||
let global_exports = store.borrow().global_exports().clone();
|
|
||||||
let handle = instantiate_wasi_c(global_exports, &preopen_dirs, &argv, &environ)?;
|
|
||||||
Instance::from_handle(&store, handle)
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "wasi-c"))]
|
|
||||||
{
|
|
||||||
bail!("wasi-c feature not enabled at build time")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
create_wasi_instance_snapshot_0(&store, &preopen_dirs, &argv, &environ)?
|
|
||||||
});
|
|
||||||
|
|
||||||
let wasi_snapshot_preview1 = HostRef::new(create_wasi_instance(
|
|
||||||
&store,
|
|
||||||
&preopen_dirs,
|
|
||||||
&argv,
|
|
||||||
&environ,
|
|
||||||
)?);
|
|
||||||
|
|
||||||
module_registry.insert("wasi_unstable".to_owned(), wasi_unstable);
|
|
||||||
module_registry.insert("wasi_snapshot_preview1".to_owned(), wasi_snapshot_preview1);
|
|
||||||
|
|
||||||
// Load the preload wasm modules.
|
|
||||||
for filename in &args.flag_preload {
|
|
||||||
let path = Path::new(&filename);
|
|
||||||
instantiate_module(&store, &module_registry, path)
|
|
||||||
.with_context(|| format!("failed to process preload at `{}`", path.display()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the main wasm module.
|
|
||||||
let path = Path::new(&args.arg_file);
|
|
||||||
handle_module(&store, &module_registry, &args, path)
|
|
||||||
.with_context(|| format!("failed to process main module `{}`", path.display()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instantiate_module(
|
|
||||||
store: &HostRef<Store>,
|
|
||||||
module_registry: &HashMap<String, HostRef<Instance>>,
|
|
||||||
path: &Path,
|
|
||||||
) -> Result<(HostRef<Instance>, HostRef<Module>, Vec<u8>)> {
|
|
||||||
// Read the wasm module binary either as `*.wat` or a raw binary
|
|
||||||
let data = wat::parse_file(path.to_path_buf())?;
|
|
||||||
|
|
||||||
let module = HostRef::new(Module::new(store, &data)?);
|
|
||||||
|
|
||||||
// Resolve import using module_registry.
|
|
||||||
let imports = module
|
|
||||||
.borrow()
|
|
||||||
.imports()
|
|
||||||
.iter()
|
|
||||||
.map(|i| {
|
|
||||||
let module_name = i.module();
|
|
||||||
if let Some(instance) = module_registry.get(module_name) {
|
|
||||||
let field_name = i.name();
|
|
||||||
if let Some(export) = instance.borrow().find_export_by_name(field_name) {
|
|
||||||
Ok(export.clone())
|
|
||||||
} else {
|
|
||||||
bail!(
|
|
||||||
"Import {} was not found in module {}",
|
|
||||||
field_name,
|
|
||||||
module_name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bail!("Import module {} was not found", module_name)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
let instance = HostRef::new(match Instance::new(store, &module, &imports) {
|
|
||||||
Ok(instance) => instance,
|
|
||||||
Err(trap) => bail!("Failed to instantiate {:?}: {:?}", path, trap),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok((instance, module, data))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_module(
|
|
||||||
store: &HostRef<Store>,
|
|
||||||
module_registry: &HashMap<String, HostRef<Instance>>,
|
|
||||||
args: &Args,
|
|
||||||
path: &Path,
|
|
||||||
) -> Result<()> {
|
|
||||||
let (instance, module, data) = instantiate_module(store, module_registry, path)?;
|
|
||||||
|
|
||||||
// If a function to invoke was given, invoke it.
|
|
||||||
if let Some(f) = &args.flag_invoke {
|
|
||||||
let data = ModuleData::new(&data)?;
|
|
||||||
invoke_export(instance, &data, f, args)?;
|
|
||||||
} else if module
|
|
||||||
.borrow()
|
|
||||||
.exports()
|
|
||||||
.iter()
|
|
||||||
.any(|export| export.name().is_empty())
|
|
||||||
{
|
|
||||||
// Launch the default command export.
|
|
||||||
let data = ModuleData::new(&data)?;
|
|
||||||
invoke_export(instance, &data, "", args)?;
|
|
||||||
} else {
|
|
||||||
// If the module doesn't have a default command export, launch the
|
|
||||||
// _start function if one is present, as a compatibility measure.
|
|
||||||
let data = ModuleData::new(&data)?;
|
|
||||||
invoke_export(instance, &data, "_start", args)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invoke_export(
|
|
||||||
instance: HostRef<Instance>,
|
|
||||||
data: &ModuleData,
|
|
||||||
name: &str,
|
|
||||||
args: &Args,
|
|
||||||
) -> Result<()> {
|
|
||||||
use wasm_webidl_bindings::ast;
|
|
||||||
use wasmtime_interface_types::Value;
|
|
||||||
|
|
||||||
let mut handle = instance.borrow().handle().clone();
|
|
||||||
|
|
||||||
// Use the binding information in `ModuleData` to figure out what arguments
|
|
||||||
// need to be passed to the function that we're invoking. Currently we take
|
|
||||||
// the CLI parameters and attempt to parse them into function arguments for
|
|
||||||
// the function we'll invoke.
|
|
||||||
let binding = data.binding_for_export(&mut handle, name)?;
|
|
||||||
if !binding.param_types()?.is_empty() {
|
|
||||||
eprintln!(
|
|
||||||
"warning: using `--invoke` with a function that takes arguments \
|
|
||||||
is experimental and may break in the future"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let mut values = Vec::new();
|
|
||||||
let mut args = args.arg_arg.iter();
|
|
||||||
for ty in binding.param_types()? {
|
|
||||||
let val = match args.next() {
|
|
||||||
Some(s) => s,
|
|
||||||
None => bail!("not enough arguments for `{}`", name),
|
|
||||||
};
|
|
||||||
values.push(match ty {
|
|
||||||
// TODO: integer parsing here should handle hexadecimal notation
|
|
||||||
// like `0x0...`, but the Rust standard library currently only
|
|
||||||
// parses base-10 representations.
|
|
||||||
ast::WebidlScalarType::Long => Value::I32(val.parse()?),
|
|
||||||
ast::WebidlScalarType::LongLong => Value::I64(val.parse()?),
|
|
||||||
ast::WebidlScalarType::UnsignedLong => Value::U32(val.parse()?),
|
|
||||||
ast::WebidlScalarType::UnsignedLongLong => Value::U64(val.parse()?),
|
|
||||||
|
|
||||||
ast::WebidlScalarType::Float | ast::WebidlScalarType::UnrestrictedFloat => {
|
|
||||||
Value::F32(val.parse()?)
|
|
||||||
}
|
|
||||||
ast::WebidlScalarType::Double | ast::WebidlScalarType::UnrestrictedDouble => {
|
|
||||||
Value::F64(val.parse()?)
|
|
||||||
}
|
|
||||||
ast::WebidlScalarType::DomString => Value::String(val.to_string()),
|
|
||||||
t => bail!("unsupported argument type {:?}", t),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoke the function and then afterwards print all the results that came
|
|
||||||
// out, if there are any.
|
|
||||||
let results = data
|
|
||||||
.invoke_export(&instance, name, &values)
|
|
||||||
.with_context(|| format!("failed to invoke `{}`", name))?;
|
|
||||||
if !results.is_empty() {
|
|
||||||
eprintln!(
|
|
||||||
"warning: using `--invoke` with a function that returns values \
|
|
||||||
is experimental and may break in the future"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for result in results {
|
|
||||||
println!("{}", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
152
src/bin/wast.rs
152
src/bin/wast.rs
@@ -1,148 +1,12 @@
|
|||||||
//! CLI tool to run wast tests using the wasmtime libraries.
|
//! The `wast` command line tool.
|
||||||
|
//!
|
||||||
|
//! Runs WebAssembly test script files.
|
||||||
|
//! See `wast --help` for usage.
|
||||||
|
|
||||||
#![deny(
|
use anyhow::Result;
|
||||||
missing_docs,
|
use structopt::StructOpt;
|
||||||
trivial_numeric_casts,
|
use wasmtime_cli::commands::WastCommand;
|
||||||
unused_extern_crates,
|
|
||||||
unstable_features
|
|
||||||
)]
|
|
||||||
#![warn(unused_import_braces)]
|
|
||||||
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../../clippy.toml")))]
|
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
|
||||||
#![cfg_attr(
|
|
||||||
feature = "cargo-clippy",
|
|
||||||
warn(
|
|
||||||
clippy::float_arithmetic,
|
|
||||||
clippy::mut_mut,
|
|
||||||
clippy::nonminimal_bool,
|
|
||||||
clippy::option_map_unwrap_or,
|
|
||||||
clippy::option_map_unwrap_or_else,
|
|
||||||
clippy::unicode_not_nfc,
|
|
||||||
clippy::use_self
|
|
||||||
)
|
|
||||||
)]
|
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use docopt::Docopt;
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process;
|
|
||||||
use wasmtime::{Config, Engine, HostRef, Store};
|
|
||||||
use wasmtime_cli::pick_compilation_strategy;
|
|
||||||
use wasmtime_environ::{cache_create_new_config, cache_init};
|
|
||||||
use wasmtime_wast::WastContext;
|
|
||||||
|
|
||||||
const USAGE: &str = "
|
|
||||||
Wast test runner.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
wast [-do] [--enable-simd] [--disable-cache | --cache-config=<cache_config_file>] [--lightbeam \
|
|
||||||
| --cranelift] <file>...
|
|
||||||
wast --create-cache-config [--cache-config=<cache_config_file>]
|
|
||||||
wast --help | --version
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-h, --help print this help message
|
|
||||||
--version print the Cranelift version
|
|
||||||
-o, --optimize runs optimization passes on the translated functions
|
|
||||||
--disable-cache disables cache system
|
|
||||||
--cache-config=<cache_config_file>
|
|
||||||
use specified cache configuration;
|
|
||||||
can be used with --create-cache-config to specify custom file
|
|
||||||
--create-cache-config
|
|
||||||
creates default configuration and writes it to the disk,
|
|
||||||
use with --cache-config to specify custom config file
|
|
||||||
instead of default one
|
|
||||||
--lightbeam use Lightbeam for all compilation
|
|
||||||
--cranelift use Cranelift for all compilation
|
|
||||||
-d, --debug enable debug output on stderr/stdout
|
|
||||||
--enable-simd enable proposed SIMD instructions
|
|
||||||
";
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
|
||||||
struct Args {
|
|
||||||
arg_file: Vec<String>,
|
|
||||||
flag_debug: bool,
|
|
||||||
flag_function: Option<String>,
|
|
||||||
flag_optimize: bool,
|
|
||||||
flag_disable_cache: bool,
|
|
||||||
flag_cache_config: Option<String>,
|
|
||||||
flag_create_cache_config: bool,
|
|
||||||
flag_enable_simd: bool,
|
|
||||||
flag_lightbeam: bool,
|
|
||||||
flag_cranelift: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let version = env!("CARGO_PKG_VERSION");
|
WastCommand::from_args().execute()
|
||||||
let args: Args = Docopt::new(USAGE)
|
|
||||||
.and_then(|d| {
|
|
||||||
d.help(true)
|
|
||||||
.version(Some(String::from(version)))
|
|
||||||
.deserialize()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|e| e.exit());
|
|
||||||
|
|
||||||
let log_config = if args.flag_debug {
|
|
||||||
pretty_env_logger::init();
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let prefix = "cranelift.dbg.";
|
|
||||||
wasmtime_cli::init_file_per_thread_logger(prefix);
|
|
||||||
Some(prefix)
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.flag_create_cache_config {
|
|
||||||
let path = cache_create_new_config(args.flag_cache_config)?;
|
|
||||||
println!(
|
|
||||||
"Successfully created new configuation file at {}",
|
|
||||||
path.display()
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let errors = cache_init(
|
|
||||||
!args.flag_disable_cache,
|
|
||||||
args.flag_cache_config.as_ref(),
|
|
||||||
log_config,
|
|
||||||
);
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
eprintln!("Cache initialization failed. Errors:");
|
|
||||||
for e in errors {
|
|
||||||
eprintln!("-> {}", e);
|
|
||||||
}
|
|
||||||
process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cfg = Config::new();
|
|
||||||
// Enable verifier passes in debug mode.
|
|
||||||
cfg.cranelift_debug_verifier(cfg!(debug_assertions));
|
|
||||||
|
|
||||||
// Enable optimization if requested.
|
|
||||||
if args.flag_optimize {
|
|
||||||
cfg.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable SIMD if requested
|
|
||||||
if args.flag_enable_simd {
|
|
||||||
cfg.wasm_simd(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decide how to compile.
|
|
||||||
cfg.strategy(pick_compilation_strategy(
|
|
||||||
args.flag_cranelift,
|
|
||||||
args.flag_lightbeam,
|
|
||||||
)?)?;
|
|
||||||
let store = HostRef::new(Store::new(&Engine::new(&cfg)));
|
|
||||||
let mut wast_context = WastContext::new(store);
|
|
||||||
|
|
||||||
wast_context
|
|
||||||
.register_spectest()
|
|
||||||
.context("error instantiating \"spectest\"")?;
|
|
||||||
|
|
||||||
for filename in &args.arg_file {
|
|
||||||
wast_context.run_file(Path::new(&filename))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/commands.rs
Normal file
8
src/commands.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
//! The module for the Wasmtime CLI commands.
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod run;
|
||||||
|
mod wasm2obj;
|
||||||
|
mod wast;
|
||||||
|
|
||||||
|
pub use self::{config::*, run::*, wasm2obj::*, wast::*};
|
||||||
49
src/commands/config.rs
Normal file
49
src/commands/config.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
//! The module that implements the `wasmtime config` command.
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use structopt::StructOpt;
|
||||||
|
use wasmtime_environ::cache_create_new_config;
|
||||||
|
|
||||||
|
const CONFIG_NEW_AFTER_HELP: &str =
|
||||||
|
"If no file path is specified, the system configuration file path will be used.";
|
||||||
|
|
||||||
|
/// Controls Wasmtime configuration settings
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
#[structopt(name = "run")]
|
||||||
|
pub enum ConfigCommand {
|
||||||
|
/// Creates a new Wasmtime configuration file
|
||||||
|
#[structopt(after_help = CONFIG_NEW_AFTER_HELP)]
|
||||||
|
New(ConfigNewCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigCommand {
|
||||||
|
/// Executes the command.
|
||||||
|
pub fn execute(&self) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::New(c) => c.execute(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new Wasmtime configuration file
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
#[structopt(name = "new", after_help = CONFIG_NEW_AFTER_HELP)]
|
||||||
|
pub struct ConfigNewCommand {
|
||||||
|
/// The path of the new configuration file
|
||||||
|
#[structopt(index = 1, value_name = "FILE_PATH")]
|
||||||
|
path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConfigNewCommand {
|
||||||
|
/// Executes the command.
|
||||||
|
pub fn execute(&self) -> Result<()> {
|
||||||
|
let path = cache_create_new_config(self.path.as_ref()).map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Successfully created a new configuation file at '{}'.",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
372
src/commands/run.rs
Normal file
372
src/commands/run.rs
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
//! The module that implements the `wasmtime run` command.
|
||||||
|
|
||||||
|
use crate::{init_file_per_thread_logger, pick_compilation_strategy, CommonOptions};
|
||||||
|
use anyhow::{bail, Context as _, Result};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::{OsStr, OsString},
|
||||||
|
fmt::Write,
|
||||||
|
fs::File,
|
||||||
|
path::{Component, Path, PathBuf},
|
||||||
|
};
|
||||||
|
use structopt::{clap::AppSettings, StructOpt};
|
||||||
|
use wasi_common::preopen_dir;
|
||||||
|
use wasmtime::{Config, Engine, HostRef, Instance, Module, Store};
|
||||||
|
use wasmtime_environ::cache_init;
|
||||||
|
use wasmtime_interface_types::ModuleData;
|
||||||
|
use wasmtime_wasi::{
|
||||||
|
create_wasi_instance, old::snapshot_0::create_wasi_instance as create_wasi_instance_snapshot_0,
|
||||||
|
};
|
||||||
|
#[cfg(feature = "wasi-c")]
|
||||||
|
use wasmtime_wasi_c::instantiate_wasi_c;
|
||||||
|
#[cfg(feature = "wasi-c")]
|
||||||
|
use wasmtime_wasi_c::instantiate_wasi_c;
|
||||||
|
|
||||||
|
fn parse_module(s: &OsStr) -> Result<PathBuf, OsString> {
|
||||||
|
// Do not accept wasmtime subcommand names as the module name
|
||||||
|
match s.to_str() {
|
||||||
|
Some("help") | Some("config") | Some("run") | Some("wasm2obj") | Some("wast") => {
|
||||||
|
Err("module name cannot be the same as a subcommand".into())
|
||||||
|
}
|
||||||
|
_ => Ok(s.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_env_var(s: &str) -> Result<(String, String)> {
|
||||||
|
let parts: Vec<_> = s.splitn(2, '=').collect();
|
||||||
|
if parts.len() != 2 {
|
||||||
|
bail!("must be of the form `key=value`");
|
||||||
|
}
|
||||||
|
Ok((parts[0].to_owned(), parts[1].to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
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()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs a WebAssembly module
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
#[structopt(name = "run", setting = AppSettings::TrailingVarArg)]
|
||||||
|
pub struct RunCommand {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
common: CommonOptions,
|
||||||
|
|
||||||
|
/// Grant access to the given host directory
|
||||||
|
#[structopt(long = "dir", number_of_values = 1, value_name = "DIRECTORY")]
|
||||||
|
dirs: Vec<String>,
|
||||||
|
|
||||||
|
/// Pass an environment variable to the program
|
||||||
|
#[structopt(long = "env", number_of_values = 1, value_name = "NAME=VAL", parse(try_from_str = parse_env_var))]
|
||||||
|
vars: Vec<(String, String)>,
|
||||||
|
|
||||||
|
/// The name of the function to run
|
||||||
|
#[structopt(long, value_name = "FUNCTION")]
|
||||||
|
invoke: Option<String>,
|
||||||
|
|
||||||
|
/// Grant access to a guest directory mapped as a host directory
|
||||||
|
#[structopt(long = "mapdir", number_of_values = 1, value_name = "GUEST_DIR::HOST_DIR", parse(try_from_str = parse_map_dirs))]
|
||||||
|
map_dirs: Vec<(String, String)>,
|
||||||
|
|
||||||
|
/// The path of the WebAssembly module to run
|
||||||
|
#[structopt(
|
||||||
|
index = 1,
|
||||||
|
required = true,
|
||||||
|
value_name = "WASM_MODULE",
|
||||||
|
parse(try_from_os_str = parse_module),
|
||||||
|
)]
|
||||||
|
module: PathBuf,
|
||||||
|
|
||||||
|
/// Load the given WebAssembly module before the main module
|
||||||
|
#[structopt(
|
||||||
|
long = "preload",
|
||||||
|
number_of_values = 1,
|
||||||
|
value_name = "MODULE_PATH",
|
||||||
|
parse(from_os_str)
|
||||||
|
)]
|
||||||
|
preloads: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// Enable the wasi-c implementation of `wasi_unstable`
|
||||||
|
#[structopt(long = "wasi-c")]
|
||||||
|
enable_wasi_c: bool,
|
||||||
|
|
||||||
|
// NOTE: this must come last for trailing varargs
|
||||||
|
/// The arguments to pass to the module
|
||||||
|
#[structopt(value_name = "ARGS")]
|
||||||
|
module_args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunCommand {
|
||||||
|
/// Executes the command.
|
||||||
|
pub fn execute(&self) -> Result<()> {
|
||||||
|
let log_config = if self.common.debug {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let prefix = "wasmtime.dbg.";
|
||||||
|
init_file_per_thread_logger(prefix);
|
||||||
|
Some(prefix)
|
||||||
|
};
|
||||||
|
|
||||||
|
let errors = cache_init(
|
||||||
|
!self.common.disable_cache,
|
||||||
|
self.common.config.as_ref(),
|
||||||
|
log_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
let mut message = String::new();
|
||||||
|
writeln!(message, "Cache initialization failed. Errors:")?;
|
||||||
|
for e in errors {
|
||||||
|
writeln!(message, " -> {}", e)?;
|
||||||
|
}
|
||||||
|
bail!(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut config = Config::new();
|
||||||
|
config
|
||||||
|
.cranelift_debug_verifier(cfg!(debug_assertions))
|
||||||
|
.debug_info(self.common.debug_info)
|
||||||
|
.wasm_simd(self.common.enable_simd)
|
||||||
|
.strategy(pick_compilation_strategy(
|
||||||
|
self.common.cranelift,
|
||||||
|
self.common.lightbeam,
|
||||||
|
)?)?;
|
||||||
|
|
||||||
|
if self.common.optimize {
|
||||||
|
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let engine = Engine::new(&config);
|
||||||
|
let store = HostRef::new(Store::new(&engine));
|
||||||
|
let mut module_registry = HashMap::new();
|
||||||
|
|
||||||
|
// Make wasi available by default.
|
||||||
|
let preopen_dirs = self.compute_preopen_dirs()?;
|
||||||
|
let argv = self.compute_argv();
|
||||||
|
|
||||||
|
let wasi_unstable = HostRef::new(if self.enable_wasi_c {
|
||||||
|
#[cfg(feature = "wasi-c")]
|
||||||
|
{
|
||||||
|
let global_exports = store.borrow().global_exports().clone();
|
||||||
|
let handle = instantiate_wasi_c(global_exports, &preopen_dirs, &argv, &self.vars)?;
|
||||||
|
Instance::from_handle(&store, handle)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "wasi-c"))]
|
||||||
|
{
|
||||||
|
bail!("wasi-c feature not enabled at build time")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
create_wasi_instance_snapshot_0(&store, &preopen_dirs, &argv, &self.vars)?
|
||||||
|
});
|
||||||
|
|
||||||
|
let wasi_snapshot_preview1 = HostRef::new(create_wasi_instance(
|
||||||
|
&store,
|
||||||
|
&preopen_dirs,
|
||||||
|
&argv,
|
||||||
|
&self.vars,
|
||||||
|
)?);
|
||||||
|
|
||||||
|
module_registry.insert("wasi_unstable".to_owned(), wasi_unstable);
|
||||||
|
module_registry.insert("wasi_snapshot_preview1".to_owned(), wasi_snapshot_preview1);
|
||||||
|
|
||||||
|
// Load the preload wasm modules.
|
||||||
|
for preload in self.preloads.iter() {
|
||||||
|
Self::instantiate_module(&store, &module_registry, preload)
|
||||||
|
.with_context(|| format!("failed to process preload at `{}`", preload.display()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the main wasm module.
|
||||||
|
self.handle_module(&store, &module_registry)
|
||||||
|
.with_context(|| format!("failed to run main module `{}`", self.module.display()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_preopen_dirs(&self) -> Result<Vec<(String, File)>> {
|
||||||
|
let mut preopen_dirs = Vec::new();
|
||||||
|
|
||||||
|
for dir in self.dirs.iter() {
|
||||||
|
preopen_dirs.push((
|
||||||
|
dir.clone(),
|
||||||
|
preopen_dir(dir).with_context(|| format!("failed to open directory '{}'", dir))?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (guest, host) in self.map_dirs.iter() {
|
||||||
|
preopen_dirs.push((
|
||||||
|
guest.clone(),
|
||||||
|
preopen_dir(host)
|
||||||
|
.with_context(|| format!("failed to open directory '{}'", host))?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(preopen_dirs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_argv(&self) -> Vec<String> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
// Add argv[0], which is the program name. Only include the base name of the
|
||||||
|
// main wasm module, to avoid leaking path information.
|
||||||
|
result.push(
|
||||||
|
self.module
|
||||||
|
.components()
|
||||||
|
.next_back()
|
||||||
|
.map(Component::as_os_str)
|
||||||
|
.and_then(OsStr::to_str)
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_owned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the remaining arguments.
|
||||||
|
for arg in self.module_args.iter() {
|
||||||
|
result.push(arg.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate_module(
|
||||||
|
store: &HostRef<Store>,
|
||||||
|
module_registry: &HashMap<String, HostRef<Instance>>,
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<(HostRef<Instance>, HostRef<Module>, Vec<u8>)> {
|
||||||
|
// Read the wasm module binary either as `*.wat` or a raw binary
|
||||||
|
let data = wat::parse_file(path.to_path_buf())?;
|
||||||
|
|
||||||
|
let module = HostRef::new(Module::new(store, &data)?);
|
||||||
|
|
||||||
|
// Resolve import using module_registry.
|
||||||
|
let imports = module
|
||||||
|
.borrow()
|
||||||
|
.imports()
|
||||||
|
.iter()
|
||||||
|
.map(|i| {
|
||||||
|
let module_name = i.module();
|
||||||
|
if let Some(instance) = module_registry.get(module_name) {
|
||||||
|
let field_name = i.name();
|
||||||
|
if let Some(export) = instance.borrow().find_export_by_name(field_name) {
|
||||||
|
Ok(export.clone())
|
||||||
|
} else {
|
||||||
|
bail!(
|
||||||
|
"Import {} was not found in module {}",
|
||||||
|
field_name,
|
||||||
|
module_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("Import module {} was not found", module_name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let instance = HostRef::new(match Instance::new(store, &module, &imports) {
|
||||||
|
Ok(instance) => instance,
|
||||||
|
Err(trap) => bail!("Failed to instantiate {:?}: {:?}", path, trap),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok((instance, module, data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_module(
|
||||||
|
&self,
|
||||||
|
store: &HostRef<Store>,
|
||||||
|
module_registry: &HashMap<String, HostRef<Instance>>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (instance, module, data) =
|
||||||
|
Self::instantiate_module(store, module_registry, &self.module)?;
|
||||||
|
|
||||||
|
// If a function to invoke was given, invoke it.
|
||||||
|
if let Some(name) = self.invoke.as_ref() {
|
||||||
|
let data = ModuleData::new(&data)?;
|
||||||
|
self.invoke_export(instance, &data, name)?;
|
||||||
|
} else if module
|
||||||
|
.borrow()
|
||||||
|
.exports()
|
||||||
|
.iter()
|
||||||
|
.any(|export| export.name().is_empty())
|
||||||
|
{
|
||||||
|
// Launch the default command export.
|
||||||
|
let data = ModuleData::new(&data)?;
|
||||||
|
self.invoke_export(instance, &data, "")?;
|
||||||
|
} else {
|
||||||
|
// If the module doesn't have a default command export, launch the
|
||||||
|
// _start function if one is present, as a compatibility measure.
|
||||||
|
let data = ModuleData::new(&data)?;
|
||||||
|
self.invoke_export(instance, &data, "_start")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invoke_export(
|
||||||
|
&self,
|
||||||
|
instance: HostRef<Instance>,
|
||||||
|
data: &ModuleData,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
use wasm_webidl_bindings::ast;
|
||||||
|
use wasmtime_interface_types::Value;
|
||||||
|
|
||||||
|
let mut handle = instance.borrow().handle().clone();
|
||||||
|
|
||||||
|
// Use the binding information in `ModuleData` to figure out what arguments
|
||||||
|
// need to be passed to the function that we're invoking. Currently we take
|
||||||
|
// the CLI parameters and attempt to parse them into function arguments for
|
||||||
|
// the function we'll invoke.
|
||||||
|
let binding = data.binding_for_export(&mut handle, name)?;
|
||||||
|
if !binding.param_types()?.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"warning: using `--invoke` with a function that takes arguments \
|
||||||
|
is experimental and may break in the future"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let mut args = self.module_args.iter();
|
||||||
|
let mut values = Vec::new();
|
||||||
|
for ty in binding.param_types()? {
|
||||||
|
let val = match args.next() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => bail!("not enough arguments for `{}`", name),
|
||||||
|
};
|
||||||
|
values.push(match ty {
|
||||||
|
// TODO: integer parsing here should handle hexadecimal notation
|
||||||
|
// like `0x0...`, but the Rust standard library currently only
|
||||||
|
// parses base-10 representations.
|
||||||
|
ast::WebidlScalarType::Long => Value::I32(val.parse()?),
|
||||||
|
ast::WebidlScalarType::LongLong => Value::I64(val.parse()?),
|
||||||
|
ast::WebidlScalarType::UnsignedLong => Value::U32(val.parse()?),
|
||||||
|
ast::WebidlScalarType::UnsignedLongLong => Value::U64(val.parse()?),
|
||||||
|
|
||||||
|
ast::WebidlScalarType::Float | ast::WebidlScalarType::UnrestrictedFloat => {
|
||||||
|
Value::F32(val.parse()?)
|
||||||
|
}
|
||||||
|
ast::WebidlScalarType::Double | ast::WebidlScalarType::UnrestrictedDouble => {
|
||||||
|
Value::F64(val.parse()?)
|
||||||
|
}
|
||||||
|
ast::WebidlScalarType::DomString => Value::String(val.to_string()),
|
||||||
|
t => bail!("unsupported argument type {:?}", t),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke the function and then afterwards print all the results that came
|
||||||
|
// out, if there are any.
|
||||||
|
let results = data
|
||||||
|
.invoke_export(&instance, name, &values)
|
||||||
|
.with_context(|| format!("failed to invoke `{}`", name))?;
|
||||||
|
if !results.is_empty() {
|
||||||
|
eprintln!(
|
||||||
|
"warning: using `--invoke` with a function that returns values \
|
||||||
|
is experimental and may break in the future"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for result in results {
|
||||||
|
println!("{}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
205
src/commands/wasm2obj.rs
Normal file
205
src/commands/wasm2obj.rs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
//! The module that implements the `wasmtime wasm2obj` command.
|
||||||
|
|
||||||
|
use crate::{init_file_per_thread_logger, pick_compilation_strategy, CommonOptions};
|
||||||
|
use anyhow::{anyhow, bail, Context as _, Result};
|
||||||
|
use faerie::Artifact;
|
||||||
|
use std::{
|
||||||
|
fmt::Write,
|
||||||
|
fs::File,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
use structopt::{clap::AppSettings, StructOpt};
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
use wasmtime::Strategy;
|
||||||
|
use wasmtime_debug::{emit_debugsections, read_debuginfo};
|
||||||
|
#[cfg(feature = "lightbeam")]
|
||||||
|
use wasmtime_environ::Lightbeam;
|
||||||
|
use wasmtime_environ::{
|
||||||
|
cache_init, entity::EntityRef, settings, settings::Configurable, wasm::DefinedMemoryIndex,
|
||||||
|
Compiler, Cranelift, ModuleEnvironment, ModuleVmctxInfo, Tunables, VMOffsets,
|
||||||
|
};
|
||||||
|
use wasmtime_jit::native;
|
||||||
|
use wasmtime_obj::emit_module;
|
||||||
|
|
||||||
|
/// The after help text for the `wasm2obj` command.
|
||||||
|
pub const WASM2OBJ_AFTER_HELP: &str =
|
||||||
|
"The translation is dependent on the environment chosen.\n\
|
||||||
|
The default is a dummy environment that produces placeholder values.";
|
||||||
|
|
||||||
|
fn parse_target(s: &str) -> Result<Triple> {
|
||||||
|
Triple::from_str(&s).map_err(|e| anyhow!(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translates a WebAssembly module to native object file
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
#[structopt(
|
||||||
|
name = "wasm2obj",
|
||||||
|
version = env!("CARGO_PKG_VERSION"),
|
||||||
|
setting = AppSettings::ColoredHelp,
|
||||||
|
after_help = WASM2OBJ_AFTER_HELP,
|
||||||
|
)]
|
||||||
|
pub struct WasmToObjCommand {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
common: CommonOptions,
|
||||||
|
|
||||||
|
/// The path of the WebAssembly module to translate
|
||||||
|
#[structopt(index = 1, value_name = "MODULE_PATH", parse(from_os_str))]
|
||||||
|
module: PathBuf,
|
||||||
|
|
||||||
|
/// The path of the output object file
|
||||||
|
#[structopt(index = 2, value_name = "OUTPUT_PATH")]
|
||||||
|
output: String,
|
||||||
|
|
||||||
|
/// The target triple; default is the host triple
|
||||||
|
#[structopt(long, value_name = "TARGET", parse(try_from_str = parse_target))]
|
||||||
|
target: Option<Triple>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmToObjCommand {
|
||||||
|
/// Executes the command.
|
||||||
|
pub fn execute(&self) -> Result<()> {
|
||||||
|
let log_config = if self.common.debug {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let prefix = "wasm2obj.dbg.";
|
||||||
|
init_file_per_thread_logger(prefix);
|
||||||
|
Some(prefix)
|
||||||
|
};
|
||||||
|
|
||||||
|
let errors = cache_init(
|
||||||
|
!self.common.disable_cache,
|
||||||
|
self.common.config.as_ref(),
|
||||||
|
log_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
let mut message = String::new();
|
||||||
|
writeln!(message, "Cache initialization failed. Errors:")?;
|
||||||
|
for e in errors {
|
||||||
|
writeln!(message, " -> {}", e)?;
|
||||||
|
}
|
||||||
|
bail!(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle_module()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_module(&self) -> Result<()> {
|
||||||
|
let strategy = pick_compilation_strategy(self.common.cranelift, self.common.lightbeam)?;
|
||||||
|
|
||||||
|
let data = wat::parse_file(&self.module).context("failed to parse module")?;
|
||||||
|
|
||||||
|
let isa_builder = match self.target.as_ref() {
|
||||||
|
Some(target) => native::lookup(target.clone())?,
|
||||||
|
None => native::builder(),
|
||||||
|
};
|
||||||
|
let mut flag_builder = settings::builder();
|
||||||
|
|
||||||
|
// There are two possible traps for division, and this way
|
||||||
|
// we get the proper one if code traps.
|
||||||
|
flag_builder.enable("avoid_div_traps").unwrap();
|
||||||
|
|
||||||
|
if self.common.enable_simd {
|
||||||
|
flag_builder.enable("enable_simd").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.common.optimize {
|
||||||
|
flag_builder.set("opt_level", "speed").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let isa = isa_builder.finish(settings::Flags::new(flag_builder));
|
||||||
|
|
||||||
|
let mut obj = Artifact::new(isa.triple().clone(), self.output.clone());
|
||||||
|
|
||||||
|
// TODO: Expose the tunables as command-line flags.
|
||||||
|
let tunables = Tunables::default();
|
||||||
|
|
||||||
|
let (
|
||||||
|
module,
|
||||||
|
module_translation,
|
||||||
|
lazy_function_body_inputs,
|
||||||
|
lazy_data_initializers,
|
||||||
|
target_config,
|
||||||
|
) = {
|
||||||
|
let environ = ModuleEnvironment::new(isa.frontend_config(), tunables);
|
||||||
|
|
||||||
|
let translation = environ
|
||||||
|
.translate(&data)
|
||||||
|
.context("failed to translate module")?;
|
||||||
|
|
||||||
|
(
|
||||||
|
translation.module,
|
||||||
|
translation.module_translation.unwrap(),
|
||||||
|
translation.function_body_inputs,
|
||||||
|
translation.data_initializers,
|
||||||
|
translation.target_config,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: use the traps information
|
||||||
|
let (compilation, relocations, address_transform, value_ranges, stack_slots, _traps) =
|
||||||
|
match strategy {
|
||||||
|
Strategy::Auto | Strategy::Cranelift => Cranelift::compile_module(
|
||||||
|
&module,
|
||||||
|
&module_translation,
|
||||||
|
lazy_function_body_inputs,
|
||||||
|
&*isa,
|
||||||
|
self.common.debug_info,
|
||||||
|
),
|
||||||
|
#[cfg(feature = "lightbeam")]
|
||||||
|
Strategy::Lightbeam => Lightbeam::compile_module(
|
||||||
|
&module,
|
||||||
|
&module_translation,
|
||||||
|
lazy_function_body_inputs,
|
||||||
|
&*isa,
|
||||||
|
generate_debug_info,
|
||||||
|
),
|
||||||
|
#[cfg(not(feature = "lightbeam"))]
|
||||||
|
Strategy::Lightbeam => bail!("lightbeam support not enabled"),
|
||||||
|
other => bail!("unsupported compilation strategy {:?}", other),
|
||||||
|
}
|
||||||
|
.context("failed to compile module")?;
|
||||||
|
|
||||||
|
let module_vmctx_info = {
|
||||||
|
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module);
|
||||||
|
let memory_offset =
|
||||||
|
ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)) as i64;
|
||||||
|
ModuleVmctxInfo {
|
||||||
|
memory_offset,
|
||||||
|
stack_slots,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
emit_module(
|
||||||
|
&mut obj,
|
||||||
|
&module,
|
||||||
|
&compilation,
|
||||||
|
&relocations,
|
||||||
|
&lazy_data_initializers,
|
||||||
|
&target_config,
|
||||||
|
)
|
||||||
|
.map_err(|e| anyhow!(e))
|
||||||
|
.context("failed to emit module")?;
|
||||||
|
|
||||||
|
if self.common.debug_info {
|
||||||
|
let debug_data = read_debuginfo(&data);
|
||||||
|
emit_debugsections(
|
||||||
|
&mut obj,
|
||||||
|
&module_vmctx_info,
|
||||||
|
target_config,
|
||||||
|
&debug_data,
|
||||||
|
&address_transform,
|
||||||
|
&value_ranges,
|
||||||
|
)
|
||||||
|
.context("failed to emit debug sections")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Make the format a parameter.
|
||||||
|
let file = File::create(Path::new(&self.output)).context("failed to create object file")?;
|
||||||
|
obj.write(file).context("failed to write object file")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
83
src/commands/wast.rs
Normal file
83
src/commands/wast.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
//! The module that implements the `wasmtime wast` command.
|
||||||
|
|
||||||
|
use crate::{init_file_per_thread_logger, pick_compilation_strategy, CommonOptions};
|
||||||
|
use anyhow::{bail, Context as _, Result};
|
||||||
|
use std::{fmt::Write, path::PathBuf};
|
||||||
|
use structopt::{clap::AppSettings, StructOpt};
|
||||||
|
use wasmtime::{Config, Engine, HostRef, Store};
|
||||||
|
use wasmtime_environ::cache_init;
|
||||||
|
use wasmtime_wast::WastContext;
|
||||||
|
|
||||||
|
/// Runs a WebAssembly test script file
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
#[structopt(
|
||||||
|
name = "wast",
|
||||||
|
version = env!("CARGO_PKG_VERSION"),
|
||||||
|
setting = AppSettings::ColoredHelp,
|
||||||
|
)]
|
||||||
|
pub struct WastCommand {
|
||||||
|
#[structopt(flatten)]
|
||||||
|
common: CommonOptions,
|
||||||
|
|
||||||
|
/// The path of the WebAssembly test script to run
|
||||||
|
#[structopt(required = true, value_name = "SCRIPT_FILE", parse(from_os_str))]
|
||||||
|
scripts: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WastCommand {
|
||||||
|
/// Executes the command.
|
||||||
|
pub fn execute(&self) -> Result<()> {
|
||||||
|
let log_config = if self.common.debug {
|
||||||
|
pretty_env_logger::init();
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let prefix = "wast.dbg.";
|
||||||
|
init_file_per_thread_logger(prefix);
|
||||||
|
Some(prefix)
|
||||||
|
};
|
||||||
|
|
||||||
|
let errors = cache_init(
|
||||||
|
!self.common.disable_cache,
|
||||||
|
self.common.config.as_ref(),
|
||||||
|
log_config,
|
||||||
|
);
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
let mut message = String::new();
|
||||||
|
writeln!(message, "Cache initialization failed. Errors:")?;
|
||||||
|
for e in errors {
|
||||||
|
writeln!(message, " -> {}", e)?;
|
||||||
|
}
|
||||||
|
bail!(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut config = Config::new();
|
||||||
|
config
|
||||||
|
.cranelift_debug_verifier(cfg!(debug_assertions))
|
||||||
|
.debug_info(self.common.debug_info)
|
||||||
|
.wasm_simd(self.common.enable_simd)
|
||||||
|
.strategy(pick_compilation_strategy(
|
||||||
|
self.common.cranelift,
|
||||||
|
self.common.lightbeam,
|
||||||
|
)?)?;
|
||||||
|
|
||||||
|
if self.common.optimize {
|
||||||
|
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
let store = HostRef::new(Store::new(&Engine::new(&config)));
|
||||||
|
let mut wast_context = WastContext::new(store);
|
||||||
|
|
||||||
|
wast_context
|
||||||
|
.register_spectest()
|
||||||
|
.expect("error instantiating \"spectest\"");
|
||||||
|
|
||||||
|
for script in self.scripts.iter() {
|
||||||
|
wast_context
|
||||||
|
.run_file(script)
|
||||||
|
.with_context(|| format!("failed to run script file '{}'", script.display()))?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/lib.rs
70
src/lib.rs
@@ -1,7 +1,37 @@
|
|||||||
|
//! The Wasmtime command line interface (CLI) crate.
|
||||||
|
//!
|
||||||
|
//! This crate implements the Wasmtime command line tools.
|
||||||
|
|
||||||
|
#![deny(
|
||||||
|
missing_docs,
|
||||||
|
trivial_numeric_casts,
|
||||||
|
unused_extern_crates,
|
||||||
|
unstable_features
|
||||||
|
)]
|
||||||
|
#![warn(unused_import_braces)]
|
||||||
|
#![cfg_attr(feature = "clippy", plugin(clippy(conf_file = "../clippy.toml")))]
|
||||||
|
#![cfg_attr(feature = "cargo-clippy", allow(clippy::new_without_default))]
|
||||||
|
#![cfg_attr(
|
||||||
|
feature = "cargo-clippy",
|
||||||
|
warn(
|
||||||
|
clippy::float_arithmetic,
|
||||||
|
clippy::mut_mut,
|
||||||
|
clippy::nonminimal_bool,
|
||||||
|
clippy::option_map_unwrap_or,
|
||||||
|
clippy::option_map_unwrap_or_else,
|
||||||
|
clippy::unicode_not_nfc,
|
||||||
|
clippy::use_self
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
|
||||||
|
pub mod commands;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
use wasmtime::Strategy;
|
use wasmtime::Strategy;
|
||||||
|
|
||||||
pub fn pick_compilation_strategy(cranelift: bool, lightbeam: bool) -> Result<Strategy> {
|
fn pick_compilation_strategy(cranelift: bool, lightbeam: bool) -> Result<Strategy> {
|
||||||
Ok(match (lightbeam, cranelift) {
|
Ok(match (lightbeam, cranelift) {
|
||||||
(true, false) => Strategy::Lightbeam,
|
(true, false) => Strategy::Lightbeam,
|
||||||
(false, true) => Strategy::Cranelift,
|
(false, true) => Strategy::Cranelift,
|
||||||
@@ -10,7 +40,7 @@ pub fn pick_compilation_strategy(cranelift: bool, lightbeam: bool) -> Result<Str
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_file_per_thread_logger(prefix: &'static str) {
|
fn init_file_per_thread_logger(prefix: &'static str) {
|
||||||
file_per_thread_logger::initialize(prefix);
|
file_per_thread_logger::initialize(prefix);
|
||||||
|
|
||||||
// Extending behavior of default spawner:
|
// Extending behavior of default spawner:
|
||||||
@@ -35,3 +65,39 @@ pub fn init_file_per_thread_logger(prefix: &'static str) {
|
|||||||
.build_global()
|
.build_global()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Common options for commands that translate WebAssembly modules
|
||||||
|
#[derive(StructOpt)]
|
||||||
|
struct CommonOptions {
|
||||||
|
/// Use specified configuration file
|
||||||
|
#[structopt(long, parse(from_os_str), value_name = "CONFIG_PATH")]
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Use Cranelift for all compilation
|
||||||
|
#[structopt(long, conflicts_with = "lightbeam")]
|
||||||
|
cranelift: bool,
|
||||||
|
|
||||||
|
/// Enable debug output
|
||||||
|
#[structopt(short, long)]
|
||||||
|
debug: bool,
|
||||||
|
|
||||||
|
/// Generate debug information
|
||||||
|
#[structopt(short = "g")]
|
||||||
|
debug_info: bool,
|
||||||
|
|
||||||
|
/// Disable cache system
|
||||||
|
#[structopt(long)]
|
||||||
|
disable_cache: bool,
|
||||||
|
|
||||||
|
/// Enable support for proposed SIMD instructions
|
||||||
|
#[structopt(long)]
|
||||||
|
enable_simd: bool,
|
||||||
|
|
||||||
|
/// Use Lightbeam for all compilation
|
||||||
|
#[structopt(long, conflicts_with = "cranelift")]
|
||||||
|
lightbeam: bool,
|
||||||
|
|
||||||
|
/// Run optimization passes on translated functions
|
||||||
|
#[structopt(short = "O", long)]
|
||||||
|
optimize: bool,
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user