wasmtime_internal_explorer/
lib.rs1use capstone::arch::BuildsCapstone;
9use serde_derive::Serialize;
10use std::{
11 fs::File,
12 io::{Write, read_to_string},
13 path::Path,
14 str::FromStr,
15};
16use wasmtime::{Result, ToWasmtimeResult as _};
17use wasmtime_environ::demangle_function_name;
18
19pub 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)]
95struct WasmOffset(u32);
96
97#[derive(Serialize, Debug)]
98struct AnnotatedWat {
99 chunks: Vec<AnnotatedWatChunk>,
100}
101
102#[derive(Serialize, Debug)]
103struct AnnotatedWatChunk {
104 wasm_offset: Option<WasmOffset>,
105 wat: String,
106}
107
108fn 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 .to_wasmtime_result()?
114 .map(|(offset, wat)| AnnotatedWatChunk {
115 wasm_offset: offset.map(|o| WasmOffset(u32::try_from(o).unwrap())),
116 wat: wat.to_string(),
117 })
118 .collect();
119 Ok(AnnotatedWat { chunks })
120}
121
122#[derive(Serialize, Debug)]
123struct AnnotatedAsm {
124 functions: Vec<AnnotatedFunction>,
125}
126
127#[derive(Serialize, Debug)]
128struct AnnotatedFunction {
129 func_index: u32,
130 name: Option<String>,
131 demangled_name: Option<String>,
132 instructions: Vec<AnnotatedInstruction>,
133}
134
135#[derive(Serialize, Debug)]
136struct AnnotatedInstruction {
137 wasm_offset: Option<WasmOffset>,
138 address: u32,
139 bytes: Vec<u8>,
140 mnemonic: Option<String>,
141 operands: Option<String>,
142}
143
144fn annotate_asm(
145 config: &wasmtime::Config,
146 target: &target_lexicon::Triple,
147 wasm: &[u8],
148) -> Result<AnnotatedAsm> {
149 let engine = wasmtime::Engine::new(config)?;
150 let module = wasmtime::Module::new(&engine, wasm)?;
151
152 let text = module.text();
153 let address_map: Vec<_> = module
154 .address_map()
155 .ok_or_else(|| wasmtime::format_err!("address maps must be enabled in the config"))?
156 .collect();
157
158 let mut address_map_iter = address_map.into_iter().peekable();
159 let mut current_entry = address_map_iter.next();
160 let mut wasm_offset_for_address = |start: usize, address: u32| -> Option<WasmOffset> {
161 while current_entry.map_or(false, |cur| cur.0 < start) {
164 current_entry = address_map_iter.next();
165 }
166
167 while address_map_iter.peek().map_or(false, |next_entry| {
170 u32::try_from(next_entry.0).unwrap() <= address
171 }) {
172 current_entry = address_map_iter.next();
173 }
174 current_entry.and_then(|entry| entry.1.map(WasmOffset))
175 };
176
177 let functions = module
178 .functions()
179 .map(|function| {
180 let body = &text[function.offset..][..function.len];
181
182 let mut cs = match target.architecture {
183 target_lexicon::Architecture::Aarch64(_) => capstone::Capstone::new()
184 .arm64()
185 .mode(capstone::arch::arm64::ArchMode::Arm)
186 .build()
187 .map_err(|e| wasmtime::format_err!("{e}"))?,
188 target_lexicon::Architecture::Riscv64(_) => capstone::Capstone::new()
189 .riscv()
190 .mode(capstone::arch::riscv::ArchMode::RiscV64)
191 .build()
192 .map_err(|e| wasmtime::format_err!("{e}"))?,
193 target_lexicon::Architecture::S390x => capstone::Capstone::new()
194 .sysz()
195 .mode(capstone::arch::sysz::ArchMode::Default)
196 .build()
197 .map_err(|e| wasmtime::format_err!("{e}"))?,
198 target_lexicon::Architecture::X86_64 => capstone::Capstone::new()
199 .x86()
200 .mode(capstone::arch::x86::ArchMode::Mode64)
201 .build()
202 .map_err(|e| wasmtime::format_err!("{e}"))?,
203 _ => wasmtime::bail!("Unsupported target: {target}"),
204 };
205
206 cs.set_skipdata(true).unwrap();
211
212 let instructions = cs
213 .disasm_all(body, function.offset as u64)
214 .map_err(|e| wasmtime::format_err!("{e}"))?;
215 let instructions = instructions
216 .iter()
217 .map(|inst| {
218 let address = u32::try_from(inst.address()).unwrap();
219 let wasm_offset = wasm_offset_for_address(function.offset, address);
220 Ok(AnnotatedInstruction {
221 wasm_offset,
222 address,
223 bytes: inst.bytes().to_vec(),
224 mnemonic: inst.mnemonic().map(ToString::to_string),
225 operands: inst.op_str().map(ToString::to_string),
226 })
227 })
228 .collect::<Result<Vec<_>>>()?;
229
230 let demangled_name = if let Some(name) = &function.name {
231 let mut demangled = String::new();
232 if demangle_function_name(&mut demangled, &name).is_ok() {
233 Some(demangled)
234 } else {
235 None
236 }
237 } else {
238 None
239 };
240
241 Ok(AnnotatedFunction {
242 func_index: function.index.as_u32(),
243 name: function.name,
244 demangled_name,
245 instructions,
246 })
247 })
248 .collect::<Result<Vec<_>>>()?;
249
250 Ok(AnnotatedAsm { functions })
251}
252
253#[derive(Serialize, Debug)]
254struct AnnotatedClif {
255 functions: Vec<AnnotatedClifFunction>,
256}
257
258#[derive(Serialize, Debug)]
259struct AnnotatedClifFunction {
260 func_index: u32,
261 name: Option<String>,
262 demangled_name: Option<String>,
263 instructions: Vec<AnnotatedClifInstruction>,
264}
265
266#[derive(Serialize, Debug)]
267struct AnnotatedClifInstruction {
268 wasm_offset: Option<WasmOffset>,
269 clif: String,
270}
271
272fn annotate_clif(clif_dir: &Path, asm: &AnnotatedAsm) -> Result<AnnotatedClif> {
273 let mut clif = AnnotatedClif {
274 functions: Vec::new(),
275 };
276 for function in &asm.functions {
277 let function_path = clif_dir.join(format!("wasm_func_{}.clif", function.func_index));
278 if !function_path.exists() {
279 continue;
280 }
281 let mut clif_function = AnnotatedClifFunction {
282 func_index: function.func_index,
283 name: function.name.clone(),
284 demangled_name: function.demangled_name.clone(),
285 instructions: Vec::new(),
286 };
287 let file = File::open(&function_path)?;
288 for mut line in read_to_string(file)?.lines() {
289 if line.is_empty() {
290 continue;
291 }
292 let mut wasm_offset = None;
293 if line.starts_with('@') {
294 wasm_offset = Some(WasmOffset(u32::from_str_radix(&line[1..5], 16)?));
295 line = &line[28..];
296 } else if line.starts_with(" ") {
297 line = &line[28..];
298 }
299 clif_function.instructions.push(AnnotatedClifInstruction {
300 wasm_offset,
301 clif: line.to_string(),
302 });
303 }
304 clif.functions.push(clif_function);
305 }
306 Ok(clif)
307}