Created stand-alone wasmer-compiler, builded only as a .wasm target for now

Removed leftover trace of compiler feature in compiler-cli
Excluded the new wasmer-compiler-cli from lint test, like wasmer-cli it's based on
This commit is contained in:
ptitSeb
2022-04-21 16:35:20 +02:00
parent d8a7d9f987
commit 8010cb88e7
19 changed files with 1262 additions and 2 deletions

22
Cargo.lock generated
View File

@ -2906,6 +2906,28 @@ dependencies = [
"wasmparser 0.83.0", "wasmparser 0.83.0",
] ]
[[package]]
name = "wasmer-compiler-cli"
version = "2.2.1"
dependencies = [
"anyhow",
"atty",
"bytesize",
"cfg-if 1.0.0",
"colored 2.0.0",
"distance",
"fern",
"log",
"structopt",
"tempfile",
"unix_mode",
"wasmer-compiler",
"wasmer-compiler-singlepass",
"wasmer-engine-universal-artifact",
"wasmer-types",
"wasmer-vfs",
]
[[package]] [[package]]
name = "wasmer-compiler-cranelift" name = "wasmer-compiler-cranelift"
version = "2.2.1" version = "2.2.1"

View File

@ -34,6 +34,7 @@ members = [
"lib/cache", "lib/cache",
"lib/c-api", "lib/c-api",
"lib/cli", "lib/cli",
"lib/cli-compiler",
"lib/compiler", "lib/compiler",
"lib/compiler-cranelift", "lib/compiler-cranelift",
"lib/compiler-singlepass", "lib/compiler-singlepass",

View File

@ -151,7 +151,7 @@ ifneq ($(ENABLE_LLVM), 0)
endif endif
endif endif
exclude_tests := --exclude wasmer-c-api --exclude wasmer-cli exclude_tests := --exclude wasmer-c-api --exclude wasmer-cli --exclude wasmer-compiler-cli
# Is failing to compile in Linux for some reason # Is failing to compile in Linux for some reason
exclude_tests += --exclude wasmer-wasi-experimental-io-devices exclude_tests += --exclude wasmer-wasi-experimental-io-devices
# We run integration tests separately (it requires building the c-api) # We run integration tests separately (it requires building the c-api)
@ -379,6 +379,9 @@ build-wasmer-debug:
bench: bench:
cargo bench $(compiler_features) cargo bench $(compiler_features)
build-wasmer-wasm:
cargo build --release --manifest-path lib/cli-compiler/Cargo.toml --target wasm32-wasi --features singlepass,universal --bin wasmer-compiler
# For best results ensure the release profile looks like the following # For best results ensure the release profile looks like the following
# in Cargo.toml: # in Cargo.toml:
# [profile.release] # [profile.release]

View File

@ -0,0 +1,65 @@
[package]
name = "wasmer-compiler-cli"
version = "2.2.1"
description = "Wasmer Compiler CLI"
categories = ["wasm", "command-line-interface"]
keywords = ["wasm", "webassembly", "cli"]
authors = ["Wasmer Engineering Team <engineering@wasmer.io>"]
repository = "https://github.com/wasmerio/wasmer"
license = "MIT"
readme = "README.md"
edition = "2018"
default-run = "wasmer-compiler"
build = "build.rs"
[[bin]]
name = "wasmer-compiler"
path = "src/bin/wasmer_compiler.rs"
doc = false
[dependencies]
wasmer-engine-universal-artifact = { version = "=2.2.1", path = "../universal-artifact", features = ["compiler"] }
wasmer-compiler = { version = "=2.2.1", path = "../compiler" }
wasmer-types = { version = "=2.2.1", path = "../types" }
wasmer-vfs = { version = "=2.2.1", path = "../vfs", default-features = false, features = ["host-fs"] }
atty = "0.2"
colored = "2.0"
anyhow = "1.0"
structopt = { version = "0.3", features = ["suggestions"] }
# For the function names autosuggestion
distance = "0.4"
# For the inspect subcommand
bytesize = "1.0"
cfg-if = "1.0"
# For debug feature
fern = { version = "0.6", features = ["colored"], optional = true }
log = { version = "0.4", optional = true }
tempfile = "3"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
wasmer-compiler-singlepass = { version = "=2.2.1", path = "../compiler-singlepass", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasmer-compiler-singlepass = { version = "=2.2.1", path = "../compiler-singlepass", optional = true, default-features = false, features = ["wasm"] }
[target.'cfg(target_os = "linux")'.dependencies]
unix_mode = "0.1.3"
[features]
# Don't add the compiler features in default, please add them on the Makefile
# since we might want to autoconfigure them depending on the availability on the host.
default = [
"universal",
]
engine = []
universal = []
compiler = [
"wasmer-compiler/translator",
]
singlepass = [
"wasmer-compiler-singlepass",
"compiler",
]
debug = ["fern", "log"]
disable-all-logging = []
jit = ["universal"]

View File

@ -0,0 +1,34 @@
# `wasmer-cli-compiler` [![Build Status](https://github.com/wasmerio/wasmer/workflows/build/badge.svg?style=flat-square)](https://github.com/wasmerio/wasmer/actions?query=workflow%3Abuild) [![Join Wasmer Slack](https://img.shields.io/static/v1?label=Slack&message=join%20chat&color=brighgreen&style=flat-square)](https://slack.wasmer.io) [![MIT License](https://img.shields.io/github/license/wasmerio/wasmer.svg?style=flat-square)](https://github.com/wasmerio/wasmer/blob/master/LICENSE)
This crate is the Wasmer Compiler only CLI.
## Features
The Compiler only Wasmer supports the following features:
* `universal` (default): support for the [Universal engine].
* `wasi` (default): support for [WASI].
* `experimental-io-devices`: support for experimental IO devices in WASI.
* `emscripten` (default): support for [Emscripten].
* `singlepass`: support for the [Singlepass compiler].
[Universal engine]: https://github.com/wasmerio/wasmer/tree/master/lib/engine-universal/
[WASI]: https://github.com/wasmerio/wasmer/tree/master/lib/wasi/
[Emscripten]: https://github.com/wasmerio/wasmer/tree/master/lib/emscripten/
[Singlepass compiler]: https://github.com/wasmerio/wasmer/tree/master/lib/compiler-singlepass/
## CLI commands
Once you have Wasmer installed, you can start executing WebAssembly files easily:
Get the current Wasmer version:
```bash
wasmer-compiler -V
```
Compile a WebAssembly file:
```bash
wasmer-compiler compile myfile.wasm -o myfile.wasmu --singlepass --universal
```

View File

@ -0,0 +1,4 @@
pub fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=WASMER_INSTALL_PREFIX");
}

View File

@ -0,0 +1,13 @@
use wasmer_compiler_cli::cli::wasmer_main;
#[cfg(not(any(feature = "cranelift", feature = "singlepass", feature = "llvm")))]
compile_error!(
"Either enable at least one compiler, or compile the wasmer-headless binary instead"
);
#[cfg(featue = "run")]
compile_error!("Cannot enable run with the compile-only build");
fn main() {
wasmer_main();
}

View File

@ -0,0 +1,75 @@
//! The logic for the Wasmer CLI tool.
use crate::commands::Compile;
use crate::commands::{Config, Validate};
use crate::error::PrettyError;
use anyhow::Result;
use structopt::{clap::ErrorKind, StructOpt};
#[derive(StructOpt)]
#[structopt(
name = "wasmer-compiler",
about = "WebAssembly standalone Compiler.",
author
)]
/// The options for the wasmer Command Line Interface
enum WasmerCLIOptions {
/// Validate a WebAssembly binary
#[structopt(name = "validate")]
Validate(Validate),
/// Compile a WebAssembly binary
#[structopt(name = "compile")]
Compile(Compile),
/// Get various configuration information needed
/// to compile programs which use Wasmer
#[structopt(name = "config")]
Config(Config),
}
impl WasmerCLIOptions {
fn execute(&self) -> Result<()> {
match self {
Self::Validate(validate) => validate.execute(),
Self::Compile(compile) => compile.execute(),
Self::Config(config) => config.execute(),
}
}
}
/// The main function for the Wasmer CLI tool.
pub fn wasmer_main() {
// We allow windows to print properly colors
#[cfg(windows)]
colored::control::set_virtual_terminal(true).unwrap();
// We try to run wasmer with the normal arguments.
// Eg. `wasmer <SUBCOMMAND>`
// In case that fails, we fallback trying the Run subcommand directly.
// Eg. `wasmer myfile.wasm --dir=.`
//
// In case we've been run as wasmer-binfmt-interpreter myfile.wasm args,
// we assume that we're registered via binfmt_misc
let args = std::env::args().collect::<Vec<_>>();
let command = args.get(1);
let options = {
match command.unwrap_or(&"".to_string()).as_ref() {
"compile" | "config" | "help" | "inspect" | "validate" => WasmerCLIOptions::from_args(),
_ => {
WasmerCLIOptions::from_iter_safe(args.iter()).unwrap_or_else(|e| {
match e.kind {
// This fixes a issue that:
// 1. Shows the version twice when doing `wasmer -V`
// 2. Shows the run help (instead of normal help) when doing `wasmer --help`
ErrorKind::VersionDisplayed | ErrorKind::HelpDisplayed => e.exit(),
_ => WasmerCLIOptions::Compile(Compile::from_args()),
}
})
}
}
};
PrettyError::report(options.execute());
}

View File

@ -0,0 +1,7 @@
//! The commands available in the Wasmer binary.
mod compile;
mod config;
mod validate;
pub use compile::*;
pub use {config::*, validate::*};

View File

@ -0,0 +1,124 @@
use crate::store::{EngineType, StoreOptions};
use crate::warning;
use anyhow::{Context, Result};
use std::path::{Path, PathBuf};
use structopt::StructOpt;
use wasmer_compiler::{CompileError, CpuFeature, ModuleEnvironment, Target, Triple};
use wasmer_engine_universal_artifact::{ArtifactCreate, UniversalArtifactBuild};
use wasmer_types::entity::PrimaryMap;
use wasmer_types::{MemoryIndex, MemoryStyle, TableIndex, TableStyle};
#[derive(Debug, StructOpt)]
/// The options for the `wasmer compile` subcommand
pub struct Compile {
/// Input file
#[structopt(name = "FILE", parse(from_os_str))]
path: PathBuf,
/// Output file
#[structopt(name = "OUTPUT PATH", short = "o", parse(from_os_str))]
output: PathBuf,
/// Compilation Target triple
#[structopt(long = "target")]
target_triple: Option<Triple>,
#[structopt(flatten)]
store: StoreOptions,
#[structopt(short = "m", multiple = true, number_of_values = 1)]
cpu_features: Vec<CpuFeature>,
}
impl Compile {
/// Runs logic for the `compile` subcommand
pub fn execute(&self) -> Result<()> {
self.inner_execute()
.context(format!("failed to compile `{}`", self.path.display()))
}
pub(crate) fn get_recommend_extension(
engine_type: &EngineType,
target_triple: &Triple,
) -> Result<&'static str> {
Ok(match engine_type {
EngineType::Universal => {
wasmer_engine_universal_artifact::UniversalArtifactBuild::get_default_extension(
target_triple,
)
}
})
}
fn inner_execute(&self) -> Result<()> {
let target = self
.target_triple
.as_ref()
.map(|target_triple| {
let mut features = self
.cpu_features
.clone()
.into_iter()
.fold(CpuFeature::set(), |a, b| a | b);
// Cranelift requires SSE2, so we have this "hack" for now to facilitate
// usage
features |= CpuFeature::SSE2;
Target::new(target_triple.clone(), features)
})
.unwrap_or_default();
let (mut engine, engine_type, compiler_type) =
self.store.get_engine_for_target(target.clone())?;
let output_filename = self
.output
.file_stem()
.map(|osstr| osstr.to_string_lossy().to_string())
.unwrap_or_default();
let recommended_extension = Self::get_recommend_extension(&engine_type, target.triple())?;
match self.output.extension() {
Some(ext) => {
if ext != recommended_extension {
warning!("the output file has a wrong extension. We recommend using `{}.{}` for the chosen target", &output_filename, &recommended_extension)
}
}
None => {
warning!("the output file has no extension. We recommend using `{}.{}` for the chosen target", &output_filename, &recommended_extension)
}
}
let tunables = self.store.get_tunables_for_target(&target)?;
println!("Engine: {}", engine_type.to_string());
println!("Compiler: {}", compiler_type.to_string());
println!("Target: {}", target.triple());
// compile and save the artifact (without using module from api)
let path: &Path = self.path.as_ref();
let wasm_bytes = std::fs::read(path)?;
let environ = ModuleEnvironment::new();
let translation = environ.translate(&wasm_bytes).map_err(CompileError::Wasm)?;
let module = translation.module;
let memory_styles: PrimaryMap<MemoryIndex, MemoryStyle> = module
.memories
.values()
.map(|memory_type| tunables.memory_style(memory_type))
.collect();
let table_styles: PrimaryMap<TableIndex, TableStyle> = module
.tables
.values()
.map(|table_type| tunables.table_style(table_type))
.collect();
let artifact = UniversalArtifactBuild::new(
&mut engine,
&wasm_bytes,
&target,
memory_styles,
table_styles,
)?;
artifact.serialize_to_file(self.output.as_ref())?;
eprintln!(
"✔ File compiled successfully to `{}`.",
self.output.display(),
);
Ok(())
}
}

View File

@ -0,0 +1,102 @@
use crate::VERSION;
use anyhow::{Context, Result};
use std::env;
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
/// The options for the `wasmer config` subcommand
pub struct Config {
/// Print the installation prefix.
#[structopt(long, conflicts_with = "pkg-config")]
prefix: bool,
/// Directory containing Wasmer executables.
#[structopt(long, conflicts_with = "pkg-config")]
bindir: bool,
/// Directory containing Wasmer headers.
#[structopt(long, conflicts_with = "pkg-config")]
includedir: bool,
/// Directory containing Wasmer libraries.
#[structopt(long, conflicts_with = "pkg-config")]
libdir: bool,
/// Libraries needed to link against Wasmer components.
#[structopt(long, conflicts_with = "pkg-config")]
libs: bool,
/// C compiler flags for files that include Wasmer headers.
#[structopt(long, conflicts_with = "pkg-config")]
cflags: bool,
/// It outputs the necessary details for compiling
/// and linking a program to Wasmer, using the `pkg-config` format.
#[structopt(long)]
pkg_config: bool,
}
impl Config {
/// Runs logic for the `config` subcommand
pub fn execute(&self) -> Result<()> {
self.inner_execute()
.context("failed to retrieve the wasmer config".to_string())
}
fn inner_execute(&self) -> Result<()> {
let key = "WASMER_DIR";
let wasmer_dir = env::var(key)
.or_else(|e| {
option_env!("WASMER_INSTALL_PREFIX")
.map(str::to_string)
.ok_or(e)
})
.context(format!(
"failed to retrieve the {} environment variables",
key
))?;
let prefix = PathBuf::from(wasmer_dir);
let prefixdir = prefix.display().to_string();
let bindir = prefix.join("bin").display().to_string();
let includedir = prefix.join("include").display().to_string();
let libdir = prefix.join("lib").display().to_string();
let cflags = format!("-I{}", includedir);
let libs = format!("-L{} -lwasmer", libdir);
if self.pkg_config {
println!("prefix={}", prefixdir);
println!("exec_prefix={}", bindir);
println!("includedir={}", includedir);
println!("libdir={}", libdir);
println!();
println!("Name: wasmer");
println!("Description: The Wasmer library for running WebAssembly");
println!("Version: {}", VERSION);
println!("Cflags: {}", cflags);
println!("Libs: {}", libs);
return Ok(());
}
if self.prefix {
println!("{}", prefixdir);
}
if self.bindir {
println!("{}", bindir);
}
if self.includedir {
println!("{}", includedir);
}
if self.libdir {
println!("{}", libdir);
}
if self.libs {
println!("{}", libs);
}
if self.cflags {
println!("{}", cflags);
}
Ok(())
}
}

View File

@ -0,0 +1,40 @@
use crate::store::StoreOptions;
use anyhow::{bail, Context, Result};
use std::path::PathBuf;
use std::str::FromStr;
use structopt::StructOpt;
use wasmer_compiler::{CpuFeature, Target, Triple};
use wasmer_types::is_wasm;
#[derive(Debug, StructOpt)]
/// The options for the `wasmer validate` subcommand
pub struct Validate {
/// File to validate as WebAssembly
#[structopt(name = "FILE", parse(from_os_str))]
path: PathBuf,
#[structopt(flatten)]
store: StoreOptions,
}
impl Validate {
/// Runs logic for the `validate` subcommand
pub fn execute(&self) -> Result<()> {
self.inner_execute()
.context(format!("failed to validate `{}`", self.path.display()))
}
fn inner_execute(&self) -> Result<()> {
let target = Target::new(
Triple::from_str("x86_64-linux-gnu").unwrap(),
CpuFeature::SSE2 | CpuFeature::AVX,
);
let (engine, _engine_type, _compiler_type) = self.store.get_engine_for_target(target)?;
let module_contents = std::fs::read(&self.path)?;
if !is_wasm(&module_contents) {
bail!("`wasmer validate` only validates WebAssembly files");
}
engine.validate(&module_contents)?;
eprintln!("Validation passed for `{}`.", self.path.display());
Ok(())
}
}

View File

@ -0,0 +1,53 @@
//! Common module with common used structures across different
//! commands.
use crate::VERSION;
use std::env;
use std::path::PathBuf;
use structopt::StructOpt;
#[derive(Debug, StructOpt, Clone, Default)]
/// The WebAssembly features that can be passed through the
/// Command Line args.
pub struct WasmFeatures {
/// Enable support for the SIMD proposal.
#[structopt(long = "enable-simd")]
pub simd: bool,
/// Enable support for the threads proposal.
#[structopt(long = "enable-threads")]
pub threads: bool,
/// Enable support for the reference types proposal.
#[structopt(long = "enable-reference-types")]
pub reference_types: bool,
/// Enable support for the multi value proposal.
#[structopt(long = "enable-multi-value")]
pub multi_value: bool,
/// Enable support for the bulk memory proposal.
#[structopt(long = "enable-bulk-memory")]
pub bulk_memory: bool,
/// Enable support for all pre-standard proposals.
#[structopt(long = "enable-all")]
pub all: bool,
}
/// Get the cache dir
pub fn get_cache_dir() -> PathBuf {
match env::var("WASMER_CACHE_DIR") {
Ok(dir) => {
let mut path = PathBuf::from(dir);
path.push(VERSION);
path
}
Err(_) => {
// We use a temporal directory for saving cache files
let mut temp_dir = env::temp_dir();
temp_dir.push("wasmer");
temp_dir.push(VERSION);
temp_dir
}
}
}

View File

@ -0,0 +1,114 @@
//! Implements `PretyError` to print pretty errors in the CLI (when they happen)
use anyhow::{Chain, Error};
use colored::*;
use std::fmt::{self, Debug, Write};
/// A `PrettyError` for printing `anyhow::Error` nicely.
pub struct PrettyError {
error: Error,
}
/// A macro that prints a warning with nice colors
#[macro_export]
macro_rules! warning {
($($arg:tt)*) => ({
use colored::*;
eprintln!("{}: {}", "warning".yellow().bold(), format!($($arg)*));
})
}
impl PrettyError {
/// Process a `Result` printing any errors and exiting
/// the process after
pub fn report<T>(result: Result<T, Error>) -> ! {
std::process::exit(match result {
Ok(_t) => 0,
Err(error) => {
eprintln!("{:?}", PrettyError { error });
1
}
});
}
}
impl Debug for PrettyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let error = &self.error;
if f.alternate() {
return Debug::fmt(&error, f);
}
write!(f, "{}", format!("{}: {}", "error".red(), error).bold())?;
// write!(f, "{}", error)?;
if let Some(cause) = error.source() {
// write!(f, "\n{}:", "caused by".bold().blue())?;
let chain = Chain::new(cause);
let (total_errors, _) = chain.size_hint();
for (n, error) in chain.enumerate() {
writeln!(f)?;
let mut indented = Indented {
inner: f,
number: Some(n + 1),
is_last: n == total_errors - 1,
started: false,
};
write!(indented, "{}", error)?;
}
}
Ok(())
}
}
struct Indented<'a, D> {
inner: &'a mut D,
number: Option<usize>,
started: bool,
is_last: bool,
}
impl<T> Write for Indented<'_, T>
where
T: Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for (i, line) in s.split('\n').enumerate() {
if !self.started {
self.started = true;
match self.number {
Some(number) => {
if !self.is_last {
write!(
self.inner,
"{} {: >4} ",
"".bold().blue(),
format!("{}:", number).dimmed()
)?
} else {
write!(
self.inner,
"{}{: >2}: ",
"╰─▶".bold().blue(),
format!("{}", number).bold().blue()
)?
}
}
None => self.inner.write_str(" ")?,
}
} else if i > 0 {
self.inner.write_char('\n')?;
if self.number.is_some() {
self.inner.write_str(" ")?;
} else {
self.inner.write_str(" ")?;
}
}
self.inner.write_str(line)?;
}
Ok(())
}
}

View File

@ -0,0 +1,30 @@
//! The Wasmer binary lib
#![deny(
missing_docs,
dead_code,
nonstandard_style,
unused_mut,
unused_variables,
unused_unsafe,
unreachable_patterns,
unstable_features
)]
#![doc(html_favicon_url = "https://wasmer.io/images/icons/favicon-32x32.png")]
#![doc(html_logo_url = "https://github.com/wasmerio.png?size=200")]
#[macro_use]
extern crate anyhow;
pub mod commands;
pub mod common;
#[macro_use]
pub mod error;
pub mod cli;
#[cfg(feature = "debug")]
pub mod logging;
pub mod store;
pub mod utils;
/// Version number of this crate.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

View File

@ -0,0 +1,68 @@
//! Logging functions for the debug feature.
use crate::utils::wasmer_should_print_color;
use anyhow::Result;
use fern::colors::{Color, ColoredLevelConfig};
use std::time;
/// The debug level
pub type DebugLevel = log::LevelFilter;
/// Subroutine to instantiate the loggers
pub fn set_up_logging(verbose: u8) -> Result<(), String> {
let colors_line = ColoredLevelConfig::new()
.error(Color::Red)
.warn(Color::Yellow)
.trace(Color::BrightBlack);
let should_color = wasmer_should_print_color();
let colors_level = colors_line.info(Color::Green);
let level = match verbose {
1 => DebugLevel::Debug,
_ => DebugLevel::Trace,
};
let dispatch = fern::Dispatch::new()
.level(level)
.chain({
let base = if should_color {
fern::Dispatch::new().format(move |out, message, record| {
let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH).expect("Can't get time");
out.finish(format_args!(
"{color_line}[{seconds}.{millis} {level} {target}{color_line}]{ansi_close} {message}",
color_line = format_args!(
"\x1B[{}m",
colors_line.get_color(&record.level()).to_fg_str()
),
seconds = time.as_secs(),
millis = time.subsec_millis(),
level = colors_level.color(record.level()),
target = record.target(),
ansi_close = "\x1B[0m",
message = message,
));
})
} else {
// default formatter without color
fern::Dispatch::new().format(move |out, message, record| {
let time = time::SystemTime::now().duration_since(time::UNIX_EPOCH).expect("Can't get time");
out.finish(format_args!(
"[{seconds}.{millis} {level} {target}] {message}",
seconds = time.as_secs(),
millis = time.subsec_millis(),
level = record.level(),
target = record.target(),
message = message,
));
})
};
base
.filter(|metadata| {
metadata.target().starts_with("wasmer")
})
.chain(std::io::stdout())
});
dispatch.apply().map_err(|e| format!("{}", e))?;
Ok(())
}

View File

@ -0,0 +1,410 @@
//! Common module with common used structures across different
//! commands.
use crate::common::WasmFeatures;
use anyhow::Result;
use std::string::ToString;
#[allow(unused_imports)]
use std::sync::Arc;
use structopt::StructOpt;
use wasmer_compiler::{CompilerConfig, Features, PointerWidth, Target};
use wasmer_engine_universal_artifact::UniversalEngineBuilder;
use wasmer_types::{MemoryStyle, MemoryType, Pages, TableStyle, TableType};
/// Minimul Subset of Tunable parameters for WebAssembly compilation.
#[derive(Clone)]
pub struct SubsetTunables {
/// For static heaps, the size in wasm pages of the heap protected by bounds checking.
pub static_memory_bound: Pages,
/// The size in bytes of the offset guard for static heaps.
pub static_memory_offset_guard_size: u64,
/// The size in bytes of the offset guard for dynamic heaps.
pub dynamic_memory_offset_guard_size: u64,
}
impl SubsetTunables {
/// Get the `BaseTunables` for a specific Target
pub fn for_target(target: &Target) -> Self {
let triple = target.triple();
let pointer_width: PointerWidth = triple.pointer_width().unwrap();
let (static_memory_bound, static_memory_offset_guard_size): (Pages, u64) =
match pointer_width {
PointerWidth::U16 => (0x400.into(), 0x1000),
PointerWidth::U32 => (0x4000.into(), 0x1_0000),
// Static Memory Bound:
// Allocating 4 GiB of address space let us avoid the
// need for explicit bounds checks.
// Static Memory Guard size:
// Allocating 2 GiB of address space lets us translate wasm
// offsets into x86 offsets as aggressively as we can.
PointerWidth::U64 => (0x1_0000.into(), 0x8000_0000),
};
// Allocate a small guard to optimize common cases but without
// wasting too much memory.
// The Windows memory manager seems more laxed than the other ones
// And a guard of just 1 page may not be enough is some borderline cases
// So using 2 pages for guard on this platform
#[cfg(target_os = "windows")]
let dynamic_memory_offset_guard_size: u64 = 0x2_0000;
#[cfg(not(target_os = "windows"))]
let dynamic_memory_offset_guard_size: u64 = 0x1_0000;
Self {
static_memory_bound,
static_memory_offset_guard_size,
dynamic_memory_offset_guard_size,
}
}
/// Get a `MemoryStyle` for the provided `MemoryType`
pub fn memory_style(&self, memory: &MemoryType) -> MemoryStyle {
// A heap with a maximum that doesn't exceed the static memory bound specified by the
// tunables make it static.
//
// If the module doesn't declare an explicit maximum treat it as 4GiB.
let maximum = memory.maximum.unwrap_or_else(Pages::max_value);
if maximum <= self.static_memory_bound {
MemoryStyle::Static {
// Bound can be larger than the maximum for performance reasons
bound: self.static_memory_bound,
offset_guard_size: self.static_memory_offset_guard_size,
}
} else {
MemoryStyle::Dynamic {
offset_guard_size: self.dynamic_memory_offset_guard_size,
}
}
}
/// Get a [`TableStyle`] for the provided [`TableType`].
pub fn table_style(&self, _table: &TableType) -> TableStyle {
TableStyle::CallerChecksSignature
}
}
#[derive(Debug, Clone, StructOpt, Default)]
/// The compiler and engine options
pub struct StoreOptions {
#[structopt(flatten)]
compiler: CompilerOptions,
}
#[derive(Debug, Clone, StructOpt, Default)]
/// The compiler options
pub struct CompilerOptions {
/// Use Singlepass compiler.
#[structopt(long, conflicts_with_all = &["cranelift", "llvm"])]
singlepass: bool,
/// Use Cranelift compiler.
#[structopt(long, conflicts_with_all = &["singlepass", "llvm"])]
cranelift: bool,
/// Use LLVM compiler.
#[structopt(long, conflicts_with_all = &["singlepass", "cranelift"])]
llvm: bool,
/// Enable compiler internal verification.
#[structopt(long)]
enable_verifier: bool,
/// LLVM debug directory, where IR and object files will be written to.
#[cfg(feature = "llvm")]
#[structopt(long, parse(from_os_str))]
llvm_debug_dir: Option<PathBuf>,
#[structopt(flatten)]
features: WasmFeatures,
}
impl CompilerOptions {
fn get_compiler(&self) -> Result<CompilerType> {
if self.cranelift {
Ok(CompilerType::Cranelift)
} else if self.llvm {
Ok(CompilerType::LLVM)
} else if self.singlepass {
Ok(CompilerType::Singlepass)
} else {
// Auto mode, we choose the best compiler for that platform
cfg_if::cfg_if! {
if #[cfg(all(feature = "cranelift", any(target_arch = "x86_64", target_arch = "aarch64")))] {
Ok(CompilerType::Cranelift)
}
else if #[cfg(all(feature = "singlepass", target_arch = "x86_64"))] {
Ok(CompilerType::Singlepass)
}
else if #[cfg(feature = "llvm")] {
Ok(CompilerType::LLVM)
} else {
bail!("There are no available compilers for your architecture");
}
}
}
}
/// Get the enaled Wasm features.
pub fn get_features(&self, mut features: Features) -> Result<Features> {
if self.features.threads || self.features.all {
features.threads(true);
}
if self.features.multi_value || self.features.all {
features.multi_value(true);
}
if self.features.simd || self.features.all {
features.simd(true);
}
if self.features.bulk_memory || self.features.all {
features.bulk_memory(true);
}
if self.features.reference_types || self.features.all {
features.reference_types(true);
}
Ok(features)
}
fn get_engine_by_type(
&self,
target: Target,
compiler_config: Box<dyn CompilerConfig>,
engine_type: EngineType,
) -> Result<UniversalEngineBuilder> {
let features = self.get_features(compiler_config.default_features_for_target(&target))?;
let engine: UniversalEngineBuilder = match engine_type {
EngineType::Universal => {
UniversalEngineBuilder::new(Some(compiler_config.compiler()), features)
}
};
Ok(engine)
}
/// Get the Compiler Config for the current options
#[allow(unused_variables)]
pub(crate) fn get_compiler_config(&self) -> Result<(Box<dyn CompilerConfig>, CompilerType)> {
let compiler = self.get_compiler()?;
let compiler_config: Box<dyn CompilerConfig> = match compiler {
CompilerType::Headless => bail!("The headless engine can't be chosen"),
#[cfg(feature = "singlepass")]
CompilerType::Singlepass => {
let mut config = wasmer_compiler_singlepass::Singlepass::new();
if self.enable_verifier {
config.enable_verifier();
}
Box::new(config)
}
#[cfg(feature = "cranelift")]
CompilerType::Cranelift => {
let mut config = wasmer_compiler_cranelift::Cranelift::new();
if self.enable_verifier {
config.enable_verifier();
}
Box::new(config)
}
#[cfg(feature = "llvm")]
CompilerType::LLVM => {
use std::fmt;
use std::fs::File;
use std::io::Write;
use wasmer_compiler_llvm::{
CompiledKind, InkwellMemoryBuffer, InkwellModule, LLVMCallbacks, LLVM,
};
use wasmer_types::entity::EntityRef;
let mut config = LLVM::new();
struct Callbacks {
debug_dir: PathBuf,
}
impl Callbacks {
fn new(debug_dir: PathBuf) -> Result<Self> {
// Create the debug dir in case it doesn't exist
std::fs::create_dir_all(&debug_dir)?;
Ok(Self { debug_dir })
}
}
// Converts a kind into a filename, that we will use to dump
// the contents of the IR object file to.
fn types_to_signature(types: &[Type]) -> String {
types
.iter()
.map(|ty| match ty {
Type::I32 => "i".to_string(),
Type::I64 => "I".to_string(),
Type::F32 => "f".to_string(),
Type::F64 => "F".to_string(),
Type::V128 => "v".to_string(),
Type::ExternRef => "e".to_string(),
Type::FuncRef => "r".to_string(),
})
.collect::<Vec<_>>()
.join("")
}
// Converts a kind into a filename, that we will use to dump
// the contents of the IR object file to.
fn function_kind_to_filename(kind: &CompiledKind) -> String {
match kind {
CompiledKind::Local(local_index) => {
format!("function_{}", local_index.index())
}
CompiledKind::FunctionCallTrampoline(func_type) => format!(
"trampoline_call_{}_{}",
types_to_signature(&func_type.params()),
types_to_signature(&func_type.results())
),
CompiledKind::DynamicFunctionTrampoline(func_type) => format!(
"trampoline_dynamic_{}_{}",
types_to_signature(&func_type.params()),
types_to_signature(&func_type.results())
),
CompiledKind::Module => "module".into(),
}
}
impl LLVMCallbacks for Callbacks {
fn preopt_ir(&self, kind: &CompiledKind, module: &InkwellModule) {
let mut path = self.debug_dir.clone();
path.push(format!("{}.preopt.ll", function_kind_to_filename(kind)));
module
.print_to_file(&path)
.expect("Error while dumping pre optimized LLVM IR");
}
fn postopt_ir(&self, kind: &CompiledKind, module: &InkwellModule) {
let mut path = self.debug_dir.clone();
path.push(format!("{}.postopt.ll", function_kind_to_filename(kind)));
module
.print_to_file(&path)
.expect("Error while dumping post optimized LLVM IR");
}
fn obj_memory_buffer(
&self,
kind: &CompiledKind,
memory_buffer: &InkwellMemoryBuffer,
) {
let mut path = self.debug_dir.clone();
path.push(format!("{}.o", function_kind_to_filename(kind)));
let mem_buf_slice = memory_buffer.as_slice();
let mut file = File::create(path)
.expect("Error while creating debug object file from LLVM IR");
let mut pos = 0;
while pos < mem_buf_slice.len() {
pos += file.write(&mem_buf_slice[pos..]).unwrap();
}
}
}
impl fmt::Debug for Callbacks {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "LLVMCallbacks")
}
}
if let Some(ref llvm_debug_dir) = self.llvm_debug_dir {
config.callbacks(Some(Arc::new(Callbacks::new(llvm_debug_dir.clone())?)));
}
if self.enable_verifier {
config.enable_verifier();
}
Box::new(config)
}
#[cfg(not(all(feature = "singlepass", feature = "cranelift", feature = "llvm",)))]
compiler => {
bail!(
"The `{}` compiler is not included in this binary.",
compiler.to_string()
)
}
};
#[allow(unreachable_code)]
Ok((compiler_config, compiler))
}
}
/// The compiler used for the store
#[derive(Debug, PartialEq, Eq)]
pub enum CompilerType {
/// Singlepass compiler
Singlepass,
/// Cranelift compiler
Cranelift,
/// LLVM compiler
LLVM,
/// Headless compiler
Headless,
}
impl CompilerType {
/// Return all enabled compilers
pub fn enabled() -> Vec<CompilerType> {
vec![
#[cfg(feature = "singlepass")]
Self::Singlepass,
#[cfg(feature = "cranelift")]
Self::Cranelift,
#[cfg(feature = "llvm")]
Self::LLVM,
]
}
}
impl ToString for CompilerType {
fn to_string(&self) -> String {
match self {
Self::Singlepass => "singlepass".to_string(),
Self::Cranelift => "cranelift".to_string(),
Self::LLVM => "llvm".to_string(),
Self::Headless => "headless".to_string(),
}
}
}
/// The engine used for the store
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum EngineType {
/// Universal Engine
Universal,
}
impl ToString for EngineType {
fn to_string(&self) -> String {
match self {
Self::Universal => "universal".to_string(),
}
}
}
impl StoreOptions {
/// Get a UniversalEngineBulder for the Target
pub fn get_engine_for_target(
&self,
target: Target,
) -> Result<(UniversalEngineBuilder, EngineType, CompilerType)> {
let (compiler_config, compiler_type) = self.compiler.get_compiler_config()?;
let (engine, engine_type) = self.get_engine_with_compiler(target, compiler_config)?;
Ok((engine, engine_type, compiler_type))
}
/// Get default EngineType
pub fn get_engine(&self) -> Result<EngineType> {
Ok(EngineType::Universal)
}
fn get_engine_with_compiler(
&self,
target: Target,
compiler_config: Box<dyn CompilerConfig>,
) -> Result<(UniversalEngineBuilder, EngineType)> {
let engine_type = self.get_engine()?;
let engine = self
.compiler
.get_engine_by_type(target, compiler_config, engine_type)?;
Ok((engine, engine_type))
}
/// Get (Subset)Tunables for the Target
pub fn get_tunables_for_target(&self, target: &Target) -> Result<SubsetTunables> {
let tunables = SubsetTunables::for_target(target);
Ok(tunables)
}
}

View File

@ -0,0 +1,92 @@
//! Utility functions for the WebAssembly module
use anyhow::{bail, Result};
use std::env;
use std::path::PathBuf;
/// Whether or not Wasmer should print with color
pub fn wasmer_should_print_color() -> bool {
env::var("WASMER_COLOR")
.ok()
.and_then(|inner| inner.parse::<bool>().ok())
.unwrap_or_else(|| atty::is(atty::Stream::Stdout))
}
fn retrieve_alias_pathbuf(alias: &str, real_dir: &str) -> Result<(String, PathBuf)> {
let pb = PathBuf::from(&real_dir);
if let Ok(pb_metadata) = pb.metadata() {
if !pb_metadata.is_dir() {
bail!("\"{}\" exists, but it is not a directory", &real_dir);
}
} else {
bail!("Directory \"{}\" does not exist", &real_dir);
}
Ok((alias.to_string(), pb))
}
/// Parses a mapdir from a string
pub fn parse_mapdir(entry: &str) -> Result<(String, PathBuf)> {
// We try first splitting by `::`
if let [alias, real_dir] = entry.split("::").collect::<Vec<&str>>()[..] {
retrieve_alias_pathbuf(alias, real_dir)
}
// And then we try splitting by `:` (for compatibility with previous API)
else if let [alias, real_dir] = entry.split(':').collect::<Vec<&str>>()[..] {
retrieve_alias_pathbuf(alias, real_dir)
} else {
bail!(
"Directory mappings must consist of two paths separate by a `::` or `:`. Found {}",
&entry
)
}
}
/// Parses an environment variable.
pub fn parse_envvar(entry: &str) -> Result<(String, String)> {
let entry = entry.trim();
match entry.find('=') {
None => bail!(
"Environment variable must be of the form `<name>=<value>`; found `{}`",
&entry
),
Some(0) => bail!(
"Environment variable is not well formed, the `name` is missing in `<name>=<value>`; got `{}`",
&entry
),
Some(position) if position == entry.len() - 1 => bail!(
"Environment variable is not well formed, the `value` is missing in `<name>=<value>`; got `{}`",
&entry
),
Some(position) => Ok((entry[..position].into(), entry[position + 1..].into())),
}
}
#[cfg(test)]
mod tests {
use super::parse_envvar;
#[test]
fn test_parse_envvar() {
assert_eq!(
parse_envvar("A").unwrap_err().to_string(),
"Environment variable must be of the form `<name>=<value>`; found `A`"
);
assert_eq!(
parse_envvar("=A").unwrap_err().to_string(),
"Environment variable is not well formed, the `name` is missing in `<name>=<value>`; got `=A`"
);
assert_eq!(
parse_envvar("A=").unwrap_err().to_string(),
"Environment variable is not well formed, the `value` is missing in `<name>=<value>`; got `A=`"
);
assert_eq!(parse_envvar("A=B").unwrap(), ("A".into(), "B".into()));
assert_eq!(parse_envvar(" A=B\t").unwrap(), ("A".into(), "B".into()));
assert_eq!(
parse_envvar("A=B=C=D").unwrap(),
("A".into(), "B=C=D".into())
);
}
}

View File

@ -14,7 +14,6 @@ edition = "2018"
[dependencies] [dependencies]
wasmer-compiler = { path = "../compiler", version = "=2.2.1", features = ["translator"], default-features = false } wasmer-compiler = { path = "../compiler", version = "=2.2.1", features = ["translator"], default-features = false }
wasmer-types = { path = "../types", version = "=2.2.1", default-features = false, features = ["std"] } wasmer-types = { path = "../types", version = "=2.2.1", default-features = false, features = ["std"] }
rayon = { version = "1.5", optional = true }
hashbrown = { version = "0.11", optional = true } hashbrown = { version = "0.11", optional = true }
gimli = { version = "0.26", optional = true } gimli = { version = "0.26", optional = true }
more-asserts = "0.2" more-asserts = "0.2"
@ -25,6 +24,9 @@ byteorder = "1.3"
smallvec = "1.6" smallvec = "1.6"
loupe = "0.1" loupe = "0.1"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rayon = { version = "1.5", optional = true }
[dev-dependencies] [dev-dependencies]
target-lexicon = { version = "0.12.2", default-features = false } target-lexicon = { version = "0.12.2", default-features = false }
@ -33,6 +35,7 @@ maintenance = { status = "actively-developed" }
[features] [features]
default = ["std", "rayon", "unwind", "avx"] default = ["std", "rayon", "unwind", "avx"]
wasm = ["std", "avx"]
std = ["wasmer-compiler/std", "wasmer-types/std"] std = ["wasmer-compiler/std", "wasmer-types/std"]
core = ["hashbrown", "wasmer-types/core"] core = ["hashbrown", "wasmer-types/core"]
unwind = ["gimli"] unwind = ["gimli"]