xref: /wasmtime-44.0.1/crates/explorer/src/lib.rs (revision d3e7548e)
1 use anyhow::Result;
2 use capstone::arch::BuildsCapstone;
3 use serde_derive::Serialize;
4 use std::{
5     fs::File,
6     io::{Write, read_to_string},
7     path::Path,
8     str::FromStr,
9 };
10 use wasmtime_environ::demangle_function_name;
11 
12 pub fn generate(
13     config: &wasmtime::Config,
14     target: Option<&str>,
15     clif_dir: Option<&Path>,
16     wasm: &[u8],
17     dest: &mut dyn Write,
18 ) -> Result<()> {
19     let target = match target {
20         None => target_lexicon::Triple::host(),
21         Some(target) => target_lexicon::Triple::from_str(target)?,
22     };
23 
24     let wat = annotate_wat(wasm)?;
25     let wat_json = serde_json::to_string(&wat)?;
26     let asm = annotate_asm(config, &target, wasm)?;
27     let asm_json = serde_json::to_string(&asm)?;
28     let clif_json = clif_dir
29         .map::<anyhow::Result<String>, _>(|clif_dir| {
30             let clif = annotate_clif(clif_dir, &asm)?;
31             Ok(serde_json::to_string(&clif)?)
32         })
33         .transpose()?;
34 
35     let index_css = include_str!("./index.css");
36     let index_js = include_str!("./index.js");
37 
38     write!(
39         dest,
40         r#"
41 <!DOCTYPE html>
42 <html>
43   <head>
44     <title>Wasmtime Compiler Explorer</title>
45     <style>
46       {index_css}
47     </style>
48   </head>
49   <body class="hbox">
50     <pre id="wat"></pre>
51         "#
52     )?;
53     if clif_json.is_some() {
54         write!(dest, r#"<div id="clif"></div>"#)?;
55     }
56     write!(
57         dest,
58         r#"
59     <div id="asm"></div>
60     <script>
61       window.WAT = {wat_json};
62         "#
63     )?;
64     if let Some(clif_json) = clif_json {
65         write!(
66             dest,
67             r#"
68           window.CLIF = {clif_json};
69             "#
70         )?;
71     }
72     write!(
73         dest,
74         r#"
75       window.ASM = {asm_json};
76     </script>
77     <script>
78       {index_js}
79     </script>
80   </body>
81 </html>
82         "#
83     )?;
84     Ok(())
85 }
86 
87 #[derive(Serialize, Clone, Copy, Debug)]
88 struct WasmOffset(u32);
89 
90 #[derive(Serialize, Debug)]
91 struct AnnotatedWat {
92     chunks: Vec<AnnotatedWatChunk>,
93 }
94 
95 #[derive(Serialize, Debug)]
96 struct AnnotatedWatChunk {
97     wasm_offset: Option<WasmOffset>,
98     wat: String,
99 }
100 
101 fn annotate_wat(wasm: &[u8]) -> Result<AnnotatedWat> {
102     let printer = wasmprinter::Config::new();
103     let mut storage = String::new();
104     let chunks = printer
105         .offsets_and_lines(wasm, &mut storage)?
106         .map(|(offset, wat)| AnnotatedWatChunk {
107             wasm_offset: offset.map(|o| WasmOffset(u32::try_from(o).unwrap())),
108             wat: wat.to_string(),
109         })
110         .collect();
111     Ok(AnnotatedWat { chunks })
112 }
113 
114 #[derive(Serialize, Debug)]
115 struct AnnotatedAsm {
116     functions: Vec<AnnotatedFunction>,
117 }
118 
119 #[derive(Serialize, Debug)]
120 struct AnnotatedFunction {
121     func_index: u32,
122     name: Option<String>,
123     demangled_name: Option<String>,
124     instructions: Vec<AnnotatedInstruction>,
125 }
126 
127 #[derive(Serialize, Debug)]
128 struct AnnotatedInstruction {
129     wasm_offset: Option<WasmOffset>,
130     address: u32,
131     bytes: Vec<u8>,
132     mnemonic: Option<String>,
133     operands: Option<String>,
134 }
135 
136 fn annotate_asm(
137     config: &wasmtime::Config,
138     target: &target_lexicon::Triple,
139     wasm: &[u8],
140 ) -> Result<AnnotatedAsm> {
141     let engine = wasmtime::Engine::new(config)?;
142     let module = wasmtime::Module::new(&engine, wasm)?;
143 
144     let text = module.text();
145     let address_map: Vec<_> = module
146         .address_map()
147         .ok_or_else(|| anyhow::anyhow!("address maps must be enabled in the config"))?
148         .collect();
149 
150     let mut address_map_iter = address_map.into_iter().peekable();
151     let mut current_entry = address_map_iter.next();
152     let mut wasm_offset_for_address = |start: usize, address: u32| -> Option<WasmOffset> {
153         // Consume any entries that happened before the current function for the
154         // first instruction.
155         while current_entry.map_or(false, |cur| cur.0 < start) {
156             current_entry = address_map_iter.next();
157         }
158 
159         // Next advance the address map up to the current `address` specified,
160         // including it.
161         while address_map_iter.peek().map_or(false, |next_entry| {
162             u32::try_from(next_entry.0).unwrap() <= address
163         }) {
164             current_entry = address_map_iter.next();
165         }
166         current_entry.and_then(|entry| entry.1.map(WasmOffset))
167     };
168 
169     let functions = module
170         .functions()
171         .map(|function| {
172             let body = &text[function.offset..][..function.len];
173 
174             let mut cs = match target.architecture {
175                 target_lexicon::Architecture::Aarch64(_) => capstone::Capstone::new()
176                     .arm64()
177                     .mode(capstone::arch::arm64::ArchMode::Arm)
178                     .build()
179                     .map_err(|e| anyhow::anyhow!("{e}"))?,
180                 target_lexicon::Architecture::Riscv64(_) => capstone::Capstone::new()
181                     .riscv()
182                     .mode(capstone::arch::riscv::ArchMode::RiscV64)
183                     .build()
184                     .map_err(|e| anyhow::anyhow!("{e}"))?,
185                 target_lexicon::Architecture::S390x => capstone::Capstone::new()
186                     .sysz()
187                     .mode(capstone::arch::sysz::ArchMode::Default)
188                     .build()
189                     .map_err(|e| anyhow::anyhow!("{e}"))?,
190                 target_lexicon::Architecture::X86_64 => capstone::Capstone::new()
191                     .x86()
192                     .mode(capstone::arch::x86::ArchMode::Mode64)
193                     .build()
194                     .map_err(|e| anyhow::anyhow!("{e}"))?,
195                 _ => anyhow::bail!("Unsupported target: {target}"),
196             };
197 
198             // This tells capstone to skip over anything that looks like data,
199             // such as inline constant pools and things like that. This also
200             // additionally is required to skip over trapping instructions on
201             // AArch64.
202             cs.set_skipdata(true).unwrap();
203 
204             let instructions = cs
205                 .disasm_all(body, function.offset as u64)
206                 .map_err(|e| anyhow::anyhow!("{e}"))?;
207             let instructions = instructions
208                 .iter()
209                 .map(|inst| {
210                     let address = u32::try_from(inst.address()).unwrap();
211                     let wasm_offset = wasm_offset_for_address(function.offset, address);
212                     Ok(AnnotatedInstruction {
213                         wasm_offset,
214                         address,
215                         bytes: inst.bytes().to_vec(),
216                         mnemonic: inst.mnemonic().map(ToString::to_string),
217                         operands: inst.op_str().map(ToString::to_string),
218                     })
219                 })
220                 .collect::<Result<Vec<_>>>()?;
221 
222             let demangled_name = if let Some(name) = &function.name {
223                 let mut demangled = String::new();
224                 if demangle_function_name(&mut demangled, &name).is_ok() {
225                     Some(demangled)
226                 } else {
227                     None
228                 }
229             } else {
230                 None
231             };
232 
233             Ok(AnnotatedFunction {
234                 func_index: function.index.as_u32(),
235                 name: function.name,
236                 demangled_name,
237                 instructions,
238             })
239         })
240         .collect::<Result<Vec<_>>>()?;
241 
242     Ok(AnnotatedAsm { functions })
243 }
244 
245 #[derive(Serialize, Debug)]
246 struct AnnotatedClif {
247     functions: Vec<AnnotatedClifFunction>,
248 }
249 
250 #[derive(Serialize, Debug)]
251 struct AnnotatedClifFunction {
252     func_index: u32,
253     name: Option<String>,
254     demangled_name: Option<String>,
255     instructions: Vec<AnnotatedClifInstruction>,
256 }
257 
258 #[derive(Serialize, Debug)]
259 struct AnnotatedClifInstruction {
260     wasm_offset: Option<WasmOffset>,
261     clif: String,
262 }
263 
264 fn annotate_clif(clif_dir: &Path, asm: &AnnotatedAsm) -> Result<AnnotatedClif> {
265     let mut clif = AnnotatedClif {
266         functions: Vec::new(),
267     };
268     for function in &asm.functions {
269         let function_path = clif_dir.join(format!("wasm_func_{}.clif", function.func_index));
270         if !function_path.exists() {
271             continue;
272         }
273         let mut clif_function = AnnotatedClifFunction {
274             func_index: function.func_index,
275             name: function.name.clone(),
276             demangled_name: function.demangled_name.clone(),
277             instructions: Vec::new(),
278         };
279         let file = File::open(&function_path)?;
280         for mut line in read_to_string(file)?.lines() {
281             if line.is_empty() {
282                 continue;
283             }
284             let mut wasm_offset = None;
285             if line.starts_with('@') {
286                 wasm_offset = Some(WasmOffset(u32::from_str_radix(&line[1..5], 16)?));
287                 line = &line[28..];
288             } else if line.starts_with("     ") {
289                 line = &line[28..];
290             }
291             clif_function.instructions.push(AnnotatedClifInstruction {
292                 wasm_offset,
293                 clif: line.to_string(),
294             });
295         }
296         clif.functions.push(clif_function);
297     }
298     Ok(clif)
299 }
300