1use anyhow::Result;
5use std::process::Command;
6
7#[derive(Debug, Clone)]
9pub struct WasmProfilingConfig {
10 pub fuel_metering: bool,
12 pub jitdump: bool,
14 pub target: WasmTarget,
16}
17
18#[derive(Debug, Clone)]
19pub enum WasmTarget {
20 Wasmtime,
22 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
36pub fn detect_simd128(wasm_path: &str) -> bool {
39 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 std::fs::read(wasm_path).is_ok_and(|bytes| bytes.windows(1).any(|w| w[0] == 0x7b))
53}
54
55pub 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 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 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
117fn 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 #[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 #[test]
153 fn test_detect_simd128_missing_file() {
154 assert!(!detect_simd128("/tmp/nonexistent_wasm_file.wasm"));
155 }
156}