1 use anyhow::Result; 2 use capstone::arch::BuildsCapstone; 3 use serde_derive::Serialize; 4 use std::{io::Write, str::FromStr}; 5 use wasmtime_environ::demangle_function_name; 6 7 pub fn generate( 8 config: &wasmtime::Config, 9 target: Option<&str>, 10 wasm: &[u8], 11 dest: &mut dyn Write, 12 ) -> Result<()> { 13 let target = match target { 14 None => target_lexicon::Triple::host(), 15 Some(target) => target_lexicon::Triple::from_str(target)?, 16 }; 17 18 let wat = annotate_wat(wasm)?; 19 let wat_json = serde_json::to_string(&wat)?; 20 let asm = annotate_asm(config, &target, wasm)?; 21 let asm_json = serde_json::to_string(&asm)?; 22 23 let index_css = include_str!("./index.css"); 24 let index_js = include_str!("./index.js"); 25 26 write!( 27 dest, 28 r#" 29 <!DOCTYPE html> 30 <html> 31 <head> 32 <title>Wasmtime Compiler Explorer</title> 33 <style> 34 {index_css} 35 </style> 36 </head> 37 <body class="hbox"> 38 <pre id="wat"></pre> 39 <div id="asm"></div> 40 <script> 41 window.WAT = {wat_json}; 42 window.ASM = {asm_json}; 43 </script> 44 <script> 45 {index_js} 46 </script> 47 </body> 48 </html> 49 "# 50 )?; 51 Ok(()) 52 } 53 54 #[derive(Serialize, Clone, Copy, Debug)] 55 struct WasmOffset(u32); 56 57 #[derive(Serialize, Debug)] 58 struct AnnotatedWat { 59 chunks: Vec<AnnotatedWatChunk>, 60 } 61 62 #[derive(Serialize, Debug)] 63 struct AnnotatedWatChunk { 64 wasm_offset: Option<WasmOffset>, 65 wat: String, 66 } 67 68 fn annotate_wat(wasm: &[u8]) -> Result<AnnotatedWat> { 69 let printer = wasmprinter::Config::new(); 70 let mut storage = String::new(); 71 let chunks = printer 72 .offsets_and_lines(wasm, &mut storage)? 73 .map(|(offset, wat)| AnnotatedWatChunk { 74 wasm_offset: offset.map(|o| WasmOffset(u32::try_from(o).unwrap())), 75 wat: wat.to_string(), 76 }) 77 .collect(); 78 Ok(AnnotatedWat { chunks }) 79 } 80 81 #[derive(Serialize, Debug)] 82 struct AnnotatedAsm { 83 functions: Vec<AnnotatedFunction>, 84 } 85 86 #[derive(Serialize, Debug)] 87 struct AnnotatedFunction { 88 func_index: u32, 89 name: Option<String>, 90 demangled_name: Option<String>, 91 instructions: Vec<AnnotatedInstruction>, 92 } 93 94 #[derive(Serialize, Debug)] 95 struct AnnotatedInstruction { 96 wasm_offset: Option<WasmOffset>, 97 address: u32, 98 bytes: Vec<u8>, 99 mnemonic: Option<String>, 100 operands: Option<String>, 101 } 102 103 fn annotate_asm( 104 config: &wasmtime::Config, 105 target: &target_lexicon::Triple, 106 wasm: &[u8], 107 ) -> Result<AnnotatedAsm> { 108 let engine = wasmtime::Engine::new(config)?; 109 let module = wasmtime::Module::new(&engine, wasm)?; 110 111 let text = module.text(); 112 let address_map: Vec<_> = module 113 .address_map() 114 .ok_or_else(|| anyhow::anyhow!("address maps must be enabled in the config"))? 115 .collect(); 116 117 let mut address_map_iter = address_map.into_iter().peekable(); 118 let mut current_entry = address_map_iter.next(); 119 let mut wasm_offset_for_address = |start: usize, address: u32| -> Option<WasmOffset> { 120 // Consume any entries that happened before the current function for the 121 // first instruction. 122 while current_entry.map_or(false, |cur| cur.0 < start) { 123 current_entry = address_map_iter.next(); 124 } 125 126 // Next advance the address map up to the current `address` specified, 127 // including it. 128 while address_map_iter.peek().map_or(false, |next_entry| { 129 u32::try_from(next_entry.0).unwrap() <= address 130 }) { 131 current_entry = address_map_iter.next(); 132 } 133 current_entry.and_then(|entry| entry.1.map(WasmOffset)) 134 }; 135 136 let functions = module 137 .functions() 138 .map(|function| { 139 let body = &text[function.offset..][..function.len]; 140 141 let mut cs = match target.architecture { 142 target_lexicon::Architecture::Aarch64(_) => capstone::Capstone::new() 143 .arm64() 144 .mode(capstone::arch::arm64::ArchMode::Arm) 145 .build() 146 .map_err(|e| anyhow::anyhow!("{e}"))?, 147 target_lexicon::Architecture::Riscv64(_) => capstone::Capstone::new() 148 .riscv() 149 .mode(capstone::arch::riscv::ArchMode::RiscV64) 150 .build() 151 .map_err(|e| anyhow::anyhow!("{e}"))?, 152 target_lexicon::Architecture::S390x => capstone::Capstone::new() 153 .sysz() 154 .mode(capstone::arch::sysz::ArchMode::Default) 155 .build() 156 .map_err(|e| anyhow::anyhow!("{e}"))?, 157 target_lexicon::Architecture::X86_64 => capstone::Capstone::new() 158 .x86() 159 .mode(capstone::arch::x86::ArchMode::Mode64) 160 .build() 161 .map_err(|e| anyhow::anyhow!("{e}"))?, 162 _ => anyhow::bail!("Unsupported target: {target}"), 163 }; 164 165 // This tells capstone to skip over anything that looks like data, 166 // such as inline constant pools and things like that. This also 167 // additionally is required to skip over trapping instructions on 168 // AArch64. 169 cs.set_skipdata(true).unwrap(); 170 171 let instructions = cs 172 .disasm_all(body, function.offset as u64) 173 .map_err(|e| anyhow::anyhow!("{e}"))?; 174 let instructions = instructions 175 .iter() 176 .map(|inst| { 177 let address = u32::try_from(inst.address()).unwrap(); 178 let wasm_offset = wasm_offset_for_address(function.offset, address); 179 Ok(AnnotatedInstruction { 180 wasm_offset, 181 address, 182 bytes: inst.bytes().to_vec(), 183 mnemonic: inst.mnemonic().map(ToString::to_string), 184 operands: inst.op_str().map(ToString::to_string), 185 }) 186 }) 187 .collect::<Result<Vec<_>>>()?; 188 189 let demangled_name = if let Some(name) = &function.name { 190 let mut demangled = String::new(); 191 if demangle_function_name(&mut demangled, &name).is_ok() { 192 Some(demangled) 193 } else { 194 None 195 } 196 } else { 197 None 198 }; 199 200 Ok(AnnotatedFunction { 201 func_index: function.index.as_u32(), 202 name: function.name, 203 demangled_name, 204 instructions, 205 }) 206 }) 207 .collect::<Result<Vec<_>>>()?; 208 209 Ok(AnnotatedAsm { functions }) 210 } 211