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`).
206 lines
6.6 KiB
Rust
206 lines
6.6 KiB
Rust
//! 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(())
|
|
}
|
|
}
|