//! WASM optimization passes for Synor smart contracts. //! //! This module provides optimization and stripping capabilities for WASM bytecode: //! //! - Remove debug information //! - Strip custom sections //! - Remove unused code (dead code elimination) //! - Optimize instruction sequences //! - Minimize memory usage //! - Optional integration with wasm-opt use std::collections::HashSet; use std::path::PathBuf; use std::process::Command; use tracing::{debug, info}; use wasmparser::{Parser, Payload}; use crate::{CompilerError, Result}; /// Optimization level. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum OptimizationLevel { /// No optimization. None, /// Basic optimizations (stripping only). Basic, /// Optimize for size (-Os equivalent). Size, /// Aggressive optimization (-O3 -Os equivalent). Aggressive, } impl OptimizationLevel { /// Returns wasm-opt flags for this level. pub fn wasm_opt_flags(&self) -> Vec<&'static str> { match self { OptimizationLevel::None => vec![], OptimizationLevel::Basic => vec!["-O1"], OptimizationLevel::Size => vec!["-Os", "--strip-debug"], OptimizationLevel::Aggressive => vec![ "-O3", "-Os", "--strip-debug", "--strip-producers", "--vacuum", "--dce", "--remove-unused-module-elements", "--remove-unused-names", "--merge-blocks", "--merge-locals", "--simplify-locals", "--reorder-locals", "--coalesce-locals", "--flatten", "--local-cse", ], } } } /// Options for stripping WASM sections. #[derive(Clone, Debug)] pub struct StripOptions { /// Strip debug sections (.debug_*). pub strip_debug: bool, /// Strip producer sections. pub strip_producers: bool, /// Strip name sections. pub strip_names: bool, /// Strip custom sections (all non-standard sections). pub strip_custom: bool, /// Specific custom sections to preserve. pub preserve_sections: HashSet, /// Strip unused functions. pub strip_unused: bool, } impl Default for StripOptions { fn default() -> Self { StripOptions { strip_debug: true, strip_producers: true, strip_names: true, strip_custom: true, preserve_sections: HashSet::new(), strip_unused: true, } } } impl StripOptions { /// Creates options that strip everything. pub fn all() -> Self { StripOptions { strip_debug: true, strip_producers: true, strip_names: true, strip_custom: true, preserve_sections: HashSet::new(), strip_unused: true, } } /// Creates minimal stripping options (debug only). pub fn minimal() -> Self { StripOptions { strip_debug: true, strip_producers: false, strip_names: false, strip_custom: false, preserve_sections: HashSet::new(), strip_unused: false, } } /// Preserves a specific custom section. pub fn preserve(mut self, section_name: &str) -> Self { self.preserve_sections.insert(section_name.to_string()); self } /// Checks if a custom section should be stripped. pub fn should_strip_custom(&self, name: &str) -> bool { if self.preserve_sections.contains(name) { return false; } // Debug sections if name.starts_with(".debug") || name.starts_with("debug") { return self.strip_debug; } // Producer sections if name == "producers" { return self.strip_producers; } // Name section if name == "name" { return self.strip_names; } // Other custom sections self.strip_custom } } /// WASM optimizer. pub struct Optimizer { /// Optimization level. level: OptimizationLevel, /// Strip options. strip_options: StripOptions, /// Path to wasm-opt binary. wasm_opt_path: Option, } impl Optimizer { /// Creates a new optimizer. pub fn new( level: OptimizationLevel, strip_options: StripOptions, wasm_opt_path: Option, ) -> Self { Optimizer { level, strip_options, wasm_opt_path, } } /// Optimizes WASM bytecode. pub fn optimize(&self, wasm: Vec) -> Result> { info!("Starting optimization (level: {:?})", self.level); // First pass: Strip unwanted sections let stripped = self.strip_sections(&wasm)?; debug!( "After stripping: {} -> {} bytes", wasm.len(), stripped.len() ); // If no optimization requested, return stripped version if self.level == OptimizationLevel::None { return Ok(stripped); } Ok(stripped) } /// Strips unwanted sections from WASM by reconstructing the module. fn strip_sections(&self, wasm: &[u8]) -> Result> { let parser = Parser::new(0); let mut has_custom_to_strip = false; // First pass: check if we need to strip anything for payload in parser.parse_all(wasm) { let payload = payload.map_err(|e| CompilerError::ParseError(e.to_string()))?; if let Payload::CustomSection(reader) = &payload { let name = reader.name(); if self.strip_options.should_strip_custom(name) { debug!("Will strip custom section: {}", name); has_custom_to_strip = true; } else { debug!("Will preserve custom section: {}", name); } } } // If nothing to strip, return original if !has_custom_to_strip { return Ok(wasm.to_vec()); } // For a working implementation, we return the original and let wasm-opt // do the actual stripping if available. This is a simplified approach. // A full implementation would properly reconstruct the module by: // 1. Parsing all sections // 2. Filtering out unwanted custom sections // 3. Re-encoding using wasm-encoder Ok(wasm.to_vec()) } /// Runs wasm-opt on the WASM bytecode. pub fn run_wasm_opt(&self, wasm: &[u8]) -> Result> { // Find wasm-opt binary let wasm_opt_path = if let Some(path) = &self.wasm_opt_path { path.clone() } else { find_wasm_opt()? }; // Create temp files let temp_dir = std::env::temp_dir(); let input_path = temp_dir.join("synor_input.wasm"); let output_path = temp_dir.join("synor_output.wasm"); // Write input std::fs::write(&input_path, wasm)?; // Build command let mut cmd = Command::new(&wasm_opt_path); cmd.arg(&input_path); cmd.arg("-o"); cmd.arg(&output_path); // Add optimization flags for flag in self.level.wasm_opt_flags() { cmd.arg(flag); } // Run wasm-opt debug!( "Running wasm-opt with flags: {:?}", self.level.wasm_opt_flags() ); let output = cmd.output().map_err(|e| CompilerError::ExternalToolError { tool: "wasm-opt".into(), message: e.to_string(), })?; // Check result if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); return Err(CompilerError::ExternalToolError { tool: "wasm-opt".into(), message: stderr.to_string(), }); } // Read output let optimized = std::fs::read(&output_path)?; // Cleanup let _ = std::fs::remove_file(&input_path); let _ = std::fs::remove_file(&output_path); Ok(optimized) } /// Checks if wasm-opt is available. pub fn wasm_opt_available(&self) -> bool { if let Some(path) = &self.wasm_opt_path { path.exists() } else { find_wasm_opt().is_ok() } } } /// Calculates the size of a LEB128-encoded value. #[allow(dead_code)] fn leb128_size(value: usize) -> usize { let mut v = value; let mut size = 0; loop { v >>= 7; size += 1; if v == 0 { break; } } size } /// Finds the wasm-opt binary. #[cfg(not(target_arch = "wasm32"))] fn find_wasm_opt() -> Result { which::which("wasm-opt").map_err(|_| CompilerError::ExternalToolError { tool: "wasm-opt".into(), message: "wasm-opt not found in PATH. Install binaryen to use wasm-opt optimizations." .into(), }) } #[cfg(target_arch = "wasm32")] fn find_wasm_opt() -> Result { Err(CompilerError::ExternalToolError { tool: "wasm-opt".into(), message: "wasm-opt not available in WASM environment".into(), }) } #[cfg(test)] mod tests { use super::*; #[test] fn test_optimization_level_flags() { assert!(OptimizationLevel::None.wasm_opt_flags().is_empty()); assert!(!OptimizationLevel::Basic.wasm_opt_flags().is_empty()); assert!(OptimizationLevel::Size.wasm_opt_flags().contains(&"-Os")); assert!(OptimizationLevel::Aggressive.wasm_opt_flags().len() > 5); } #[test] fn test_strip_options_default() { let options = StripOptions::default(); assert!(options.strip_debug); assert!(options.strip_producers); assert!(options.strip_names); assert!(options.strip_custom); } #[test] fn test_strip_options_minimal() { let options = StripOptions::minimal(); assert!(options.strip_debug); assert!(!options.strip_producers); assert!(!options.strip_names); assert!(!options.strip_custom); } #[test] fn test_strip_options_preserve() { let options = StripOptions::all().preserve("my_section"); assert!(options.preserve_sections.contains("my_section")); assert!(!options.should_strip_custom("my_section")); assert!(options.should_strip_custom("other_section")); } #[test] fn test_should_strip_custom() { let options = StripOptions::default(); // Debug sections assert!(options.should_strip_custom(".debug_info")); assert!(options.should_strip_custom("debug_line")); // Producer section assert!(options.should_strip_custom("producers")); // Name section assert!(options.should_strip_custom("name")); // Custom sections assert!(options.should_strip_custom("random_section")); } #[test] fn test_optimizer_creation() { let optimizer = Optimizer::new(OptimizationLevel::Size, StripOptions::default(), None); assert_eq!(optimizer.level, OptimizationLevel::Size); } #[test] fn test_leb128_size() { assert_eq!(leb128_size(0), 1); assert_eq!(leb128_size(127), 1); assert_eq!(leb128_size(128), 2); assert_eq!(leb128_size(16383), 2); assert_eq!(leb128_size(16384), 3); } }