xref: /wasmtime-44.0.1/crates/explorer/src/lib.rs (revision dcdbed12)
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