1use anyhow::Result;
2use capstone::arch::BuildsCapstone;
3use serde_derive::Serialize;
4use std::{
5 fs::File,
6 io::{read_to_string, Write},
7 path::Path,
8 str::FromStr,
9};
10use wasmtime_environ::demangle_function_name;
11
12pub 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)]
88struct WasmOffset(u32);
89
90#[derive(Serialize, Debug)]
91struct AnnotatedWat {
92 chunks: Vec<AnnotatedWatChunk>,
93}
94
95#[derive(Serialize, Debug)]
96struct AnnotatedWatChunk {
97 wasm_offset: Option<WasmOffset>,
98 wat: String,
99}
100
101fn 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)]
115struct AnnotatedAsm {
116 functions: Vec<AnnotatedFunction>,
117}
118
119#[derive(Serialize, Debug)]
120struct AnnotatedFunction {
121 func_index: u32,
122 name: Option<String>,
123 demangled_name: Option<String>,
124 instructions: Vec<AnnotatedInstruction>,
125}
126
127#[derive(Serialize, Debug)]
128struct AnnotatedInstruction {
129 wasm_offset: Option<WasmOffset>,
130 address: u32,
131 bytes: Vec<u8>,
132 mnemonic: Option<String>,
133 operands: Option<String>,
134}
135
136fn 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 while current_entry.map_or(false, |cur| cur.0 < start) {
156 current_entry = address_map_iter.next();
157 }
158
159 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 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)]
246struct AnnotatedClif {
247 functions: Vec<AnnotatedClifFunction>,
248}
249
250#[derive(Serialize, Debug)]
251struct AnnotatedClifFunction {
252 func_index: u32,
253 name: Option<String>,
254 demangled_name: Option<String>,
255 instructions: Vec<AnnotatedClifInstruction>,
256}
257
258#[derive(Serialize, Debug)]
259struct AnnotatedClifInstruction {
260 wasm_offset: Option<WasmOffset>,
261 clif: String,
262}
263
264fn 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}