use formalang::ir::IrModule;
use formalang::pipeline::Backend;
use thiserror::Error;
use crate::component::{self, ComponentWrapError};
use crate::module_lowering::{self, ModuleLowerError};
use crate::preflight::{self, PreflightError};
use crate::survey;
use crate::wit::{self, WitEmitError};
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct WasmBackend {
validate: bool,
}
impl WasmBackend {
#[must_use]
pub const fn new() -> Self {
Self { validate: false }
}
#[must_use]
pub const fn with_validation(mut self) -> Self {
self.validate = true;
self
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum WasmBackendError {
#[error(transparent)]
Preflight(#[from] PreflightError),
#[error(transparent)]
ModuleLower(#[from] ModuleLowerError),
#[error(transparent)]
WitEmit(#[from] WitEmitError),
#[error(transparent)]
ComponentWrap(#[from] ComponentWrapError),
#[cfg(feature = "wasm-opt")]
#[error("wasm-opt post-pass failed: {reason}")]
WasmOpt {
reason: String,
},
#[error("wasmparser rejected the emitted component: {reason}")]
Validation {
reason: String,
},
}
impl Backend for WasmBackend {
type Output = Vec<u8>;
type Error = WasmBackendError;
#[tracing::instrument(skip(self, module), fields(
functions = module.functions.len(),
structs = module.structs.len(),
enums = module.enums.len(),
impls = module.impls.len(),
))]
fn generate(&self, module: &IrModule) -> Result<Self::Output, Self::Error> {
tracing::debug!("preflight");
preflight::check(module)?;
tracing::debug!("survey");
let surface = survey::survey(module);
tracing::debug!("lower_module");
let core_bytes = module_lowering::lower_module(module)?;
tracing::debug!(core_bytes = core_bytes.len(), "core module emitted");
#[cfg(feature = "wasm-opt")]
let core_bytes = {
tracing::debug!("optimize_core_module");
optimize_core_module(&core_bytes)?
};
#[cfg(feature = "wasm-opt")]
tracing::debug!(core_bytes = core_bytes.len(), "post-wasm-opt size");
tracing::debug!("emit_wit");
let wit_text = wit::emit_wit(module, &surface)?;
tracing::debug!("wrap_component");
let bytes = component::wrap_component(core_bytes, &wit_text)?;
tracing::debug!(component_bytes = bytes.len(), "component wrapped");
if self.validate {
tracing::debug!("validate");
validate_component(&bytes)?;
}
Ok(bytes)
}
}
fn validate_component(bytes: &[u8]) -> Result<(), WasmBackendError> {
use wasmparser::{Validator, WasmFeatures};
let mut validator = Validator::new_with_features(WasmFeatures::default());
validator
.validate_all(bytes)
.map_err(|e| WasmBackendError::Validation {
reason: format!("{e}"),
})?;
Ok(())
}
#[cfg(feature = "wasm-opt")]
pub fn optimize_core_module(core_bytes: &[u8]) -> Result<Vec<u8>, WasmBackendError> {
use std::io::{Read as _, Write as _};
use wasm_opt::OptimizationOptions;
let dir = tempfile::tempdir().map_err(|e| WasmBackendError::WasmOpt {
reason: format!("tempdir: {e}"),
})?;
let in_path = dir.path().join("in.wasm");
let out_path = dir.path().join("out.wasm");
{
let mut f = std::fs::File::create(&in_path).map_err(|e| WasmBackendError::WasmOpt {
reason: format!("create input: {e}"),
})?;
f.write_all(core_bytes)
.map_err(|e| WasmBackendError::WasmOpt {
reason: format!("write input: {e}"),
})?;
}
OptimizationOptions::new_optimize_for_size()
.enable_feature(wasm_opt::Feature::All)
.run(&in_path, &out_path)
.map_err(|e| WasmBackendError::WasmOpt {
reason: format!("{e:#}"),
})?;
let mut optimized = Vec::new();
std::fs::File::open(&out_path)
.map_err(|e| WasmBackendError::WasmOpt {
reason: format!("open output: {e}"),
})?
.read_to_end(&mut optimized)
.map_err(|e| WasmBackendError::WasmOpt {
reason: format!("read output: {e}"),
})?;
Ok(optimized)
}