Skip to main content

cgp/profilers/
wasm.rs

1//! WASM SIMD128 profiling via wasmtime. Spec section 4.6.
2//! Uses wasmtime's fuel metering for deterministic instruction counting.
3
4use anyhow::Result;
5use std::process::Command;
6
7/// WASM profiling configuration.
8#[derive(Debug, Clone)]
9pub struct WasmProfilingConfig {
10    /// Enable fuel metering for instruction counting.
11    pub fuel_metering: bool,
12    /// Enable wasmtime's VTune/perf jitdump integration.
13    pub jitdump: bool,
14    /// Target runtime.
15    pub target: WasmTarget,
16}
17
18#[derive(Debug, Clone)]
19pub enum WasmTarget {
20    /// Profile via wasmtime CLI with --profile=jitdump
21    Wasmtime,
22    /// Profile via Chrome DevTools Protocol (headless browser)
23    Browser { cdp_url: String },
24}
25
26impl Default for WasmProfilingConfig {
27    fn default() -> Self {
28        Self {
29            fuel_metering: true,
30            jitdump: false,
31            target: WasmTarget::Wasmtime,
32        }
33    }
34}
35
36/// Check if a WASM module contains SIMD128 instructions.
37/// Searches for v128 type references in the wasm binary.
38pub fn detect_simd128(wasm_path: &str) -> bool {
39    // wasmtime can inspect with --invoke and fuel metering
40    // For a quick check, look for SIMD128 in wasm-tools dump or wasmtime output
41    let output = Command::new("wasm-tools")
42        .args(["dump", wasm_path])
43        .output()
44        .ok();
45
46    if let Some(out) = output {
47        let stdout = String::from_utf8_lossy(&out.stdout);
48        return stdout.contains("v128") || stdout.contains("simd");
49    }
50
51    // Fallback: check raw bytes for v128 type (0x7b in WASM)
52    std::fs::read(wasm_path).is_ok_and(|bytes| bytes.windows(1).any(|w| w[0] == 0x7b))
53}
54
55/// Profile a WASM function.
56pub fn profile_wasm(function: &str, size: u32) -> Result<()> {
57    println!("\n=== CGP WASM Profile: {function} (size={size}) ===\n");
58
59    let has_wasmtime = which::which("wasmtime").is_ok();
60    if !has_wasmtime {
61        println!("  wasmtime not found. Install wasmtime for WASM profiling.");
62        println!("  Install: curl https://wasmtime.dev/install.sh -sSf | bash");
63        return Ok(());
64    }
65
66    println!("  Backend: wasmtime (fuel metering + jitdump)");
67    println!("  Function: {function}");
68    println!("  Size: {size}");
69
70    // Try to find a WASM binary for trueno
71    let wasm_path = find_wasm_binary();
72    match wasm_path {
73        Some(path) => {
74            let has_simd = detect_simd128(&path);
75            if !has_simd {
76                println!(
77                    "  \x1b[33m[WARN]\x1b[0m No SIMD128 instructions detected -- scalar fallback"
78                );
79                println!("  Compile with: -Ctarget-feature=+simd128");
80            } else {
81                println!("  SIMD128: detected");
82            }
83
84            // Run with fuel metering for instruction counting
85            println!("  Running wasmtime with fuel metering...");
86            let output = Command::new("wasmtime")
87                .args(["run", "--fuel", "1000000000", &path])
88                .output();
89
90            match output {
91                Ok(out) => {
92                    if out.status.success() {
93                        println!("  Execution: success");
94                    } else {
95                        let stderr = String::from_utf8_lossy(&out.stderr);
96                        if stderr.contains("fuel") {
97                            println!("  Execution: ran out of fuel (>1B instructions)");
98                        } else {
99                            println!("  Execution: failed ({stderr})");
100                        }
101                    }
102                }
103                Err(e) => println!("  wasmtime failed: {e}"),
104            }
105        }
106        None => {
107            println!("  No WASM binary found.");
108            println!("  Build with: cargo build --target wasm32-unknown-unknown --release");
109            println!("  Or specify: cgp profile wasm --function <fn> --wasm-path <file>");
110        }
111    }
112
113    println!();
114    Ok(())
115}
116
117/// Find a trueno WASM binary.
118fn find_wasm_binary() -> Option<String> {
119    let target_dir = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "./target".to_string());
120    let candidates = [
121        format!("{target_dir}/wasm32-unknown-unknown/release/trueno.wasm"),
122        "./target/wasm32-unknown-unknown/release/trueno.wasm".to_string(),
123    ];
124    for path in &candidates {
125        if std::path::Path::new(path).exists() {
126            return Some(path.clone());
127        }
128    }
129    None
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_default_config() {
138        let config = WasmProfilingConfig::default();
139        assert!(config.fuel_metering);
140        assert!(!config.jitdump);
141        assert!(matches!(config.target, WasmTarget::Wasmtime));
142    }
143
144    /// FALSIFY-CGP-072: profile_wasm must not crash even without wasmtime.
145    #[test]
146    fn test_profile_wasm_no_panic() {
147        let result = profile_wasm("vector_dot_wasm", 1024);
148        assert!(result.is_ok());
149    }
150
151    /// FALSIFY-CGP-073: detect_simd128 should return false for non-existent files.
152    #[test]
153    fn test_detect_simd128_missing_file() {
154        assert!(!detect_simd128("/tmp/nonexistent_wasm_file.wasm"));
155    }
156}