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, ToWasmtimeResult as _}; 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 .to_wasmtime_result()? 114 .map(|(offset, wat)| AnnotatedWatChunk { 115 wasm_offset: offset.map(|o| WasmOffset(u32::try_from(o).unwrap())), 116 wat: wat.to_string(), 117 }) 118 .collect(); 119 Ok(AnnotatedWat { chunks }) 120 } 121 122 #[derive(Serialize, Debug)] 123 struct AnnotatedAsm { 124 functions: Vec<AnnotatedFunction>, 125 } 126 127 #[derive(Serialize, Debug)] 128 struct AnnotatedFunction { 129 func_index: u32, 130 name: Option<String>, 131 demangled_name: Option<String>, 132 instructions: Vec<AnnotatedInstruction>, 133 } 134 135 #[derive(Serialize, Debug)] 136 struct AnnotatedInstruction { 137 wasm_offset: Option<WasmOffset>, 138 address: u32, 139 bytes: Vec<u8>, 140 mnemonic: Option<String>, 141 operands: Option<String>, 142 } 143 144 fn annotate_asm( 145 config: &wasmtime::Config, 146 target: &target_lexicon::Triple, 147 wasm: &[u8], 148 ) -> Result<AnnotatedAsm> { 149 let engine = wasmtime::Engine::new(config)?; 150 let module = wasmtime::Module::new(&engine, wasm)?; 151 152 let text = module.text(); 153 let address_map: Vec<_> = module 154 .address_map() 155 .ok_or_else(|| wasmtime::format_err!("address maps must be enabled in the config"))? 156 .collect(); 157 158 let mut address_map_iter = address_map.into_iter().peekable(); 159 let mut current_entry = address_map_iter.next(); 160 let mut wasm_offset_for_address = |start: usize, address: u32| -> Option<WasmOffset> { 161 // Consume any entries that happened before the current function for the 162 // first instruction. 163 while current_entry.map_or(false, |cur| cur.0 < start) { 164 current_entry = address_map_iter.next(); 165 } 166 167 // Next advance the address map up to the current `address` specified, 168 // including it. 169 while address_map_iter.peek().map_or(false, |next_entry| { 170 u32::try_from(next_entry.0).unwrap() <= address 171 }) { 172 current_entry = address_map_iter.next(); 173 } 174 current_entry.and_then(|entry| entry.1.map(WasmOffset)) 175 }; 176 177 let functions = module 178 .functions() 179 .map(|function| { 180 let body = &text[function.offset..][..function.len]; 181 182 let mut cs = match target.architecture { 183 target_lexicon::Architecture::Aarch64(_) => capstone::Capstone::new() 184 .arm64() 185 .mode(capstone::arch::arm64::ArchMode::Arm) 186 .build() 187 .map_err(|e| wasmtime::format_err!("{e}"))?, 188 target_lexicon::Architecture::Riscv64(_) => capstone::Capstone::new() 189 .riscv() 190 .mode(capstone::arch::riscv::ArchMode::RiscV64) 191 .build() 192 .map_err(|e| wasmtime::format_err!("{e}"))?, 193 target_lexicon::Architecture::S390x => capstone::Capstone::new() 194 .sysz() 195 .mode(capstone::arch::sysz::ArchMode::Default) 196 .build() 197 .map_err(|e| wasmtime::format_err!("{e}"))?, 198 target_lexicon::Architecture::X86_64 => capstone::Capstone::new() 199 .x86() 200 .mode(capstone::arch::x86::ArchMode::Mode64) 201 .build() 202 .map_err(|e| wasmtime::format_err!("{e}"))?, 203 _ => wasmtime::bail!("Unsupported target: {target}"), 204 }; 205 206 // This tells capstone to skip over anything that looks like data, 207 // such as inline constant pools and things like that. This also 208 // additionally is required to skip over trapping instructions on 209 // AArch64. 210 cs.set_skipdata(true).unwrap(); 211 212 let instructions = cs 213 .disasm_all(body, function.offset as u64) 214 .map_err(|e| wasmtime::format_err!("{e}"))?; 215 let instructions = instructions 216 .iter() 217 .map(|inst| { 218 let address = u32::try_from(inst.address()).unwrap(); 219 let wasm_offset = wasm_offset_for_address(function.offset, address); 220 Ok(AnnotatedInstruction { 221 wasm_offset, 222 address, 223 bytes: inst.bytes().to_vec(), 224 mnemonic: inst.mnemonic().map(ToString::to_string), 225 operands: inst.op_str().map(ToString::to_string), 226 }) 227 }) 228 .collect::<Result<Vec<_>>>()?; 229 230 let demangled_name = if let Some(name) = &function.name { 231 let mut demangled = String::new(); 232 if demangle_function_name(&mut demangled, &name).is_ok() { 233 Some(demangled) 234 } else { 235 None 236 } 237 } else { 238 None 239 }; 240 241 Ok(AnnotatedFunction { 242 func_index: function.index.as_u32(), 243 name: function.name, 244 demangled_name, 245 instructions, 246 }) 247 }) 248 .collect::<Result<Vec<_>>>()?; 249 250 Ok(AnnotatedAsm { functions }) 251 } 252 253 #[derive(Serialize, Debug)] 254 struct AnnotatedClif { 255 functions: Vec<AnnotatedClifFunction>, 256 } 257 258 #[derive(Serialize, Debug)] 259 struct AnnotatedClifFunction { 260 func_index: u32, 261 name: Option<String>, 262 demangled_name: Option<String>, 263 instructions: Vec<AnnotatedClifInstruction>, 264 } 265 266 #[derive(Serialize, Debug)] 267 struct AnnotatedClifInstruction { 268 wasm_offset: Option<WasmOffset>, 269 clif: String, 270 } 271 272 fn annotate_clif(clif_dir: &Path, asm: &AnnotatedAsm) -> Result<AnnotatedClif> { 273 let mut clif = AnnotatedClif { 274 functions: Vec::new(), 275 }; 276 for function in &asm.functions { 277 let function_path = clif_dir.join(format!("wasm_func_{}.clif", function.func_index)); 278 if !function_path.exists() { 279 continue; 280 } 281 let mut clif_function = AnnotatedClifFunction { 282 func_index: function.func_index, 283 name: function.name.clone(), 284 demangled_name: function.demangled_name.clone(), 285 instructions: Vec::new(), 286 }; 287 let file = File::open(&function_path)?; 288 for mut line in read_to_string(file)?.lines() { 289 if line.is_empty() { 290 continue; 291 } 292 let mut wasm_offset = None; 293 if line.starts_with('@') { 294 wasm_offset = Some(WasmOffset(u32::from_str_radix(&line[1..5], 16)?)); 295 line = &line[28..]; 296 } else if line.starts_with(" ") { 297 line = &line[28..]; 298 } 299 clif_function.instructions.push(AnnotatedClifInstruction { 300 wasm_offset, 301 clif: line.to_string(), 302 }); 303 } 304 clif.functions.push(clif_function); 305 } 306 Ok(clif) 307 } 308