wasmtime_internal_explorer/
lib.rs

1//! > **⚠️ Warning ⚠️**: this crate is an internal-only crate for the Wasmtime
2//! > project and is not intended for general use. APIs are not strictly
3//! > reviewed for safety and usage outside of Wasmtime may have bugs. If
4//! > you're interested in using this feel free to file an issue on the
5//! > Wasmtime repository to start a discussion about doing so, but otherwise
6//! > be aware that your usage of this crate is not supported.
7
8use anyhow::Result;
9use capstone::arch::BuildsCapstone;
10use serde_derive::Serialize;
11use std::{
12    fs::File,
13    io::{Write, read_to_string},
14    path::Path,
15    str::FromStr,
16};
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::<anyhow::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        .map(|(offset, wat)| AnnotatedWatChunk {
114            wasm_offset: offset.map(|o| WasmOffset(u32::try_from(o).unwrap())),
115            wat: wat.to_string(),
116        })
117        .collect();
118    Ok(AnnotatedWat { chunks })
119}
120
121#[derive(Serialize, Debug)]
122struct AnnotatedAsm {
123    functions: Vec<AnnotatedFunction>,
124}
125
126#[derive(Serialize, Debug)]
127struct AnnotatedFunction {
128    func_index: u32,
129    name: Option<String>,
130    demangled_name: Option<String>,
131    instructions: Vec<AnnotatedInstruction>,
132}
133
134#[derive(Serialize, Debug)]
135struct AnnotatedInstruction {
136    wasm_offset: Option<WasmOffset>,
137    address: u32,
138    bytes: Vec<u8>,
139    mnemonic: Option<String>,
140    operands: Option<String>,
141}
142
143fn annotate_asm(
144    config: &wasmtime::Config,
145    target: &target_lexicon::Triple,
146    wasm: &[u8],
147) -> Result<AnnotatedAsm> {
148    let engine = wasmtime::Engine::new(config)?;
149    let module = wasmtime::Module::new(&engine, wasm)?;
150
151    let text = module.text();
152    let address_map: Vec<_> = module
153        .address_map()
154        .ok_or_else(|| anyhow::anyhow!("address maps must be enabled in the config"))?
155        .collect();
156
157    let mut address_map_iter = address_map.into_iter().peekable();
158    let mut current_entry = address_map_iter.next();
159    let mut wasm_offset_for_address = |start: usize, address: u32| -> Option<WasmOffset> {
160        // Consume any entries that happened before the current function for the
161        // first instruction.
162        while current_entry.map_or(false, |cur| cur.0 < start) {
163            current_entry = address_map_iter.next();
164        }
165
166        // Next advance the address map up to the current `address` specified,
167        // including it.
168        while address_map_iter.peek().map_or(false, |next_entry| {
169            u32::try_from(next_entry.0).unwrap() <= address
170        }) {
171            current_entry = address_map_iter.next();
172        }
173        current_entry.and_then(|entry| entry.1.map(WasmOffset))
174    };
175
176    let functions = module
177        .functions()
178        .map(|function| {
179            let body = &text[function.offset..][..function.len];
180
181            let mut cs = match target.architecture {
182                target_lexicon::Architecture::Aarch64(_) => capstone::Capstone::new()
183                    .arm64()
184                    .mode(capstone::arch::arm64::ArchMode::Arm)
185                    .build()
186                    .map_err(|e| anyhow::anyhow!("{e}"))?,
187                target_lexicon::Architecture::Riscv64(_) => capstone::Capstone::new()
188                    .riscv()
189                    .mode(capstone::arch::riscv::ArchMode::RiscV64)
190                    .build()
191                    .map_err(|e| anyhow::anyhow!("{e}"))?,
192                target_lexicon::Architecture::S390x => capstone::Capstone::new()
193                    .sysz()
194                    .mode(capstone::arch::sysz::ArchMode::Default)
195                    .build()
196                    .map_err(|e| anyhow::anyhow!("{e}"))?,
197                target_lexicon::Architecture::X86_64 => capstone::Capstone::new()
198                    .x86()
199                    .mode(capstone::arch::x86::ArchMode::Mode64)
200                    .build()
201                    .map_err(|e| anyhow::anyhow!("{e}"))?,
202                _ => anyhow::bail!("Unsupported target: {target}"),
203            };
204
205            // This tells capstone to skip over anything that looks like data,
206            // such as inline constant pools and things like that. This also
207            // additionally is required to skip over trapping instructions on
208            // AArch64.
209            cs.set_skipdata(true).unwrap();
210
211            let instructions = cs
212                .disasm_all(body, function.offset as u64)
213                .map_err(|e| anyhow::anyhow!("{e}"))?;
214            let instructions = instructions
215                .iter()
216                .map(|inst| {
217                    let address = u32::try_from(inst.address()).unwrap();
218                    let wasm_offset = wasm_offset_for_address(function.offset, address);
219                    Ok(AnnotatedInstruction {
220                        wasm_offset,
221                        address,
222                        bytes: inst.bytes().to_vec(),
223                        mnemonic: inst.mnemonic().map(ToString::to_string),
224                        operands: inst.op_str().map(ToString::to_string),
225                    })
226                })
227                .collect::<Result<Vec<_>>>()?;
228
229            let demangled_name = if let Some(name) = &function.name {
230                let mut demangled = String::new();
231                if demangle_function_name(&mut demangled, &name).is_ok() {
232                    Some(demangled)
233                } else {
234                    None
235                }
236            } else {
237                None
238            };
239
240            Ok(AnnotatedFunction {
241                func_index: function.index.as_u32(),
242                name: function.name,
243                demangled_name,
244                instructions,
245            })
246        })
247        .collect::<Result<Vec<_>>>()?;
248
249    Ok(AnnotatedAsm { functions })
250}
251
252#[derive(Serialize, Debug)]
253struct AnnotatedClif {
254    functions: Vec<AnnotatedClifFunction>,
255}
256
257#[derive(Serialize, Debug)]
258struct AnnotatedClifFunction {
259    func_index: u32,
260    name: Option<String>,
261    demangled_name: Option<String>,
262    instructions: Vec<AnnotatedClifInstruction>,
263}
264
265#[derive(Serialize, Debug)]
266struct AnnotatedClifInstruction {
267    wasm_offset: Option<WasmOffset>,
268    clif: String,
269}
270
271fn annotate_clif(clif_dir: &Path, asm: &AnnotatedAsm) -> Result<AnnotatedClif> {
272    let mut clif = AnnotatedClif {
273        functions: Vec::new(),
274    };
275    for function in &asm.functions {
276        let function_path = clif_dir.join(format!("wasm_func_{}.clif", function.func_index));
277        if !function_path.exists() {
278            continue;
279        }
280        let mut clif_function = AnnotatedClifFunction {
281            func_index: function.func_index,
282            name: function.name.clone(),
283            demangled_name: function.demangled_name.clone(),
284            instructions: Vec::new(),
285        };
286        let file = File::open(&function_path)?;
287        for mut line in read_to_string(file)?.lines() {
288            if line.is_empty() {
289                continue;
290            }
291            let mut wasm_offset = None;
292            if line.starts_with('@') {
293                wasm_offset = Some(WasmOffset(u32::from_str_radix(&line[1..5], 16)?));
294                line = &line[28..];
295            } else if line.starts_with("     ") {
296                line = &line[28..];
297            }
298            clif_function.instructions.push(AnnotatedClifInstruction {
299                wasm_offset,
300                clif: line.to_string(),
301            });
302        }
303        clif.functions.push(clif_function);
304    }
305    Ok(clif)
306}