1 use anyhow::Result; 2 use capstone::arch::BuildsCapstone; 3 use serde_derive::Serialize; 4 use std::{ 5 fs::File, 6 io::{Write, read_to_string}, 7 path::Path, 8 str::FromStr, 9 }; 10 use wasmtime_environ::demangle_function_name; 11 12 pub fn generate( 13 config: &wasmtime::Config, 14 target: Option<&str>, 15 clif_dir: Option<&Path>, 16 wasm: &[u8], 17 dest: &mut dyn Write, 18 ) -> Result<()> { 19 let target = match target { 20 None => target_lexicon::Triple::host(), 21 Some(target) => target_lexicon::Triple::from_str(target)?, 22 }; 23 24 let wat = annotate_wat(wasm)?; 25 let wat_json = serde_json::to_string(&wat)?; 26 let asm = annotate_asm(config, &target, wasm)?; 27 let asm_json = serde_json::to_string(&asm)?; 28 let clif_json = clif_dir 29 .map::<anyhow::Result<String>, _>(|clif_dir| { 30 let clif = annotate_clif(clif_dir, &asm)?; 31 Ok(serde_json::to_string(&clif)?) 32 }) 33 .transpose()?; 34 35 let index_css = include_str!("./index.css"); 36 let index_js = include_str!("./index.js"); 37 38 write!( 39 dest, 40 r#" 41 <!DOCTYPE html> 42 <html> 43 <head> 44 <title>Wasmtime Compiler Explorer</title> 45 <style> 46 {index_css} 47 </style> 48 </head> 49 <body class="hbox"> 50 <pre id="wat"></pre> 51 "# 52 )?; 53 if clif_json.is_some() { 54 write!(dest, r#"<div id="clif"></div>"#)?; 55 } 56 write!( 57 dest, 58 r#" 59 <div id="asm"></div> 60 <script> 61 window.WAT = {wat_json}; 62 "# 63 )?; 64 if let Some(clif_json) = clif_json { 65 write!( 66 dest, 67 r#" 68 window.CLIF = {clif_json}; 69 "# 70 )?; 71 } 72 write!( 73 dest, 74 r#" 75 window.ASM = {asm_json}; 76 </script> 77 <script> 78 {index_js} 79 </script> 80 </body> 81 </html> 82 "# 83 )?; 84 Ok(()) 85 } 86 87 #[derive(Serialize, Clone, Copy, Debug)] 88 struct WasmOffset(u32); 89 90 #[derive(Serialize, Debug)] 91 struct AnnotatedWat { 92 chunks: Vec<AnnotatedWatChunk>, 93 } 94 95 #[derive(Serialize, Debug)] 96 struct AnnotatedWatChunk { 97 wasm_offset: Option<WasmOffset>, 98 wat: String, 99 } 100 101 fn annotate_wat(wasm: &[u8]) -> Result<AnnotatedWat> { 102 let printer = wasmprinter::Config::new(); 103 let mut storage = String::new(); 104 let chunks = printer 105 .offsets_and_lines(wasm, &mut storage)? 106 .map(|(offset, wat)| AnnotatedWatChunk { 107 wasm_offset: offset.map(|o| WasmOffset(u32::try_from(o).unwrap())), 108 wat: wat.to_string(), 109 }) 110 .collect(); 111 Ok(AnnotatedWat { chunks }) 112 } 113 114 #[derive(Serialize, Debug)] 115 struct AnnotatedAsm { 116 functions: Vec<AnnotatedFunction>, 117 } 118 119 #[derive(Serialize, Debug)] 120 struct AnnotatedFunction { 121 func_index: u32, 122 name: Option<String>, 123 demangled_name: Option<String>, 124 instructions: Vec<AnnotatedInstruction>, 125 } 126 127 #[derive(Serialize, Debug)] 128 struct AnnotatedInstruction { 129 wasm_offset: Option<WasmOffset>, 130 address: u32, 131 bytes: Vec<u8>, 132 mnemonic: Option<String>, 133 operands: Option<String>, 134 } 135 136 fn annotate_asm( 137 config: &wasmtime::Config, 138 target: &target_lexicon::Triple, 139 wasm: &[u8], 140 ) -> Result<AnnotatedAsm> { 141 let engine = wasmtime::Engine::new(config)?; 142 let module = wasmtime::Module::new(&engine, wasm)?; 143 144 let text = module.text(); 145 let address_map: Vec<_> = module 146 .address_map() 147 .ok_or_else(|| anyhow::anyhow!("address maps must be enabled in the config"))? 148 .collect(); 149 150 let mut address_map_iter = address_map.into_iter().peekable(); 151 let mut current_entry = address_map_iter.next(); 152 let mut wasm_offset_for_address = |start: usize, address: u32| -> Option<WasmOffset> { 153 // Consume any entries that happened before the current function for the 154 // first instruction. 155 while current_entry.map_or(false, |cur| cur.0 < start) { 156 current_entry = address_map_iter.next(); 157 } 158 159 // Next advance the address map up to the current `address` specified, 160 // including it. 161 while address_map_iter.peek().map_or(false, |next_entry| { 162 u32::try_from(next_entry.0).unwrap() <= address 163 }) { 164 current_entry = address_map_iter.next(); 165 } 166 current_entry.and_then(|entry| entry.1.map(WasmOffset)) 167 }; 168 169 let functions = module 170 .functions() 171 .map(|function| { 172 let body = &text[function.offset..][..function.len]; 173 174 let mut cs = match target.architecture { 175 target_lexicon::Architecture::Aarch64(_) => capstone::Capstone::new() 176 .arm64() 177 .mode(capstone::arch::arm64::ArchMode::Arm) 178 .build() 179 .map_err(|e| anyhow::anyhow!("{e}"))?, 180 target_lexicon::Architecture::Riscv64(_) => capstone::Capstone::new() 181 .riscv() 182 .mode(capstone::arch::riscv::ArchMode::RiscV64) 183 .build() 184 .map_err(|e| anyhow::anyhow!("{e}"))?, 185 target_lexicon::Architecture::S390x => capstone::Capstone::new() 186 .sysz() 187 .mode(capstone::arch::sysz::ArchMode::Default) 188 .build() 189 .map_err(|e| anyhow::anyhow!("{e}"))?, 190 target_lexicon::Architecture::X86_64 => capstone::Capstone::new() 191 .x86() 192 .mode(capstone::arch::x86::ArchMode::Mode64) 193 .build() 194 .map_err(|e| anyhow::anyhow!("{e}"))?, 195 _ => anyhow::bail!("Unsupported target: {target}"), 196 }; 197 198 // This tells capstone to skip over anything that looks like data, 199 // such as inline constant pools and things like that. This also 200 // additionally is required to skip over trapping instructions on 201 // AArch64. 202 cs.set_skipdata(true).unwrap(); 203 204 let instructions = cs 205 .disasm_all(body, function.offset as u64) 206 .map_err(|e| anyhow::anyhow!("{e}"))?; 207 let instructions = instructions 208 .iter() 209 .map(|inst| { 210 let address = u32::try_from(inst.address()).unwrap(); 211 let wasm_offset = wasm_offset_for_address(function.offset, address); 212 Ok(AnnotatedInstruction { 213 wasm_offset, 214 address, 215 bytes: inst.bytes().to_vec(), 216 mnemonic: inst.mnemonic().map(ToString::to_string), 217 operands: inst.op_str().map(ToString::to_string), 218 }) 219 }) 220 .collect::<Result<Vec<_>>>()?; 221 222 let demangled_name = if let Some(name) = &function.name { 223 let mut demangled = String::new(); 224 if demangle_function_name(&mut demangled, &name).is_ok() { 225 Some(demangled) 226 } else { 227 None 228 } 229 } else { 230 None 231 }; 232 233 Ok(AnnotatedFunction { 234 func_index: function.index.as_u32(), 235 name: function.name, 236 demangled_name, 237 instructions, 238 }) 239 }) 240 .collect::<Result<Vec<_>>>()?; 241 242 Ok(AnnotatedAsm { functions }) 243 } 244 245 #[derive(Serialize, Debug)] 246 struct AnnotatedClif { 247 functions: Vec<AnnotatedClifFunction>, 248 } 249 250 #[derive(Serialize, Debug)] 251 struct AnnotatedClifFunction { 252 func_index: u32, 253 name: Option<String>, 254 demangled_name: Option<String>, 255 instructions: Vec<AnnotatedClifInstruction>, 256 } 257 258 #[derive(Serialize, Debug)] 259 struct AnnotatedClifInstruction { 260 wasm_offset: Option<WasmOffset>, 261 clif: String, 262 } 263 264 fn annotate_clif(clif_dir: &Path, asm: &AnnotatedAsm) -> Result<AnnotatedClif> { 265 let mut clif = AnnotatedClif { 266 functions: Vec::new(), 267 }; 268 for function in &asm.functions { 269 let function_path = clif_dir.join(format!("wasm_func_{}.clif", function.func_index)); 270 if !function_path.exists() { 271 continue; 272 } 273 let mut clif_function = AnnotatedClifFunction { 274 func_index: function.func_index, 275 name: function.name.clone(), 276 demangled_name: function.demangled_name.clone(), 277 instructions: Vec::new(), 278 }; 279 let file = File::open(&function_path)?; 280 for mut line in read_to_string(file)?.lines() { 281 if line.is_empty() { 282 continue; 283 } 284 let mut wasm_offset = None; 285 if line.starts_with('@') { 286 wasm_offset = Some(WasmOffset(u32::from_str_radix(&line[1..5], 16)?)); 287 line = &line[28..]; 288 } else if line.starts_with(" ") { 289 line = &line[28..]; 290 } 291 clif_function.instructions.push(AnnotatedClifInstruction { 292 wasm_offset, 293 clif: line.to_string(), 294 }); 295 } 296 clif.functions.push(clif_function); 297 } 298 Ok(clif) 299 } 300