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