xref: /wasmtime-44.0.1/crates/explorer/src/lib.rs (revision c3a6060b)
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 
generate( config: &wasmtime::Config, target: Option<&str>, clif_dir: Option<&Path>, wasm: &[u8], dest: &mut dyn Write, ) -> Result<()>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 
annotate_wat(wasm: &[u8]) -> Result<AnnotatedWat>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 
annotate_asm( config: &wasmtime::Config, target: &target_lexicon::Triple, wasm: &[u8], ) -> Result<AnnotatedAsm>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 
annotate_clif(clif_dir: &Path, asm: &AnnotatedAsm) -> Result<AnnotatedClif>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