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