use anyhow::Result;
use wasm_opt::{
Feature,
OptimizationOptions,
Pass,
};
use std::{
fmt,
path::PathBuf,
str,
};
pub struct WasmOptHandler {
optimization_level: OptimizationPasses,
keep_debug_symbols: bool,
}
impl WasmOptHandler {
pub fn new(
optimization_level: OptimizationPasses,
keep_debug_symbols: bool,
) -> Result<Self> {
Ok(Self {
optimization_level,
keep_debug_symbols,
})
}
pub fn optimize(&self, original_wasm: &PathBuf, dest_wasm: &PathBuf) -> Result<()> {
tracing::debug!(
"Optimization level passed to wasm-opt: {}",
self.optimization_level
);
OptimizationOptions::from(self.optimization_level)
.mvp_features_only()
.enable_feature(Feature::SignExt)
.add_pass(Pass::SignextLowering)
.zero_filled_memory(true)
.debug_info(self.keep_debug_symbols)
.run(original_wasm, dest_wasm)?;
if !dest_wasm.exists() {
return Err(anyhow::anyhow!(
"Optimization failed, optimized wasm output file `{}` not found.",
dest_wasm.display()
))
}
Ok(())
}
}
#[derive(
Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize,
)]
pub enum OptimizationPasses {
Zero,
One,
Two,
Three,
Four,
S,
#[default]
Z,
}
impl fmt::Display for OptimizationPasses {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let out = match self {
OptimizationPasses::Zero => "0",
OptimizationPasses::One => "1",
OptimizationPasses::Two => "2",
OptimizationPasses::Three => "3",
OptimizationPasses::Four => "4",
OptimizationPasses::S => "s",
OptimizationPasses::Z => "z",
};
write!(f, "{out}")
}
}
impl str::FromStr for OptimizationPasses {
type Err = anyhow::Error;
fn from_str(input: &str) -> std::result::Result<Self, Self::Err> {
let normalized_input = input.replace('"', "").to_lowercase();
match normalized_input.as_str() {
"0" => Ok(OptimizationPasses::Zero),
"1" => Ok(OptimizationPasses::One),
"2" => Ok(OptimizationPasses::Two),
"3" => Ok(OptimizationPasses::Three),
"4" => Ok(OptimizationPasses::Four),
"s" => Ok(OptimizationPasses::S),
"z" => Ok(OptimizationPasses::Z),
_ => anyhow::bail!("Unknown optimization passes for option {}", input),
}
}
}
impl From<String> for OptimizationPasses {
fn from(str: String) -> Self {
<OptimizationPasses as str::FromStr>::from_str(&str).expect("conversion failed")
}
}
impl From<OptimizationPasses> for OptimizationOptions {
fn from(passes: OptimizationPasses) -> OptimizationOptions {
match passes {
OptimizationPasses::Zero => OptimizationOptions::new_opt_level_0(),
OptimizationPasses::One => OptimizationOptions::new_opt_level_1(),
OptimizationPasses::Two => OptimizationOptions::new_opt_level_2(),
OptimizationPasses::Three => OptimizationOptions::new_opt_level_3(),
OptimizationPasses::Four => OptimizationOptions::new_opt_level_4(),
OptimizationPasses::S => OptimizationOptions::new_optimize_for_size(),
OptimizationPasses::Z => {
OptimizationOptions::new_optimize_for_size_aggressively()
}
}
}
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct OptimizationResult {
pub original_size: f64,
pub optimized_size: f64,
}