#![allow(dead_code, unused_imports, unused_variables)]
use std::path::PathBuf;
use std::string::ToString;
use std::sync::Arc;
use anyhow::{bail, Result};
#[cfg(feature = "sys")]
use wasmer::sys::*;
use wasmer::*;
use wasmer_types::{target::Target, Features};
#[cfg(feature = "compiler")]
use wasmer_compiler::CompilerConfig;
use wasmer::Engine;
#[derive(Debug, clap::Parser, Clone, Default)]
pub struct WasmFeatures {
#[clap(long = "enable-simd")]
pub simd: bool,
#[clap(long = "disable-threads")]
pub disable_threads: bool,
#[clap(long = "enable-threads")]
pub _threads: bool,
#[clap(long = "enable-reference-types")]
pub reference_types: bool,
#[clap(long = "enable-multi-value")]
pub multi_value: bool,
#[clap(long = "enable-bulk-memory")]
pub bulk_memory: bool,
#[clap(long = "enable-tail-call")]
pub tail_call: bool,
#[clap(long = "enable-module-linking")]
pub module_linking: bool,
#[clap(long = "enable-multi-memory")]
pub multi_memory: bool,
#[clap(long = "enable-memory64")]
pub memory64: bool,
#[clap(long = "enable-exceptions")]
pub exceptions: bool,
#[clap(long = "enable-relaxed-simd")]
pub relaxed_simd: bool,
#[clap(long = "enable-extended-const")]
pub extended_const: bool,
#[clap(long = "enable-all")]
pub all: bool,
}
#[derive(Debug, Clone, clap::Parser, Default)]
pub struct RuntimeOptions {
#[cfg(feature = "singlepass")]
#[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
#[cfg(feature = "llvm")]
"llvm",
#[cfg(feature = "v8")]
"v8",
#[cfg(feature = "cranelift")]
"cranelift",
#[cfg(feature = "wamr")]
"wamr",
#[cfg(feature = "wasmi")]
"wasmi"
]))]
singlepass: bool,
#[cfg(feature = "cranelift")]
#[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
#[cfg(feature = "llvm")]
"llvm",
#[cfg(feature = "v8")]
"v8",
#[cfg(feature = "singlepass")]
"singlepass",
#[cfg(feature = "wamr")]
"wamr",
#[cfg(feature = "wasmi")]
"wasmi"
]))]
cranelift: bool,
#[cfg(feature = "llvm")]
#[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
#[cfg(feature = "cranelift")]
"cranelift",
#[cfg(feature = "v8")]
"v8",
#[cfg(feature = "singlepass")]
"singlepass",
#[cfg(feature = "wamr")]
"wamr",
#[cfg(feature = "wasmi")]
"wasmi"
]))]
llvm: bool,
#[cfg(feature = "v8")]
#[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
#[cfg(feature = "cranelift")]
"cranelift",
#[cfg(feature = "llvm")]
"llvm",
#[cfg(feature = "singlepass")]
"singlepass",
#[cfg(feature = "wamr")]
"wamr",
#[cfg(feature = "wasmi")]
"wasmi"
]))]
v8: bool,
#[cfg(feature = "wamr")]
#[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
#[cfg(feature = "cranelift")]
"cranelift",
#[cfg(feature = "llvm")]
"llvm",
#[cfg(feature = "singlepass")]
"singlepass",
#[cfg(feature = "v8")]
"v8",
#[cfg(feature = "wasmi")]
"wasmi"
]))]
wamr: bool,
#[cfg(feature = "wasmi")]
#[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
#[cfg(feature = "cranelift")]
"cranelift",
#[cfg(feature = "llvm")]
"llvm",
#[cfg(feature = "singlepass")]
"singlepass",
#[cfg(feature = "v8")]
"v8",
#[cfg(feature = "wamr")]
"wamr"
]))]
wasmi: bool,
#[clap(long)]
enable_verifier: bool,
#[clap(long)]
llvm_debug_dir: Option<PathBuf>,
#[clap(flatten)]
features: WasmFeatures,
}
impl RuntimeOptions {
pub fn get_available_backends(&self) -> Result<Vec<BackendType>> {
#[cfg(feature = "cranelift")]
{
if self.cranelift {
return Ok(vec![BackendType::Cranelift]);
}
}
#[cfg(feature = "llvm")]
{
if self.llvm {
return Ok(vec![BackendType::LLVM]);
}
}
#[cfg(feature = "singlepass")]
{
if self.singlepass {
return Ok(vec![BackendType::Singlepass]);
}
}
#[cfg(feature = "wamr")]
{
if self.wamr {
return Ok(vec![BackendType::Wamr]);
}
}
#[cfg(feature = "v8")]
{
if self.v8 {
return Ok(vec![BackendType::V8]);
}
}
#[cfg(feature = "wasmi")]
{
if self.wasmi {
return Ok(vec![BackendType::Wasmi]);
}
}
Ok(BackendType::enabled())
}
pub fn filter_backends_by_features(
backends: Vec<BackendType>,
required_features: &Features,
target: &Target,
) -> Vec<BackendType> {
backends
.into_iter()
.filter(|backend| backend.supports_features(required_features, target))
.collect()
}
pub fn get_store(&self) -> Result<Store> {
let engine = self.get_engine(&Target::default())?;
Ok(Store::new(engine))
}
pub fn get_engine(&self, target: &Target) -> Result<Engine> {
let backends = self.get_available_backends()?;
let backend = backends.first().unwrap();
let backend_kind = wasmer::BackendKind::from(backend);
let required_features = wasmer::Engine::default_features_for_backend(&backend_kind, target);
backend.get_engine(target, &required_features)
}
pub fn get_engine_for_module(&self, module_contents: &[u8], target: &Target) -> Result<Engine> {
let required_features = self
.detect_features_from_wasm(module_contents)
.unwrap_or_default();
self.get_engine_for_features(&required_features, target)
}
pub fn get_engine_for_features(
&self,
required_features: &Features,
target: &Target,
) -> Result<Engine> {
let backends = self.get_available_backends()?;
let filtered_backends =
Self::filter_backends_by_features(backends.clone(), required_features, target);
if filtered_backends.is_empty() {
let enabled_backends = BackendType::enabled();
if backends.len() == 1 && enabled_backends.len() > 1 {
let filtered_backends =
Self::filter_backends_by_features(enabled_backends, required_features, target);
let extra_text: String = if !filtered_backends.is_empty() {
format!(". You can use --{} instead", filtered_backends[0])
} else {
"".to_string()
};
bail!(
"The {} backend does not support the required features for the Wasm module{}",
backends[0],
extra_text
);
} else {
bail!("No backends support the required features for the Wasm module. Feel free to open an issue at https://github.com/wasmerio/wasmer/issues");
}
}
filtered_backends
.first()
.unwrap()
.get_engine(target, required_features)
}
#[cfg(feature = "compiler")]
pub fn get_features(&self, features: &Features) -> Result<Features> {
let mut result = features.clone();
if !self.features.disable_threads || self.features.all {
result.threads(true);
}
if self.features.disable_threads && !self.features.all {
result.threads(false);
}
if self.features.multi_value || self.features.all {
result.multi_value(true);
}
if self.features.simd || self.features.all {
result.simd(true);
}
if self.features.bulk_memory || self.features.all {
result.bulk_memory(true);
}
if self.features.reference_types || self.features.all {
result.reference_types(true);
}
Ok(result)
}
#[cfg(feature = "compiler")]
pub fn get_configured_features(&self) -> Result<Features> {
let features = Features::default();
self.get_features(&features)
}
pub fn detect_features_from_wasm(
&self,
wasm_bytes: &[u8],
) -> Result<Features, wasmparser::BinaryReaderError> {
let mut features = Features::detect_from_wasm(wasm_bytes)?;
if !self.features.disable_threads || self.features.all {
features.threads(true);
}
if self.features.reference_types || self.features.all {
features.reference_types(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.multi_value || self.features.all {
features.multi_value(true);
}
if self.features.tail_call || self.features.all {
features.tail_call(true);
}
if self.features.module_linking || self.features.all {
features.module_linking(true);
}
if self.features.multi_memory || self.features.all {
features.multi_memory(true);
}
if self.features.memory64 || self.features.all {
features.memory64(true);
}
if self.features.exceptions || self.features.all {
features.exceptions(true);
}
Ok(features)
}
#[cfg(feature = "compiler")]
pub fn get_sys_compiler_engine_for_target(
&self,
target: Target,
) -> std::result::Result<Engine, anyhow::Error> {
let backends = self.get_available_backends()?;
let compiler_config = self.get_sys_compiler_config(backends.first().unwrap())?;
let default_features = compiler_config.default_features_for_target(&target);
let features = self.get_features(&default_features)?;
Ok(wasmer_compiler::EngineBuilder::new(compiler_config)
.set_features(Some(features))
.set_target(Some(target))
.engine()
.into())
}
#[allow(unused_variables)]
#[cfg(feature = "compiler")]
pub(crate) fn get_sys_compiler_config(
&self,
rt: &BackendType,
) -> Result<Box<dyn CompilerConfig>> {
let compiler_config: Box<dyn CompilerConfig> = match rt {
BackendType::Headless => bail!("The headless engine can't be chosen"),
#[cfg(feature = "singlepass")]
BackendType::Singlepass => {
let mut config = wasmer_compiler_singlepass::Singlepass::new();
if self.enable_verifier {
config.enable_verifier();
}
Box::new(config)
}
#[cfg(feature = "cranelift")]
BackendType::Cranelift => {
let mut config = wasmer_compiler_cranelift::Cranelift::new();
if self.enable_verifier {
config.enable_verifier();
}
Box::new(config)
}
#[cfg(feature = "llvm")]
BackendType::LLVM => {
use std::{fmt, fs::File, 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> {
std::fs::create_dir_all(&debug_dir)?;
Ok(Self { debug_dir })
}
}
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(),
Type::ExceptionRef => "x".to_string(),
})
.collect::<Vec<_>>()
.join("")
}
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)
}
BackendType::V8 | BackendType::Wamr | BackendType::Wasmi => unreachable!(),
#[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)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[allow(clippy::upper_case_acronyms, dead_code)]
pub enum BackendType {
Singlepass,
Cranelift,
LLVM,
V8,
Wamr,
Wasmi,
#[allow(dead_code)]
Headless,
}
impl BackendType {
pub fn enabled() -> Vec<Self> {
vec![
#[cfg(feature = "cranelift")]
Self::Cranelift,
#[cfg(feature = "llvm")]
Self::LLVM,
#[cfg(feature = "singlepass")]
Self::Singlepass,
#[cfg(feature = "v8")]
Self::V8,
#[cfg(feature = "wamr")]
Self::Wamr,
#[cfg(feature = "wasmi")]
Self::Wasmi,
]
}
pub fn get_engine(&self, target: &Target, features: &Features) -> Result<Engine> {
match self {
#[cfg(feature = "singlepass")]
Self::Singlepass => {
let config = wasmer_compiler_singlepass::Singlepass::new();
let engine = wasmer_compiler::EngineBuilder::new(config)
.set_features(Some(features.clone()))
.set_target(Some(target.clone()))
.engine()
.into();
Ok(engine)
}
#[cfg(feature = "cranelift")]
Self::Cranelift => {
let config = wasmer_compiler_cranelift::Cranelift::new();
let engine = wasmer_compiler::EngineBuilder::new(config)
.set_features(Some(features.clone()))
.set_target(Some(target.clone()))
.engine()
.into();
Ok(engine)
}
#[cfg(feature = "llvm")]
Self::LLVM => {
let config = wasmer_compiler_llvm::LLVM::new();
let engine = wasmer_compiler::EngineBuilder::new(config)
.set_features(Some(features.clone()))
.set_target(Some(target.clone()))
.engine()
.into();
Ok(engine)
}
#[cfg(feature = "v8")]
Self::V8 => Ok(wasmer::v8::V8::new().into()),
#[cfg(feature = "wamr")]
Self::Wamr => Ok(wasmer::wamr::Wamr::new().into()),
#[cfg(feature = "wasmi")]
Self::Wasmi => Ok(wasmer::wasmi::Wasmi::new().into()),
Self::Headless => bail!("Headless is not a valid runtime to instantiate directly"),
#[allow(unreachable_patterns)]
_ => bail!("Unsupported backend type"),
}
}
#[allow(unreachable_code)]
pub fn supports_features(&self, required_features: &Features, target: &Target) -> bool {
let backend_kind = match self {
#[cfg(feature = "singlepass")]
Self::Singlepass => wasmer::BackendKind::Singlepass,
#[cfg(feature = "cranelift")]
Self::Cranelift => wasmer::BackendKind::Cranelift,
#[cfg(feature = "llvm")]
Self::LLVM => wasmer::BackendKind::LLVM,
#[cfg(feature = "v8")]
Self::V8 => wasmer::BackendKind::V8,
#[cfg(feature = "wamr")]
Self::Wamr => wasmer::BackendKind::Wamr,
#[cfg(feature = "wasmi")]
Self::Wasmi => wasmer::BackendKind::Wasmi,
Self::Headless => return false, #[allow(unreachable_patterns)]
_ => return false,
};
let supported = wasmer::Engine::supported_features_for_backend(&backend_kind, target);
if !supported.contains_features(required_features) {
return false;
}
true
}
}
impl From<&BackendType> for wasmer::BackendKind {
fn from(backend_type: &BackendType) -> Self {
match backend_type {
#[cfg(feature = "singlepass")]
BackendType::Singlepass => wasmer::BackendKind::Singlepass,
#[cfg(feature = "cranelift")]
BackendType::Cranelift => wasmer::BackendKind::Cranelift,
#[cfg(feature = "llvm")]
BackendType::LLVM => wasmer::BackendKind::LLVM,
#[cfg(feature = "v8")]
BackendType::V8 => wasmer::BackendKind::V8,
#[cfg(feature = "wamr")]
BackendType::Wamr => wasmer::BackendKind::Wamr,
#[cfg(feature = "wasmi")]
BackendType::Wasmi => wasmer::BackendKind::Wasmi,
_ => {
#[cfg(feature = "sys")]
{
wasmer::BackendKind::Headless
}
#[cfg(not(feature = "sys"))]
{
unreachable!("No backend enabled!")
}
}
}
}
}
impl std::fmt::Display for BackendType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Singlepass => "singlepass",
Self::Cranelift => "cranelift",
Self::LLVM => "llvm",
Self::V8 => "v8",
Self::Wamr => "wamr",
Self::Wasmi => "wasmi",
Self::Headless => "headless",
}
)
}
}