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