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