bashrs 6.66.0

Rust-to-Shell transpiler for deterministic bootstrap scripts
use crate::models::Result;

pub struct BinaryOptimizer {
    merge_strings: bool,
    gc_sections: bool,
    pack_rodata: bool,
}

impl Default for BinaryOptimizer {
    fn default() -> Self {
        Self {
            merge_strings: true,
            gc_sections: true,
            pack_rodata: true,
        }
    }
}

impl BinaryOptimizer {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn optimize(&self, elf_data: &mut Vec<u8>) -> Result<()> {
        if self.merge_strings {
            self.merge_duplicate_strings(elf_data.as_mut_slice())?;
        }

        if self.gc_sections {
            self.garbage_collect_sections(elf_data.as_mut_slice())?;
        }

        if self.pack_rodata {
            self.compress_rodata(elf_data.as_mut_slice())?;
        }

        Ok(())
    }

    fn merge_duplicate_strings(&self, _elf_data: &mut [u8]) -> Result<()> {
        // String merging deferred to v3.0+ binary compiler
        Ok(())
    }

    fn garbage_collect_sections(&self, _elf_data: &mut [u8]) -> Result<()> {
        // Dead code elimination deferred to v3.0+ binary compiler
        Ok(())
    }

    fn compress_rodata(&self, _elf_data: &mut [u8]) -> Result<()> {
        // Rodata compression deferred to v3.0+ binary compiler
        Ok(())
    }

    /// Estimate size reduction from optimizations
    pub fn estimate_size_reduction(&self, original_size: usize) -> usize {
        let mut reduction = 0;

        if self.merge_strings {
            reduction += original_size / 20; // ~5% from string merging
        }

        if self.gc_sections {
            reduction += original_size / 4; // ~25% from dead code elimination
        }

        if self.pack_rodata {
            reduction += original_size / 10; // ~10% from compression
        }

        reduction
    }
}

/// Compiler flags for size optimization
pub fn size_optimization_flags() -> Vec<&'static str> {
    vec![
        "-Os",                 // Optimize for size
        "-ffunction-sections", // Put each function in its own section
        "-fdata-sections",     // Put each data item in its own section
        "-fno-unwind-tables",  // No C++ exception tables
        "-fno-asynchronous-unwind-tables",
        "-fno-ident",            // No compiler version strings
        "-fomit-frame-pointer",  // Save stack space
        "-fmerge-all-constants", // Merge identical constants
        "-fno-exceptions",       // No C++ exceptions
        "-fno-rtti",             // No C++ RTTI
    ]
}

/// Linker flags for size optimization
pub fn size_linker_flags() -> Vec<&'static str> {
    vec![
        "-Wl,--gc-sections",    // Remove unused sections
        "-Wl,--as-needed",      // Only link needed libraries
        "-Wl,--strip-all",      // Strip all symbols
        "-Wl,-O2",              // Optimize linker output
        "-Wl,--hash-style=gnu", // Smaller hash tables
        "-Wl,--build-id=none",  // No build ID
        "-Wl,-z,norelro",       // No RELRO (saves ~4KB)
        "-static",              // Static linking
    ]
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_size_estimation() {
        let optimizer = BinaryOptimizer::new();
        let original = 1000000; // 1MB
        let reduction = optimizer.estimate_size_reduction(original);

        // Should estimate ~40% reduction
        assert!(reduction > 350000);
        assert!(reduction < 450000);
    }

    #[test]
    fn test_optimization_flags() {
        let flags = size_optimization_flags();
        assert!(flags.contains(&"-Os"));
        assert!(flags.contains(&"-ffunction-sections"));

        let linker_flags = size_linker_flags();
        assert!(linker_flags.contains(&"-Wl,--gc-sections"));
        assert!(linker_flags.contains(&"-static"));
    }

    #[test]
    fn test_binary_optimizer_default() {
        let optimizer = BinaryOptimizer::default();
        assert!(optimizer.merge_strings);
        assert!(optimizer.gc_sections);
        assert!(optimizer.pack_rodata);
    }

    #[test]
    fn test_binary_optimizer_new() {
        let optimizer = BinaryOptimizer::new();
        assert!(optimizer.merge_strings);
        assert!(optimizer.gc_sections);
        assert!(optimizer.pack_rodata);
    }

    #[test]
    fn test_optimize_empty_data() {
        let optimizer = BinaryOptimizer::new();
        let mut data = Vec::new();
        let result = optimizer.optimize(&mut data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_optimize_with_data() {
        let optimizer = BinaryOptimizer::new();
        let mut data = vec![0u8; 1000];
        let result = optimizer.optimize(&mut data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_merge_duplicate_strings() {
        let optimizer = BinaryOptimizer::new();
        let mut data = b"hello world hello".to_vec();
        let result = optimizer.merge_duplicate_strings(&mut data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_garbage_collect_sections() {
        let optimizer = BinaryOptimizer::new();
        let mut data = vec![0u8; 100];
        let result = optimizer.garbage_collect_sections(&mut data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_compress_rodata() {
        let optimizer = BinaryOptimizer::new();
        let mut data = vec![0u8; 100];
        let result = optimizer.compress_rodata(&mut data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_estimate_size_reduction_zero() {
        let optimizer = BinaryOptimizer::new();
        let reduction = optimizer.estimate_size_reduction(0);
        assert_eq!(reduction, 0);
    }

    #[test]
    fn test_estimate_size_reduction_small() {
        let optimizer = BinaryOptimizer::new();
        let reduction = optimizer.estimate_size_reduction(100);
        // 5% + 25% + 10% = 40% reduction
        // 100 / 20 + 100 / 4 + 100 / 10 = 5 + 25 + 10 = 40
        assert_eq!(reduction, 40);
    }

    #[test]
    fn test_size_optimization_flags_count() {
        let flags = size_optimization_flags();
        assert_eq!(flags.len(), 10);
    }

    #[test]
    fn test_size_optimization_flags_contains_all() {
        let flags = size_optimization_flags();
        assert!(flags.contains(&"-Os"));
        assert!(flags.contains(&"-ffunction-sections"));
        assert!(flags.contains(&"-fdata-sections"));
        assert!(flags.contains(&"-fno-unwind-tables"));
        assert!(flags.contains(&"-fno-asynchronous-unwind-tables"));
        assert!(flags.contains(&"-fno-ident"));
        assert!(flags.contains(&"-fomit-frame-pointer"));
        assert!(flags.contains(&"-fmerge-all-constants"));
        assert!(flags.contains(&"-fno-exceptions"));
        assert!(flags.contains(&"-fno-rtti"));
    }

    #[test]
    fn test_size_linker_flags_count() {
        let flags = size_linker_flags();
        assert_eq!(flags.len(), 8);
    }

    #[test]
    fn test_size_linker_flags_contains_all() {
        let flags = size_linker_flags();
        assert!(flags.contains(&"-Wl,--gc-sections"));
        assert!(flags.contains(&"-Wl,--as-needed"));
        assert!(flags.contains(&"-Wl,--strip-all"));
        assert!(flags.contains(&"-Wl,-O2"));
        assert!(flags.contains(&"-Wl,--hash-style=gnu"));
        assert!(flags.contains(&"-Wl,--build-id=none"));
        assert!(flags.contains(&"-Wl,-z,norelro"));
        assert!(flags.contains(&"-static"));
    }
}