Skip to main content

wasmer_compiler/
source_map.rs

1use addr2line::gimli::{Dwarf, EndianRcSlice, LittleEndian, SectionId};
2use std::{collections::BTreeMap, path::PathBuf, rc::Rc};
3use wasmer_types::{LocalFunctionIndex, ModuleInfo, entity::PrimaryMap};
4
5use crate::{
6    FunctionBodyData, ModuleTranslationState,
7    wasmparser::{BinaryReader, FunctionBody},
8};
9
10/// An original source position associated with a Wasm operator.
11#[derive(Clone, Debug)]
12pub struct SourceLocation {
13    /// Source file name.
14    pub file: String,
15    /// Directory containing the source file.
16    pub directory: String,
17    /// One-based source line number.
18    pub line: u32,
19    /// Source column number.
20    pub column: u32,
21}
22
23/// Source locations recovered from an input Wasm module's DWARF sections.
24///
25/// Wasm DWARF addresses are relative to the beginning of the code-section
26/// payload, whereas [`FunctionBodyData`] offsets are relative to the complete
27/// module. This map performs that translation once before parallel codegen.
28#[derive(Default)]
29pub struct WasmSourceMap {
30    locations: BTreeMap<usize, SourceLocation>,
31}
32
33impl WasmSourceMap {
34    /// Build a source map for all local function operators in a module.
35    pub fn new(
36        module: &ModuleInfo,
37        translation: &ModuleTranslationState,
38        functions: &PrimaryMap<LocalFunctionIndex, FunctionBodyData<'_>>,
39    ) -> Result<Self, String> {
40        let Some(code_base) = translation.code_section_offset() else {
41            // We must support a Wasm modules without code section.
42            return Ok(Self::default());
43        };
44
45        type Reader = EndianRcSlice<LittleEndian>;
46        let dwarf = match Dwarf::<Reader>::load(|id: SectionId| {
47            let data = module.custom_sections(id.name()).next().unwrap_or_default();
48            Ok::<_, addr2line::gimli::Error>(Reader::new(Rc::from(data), LittleEndian))
49        }) {
50            Ok(dwarf) => dwarf,
51            Err(err) => return Err(format!("cannot parse DWARF file: {err}")),
52        };
53        let context = match addr2line::Context::from_dwarf(dwarf) {
54            Ok(context) => context,
55            Err(err) => return Err(format!("cannot construct addr2line Context: {err}")),
56        };
57
58        let mut locations = BTreeMap::new();
59        for (_, input) in functions.iter() {
60            let body = FunctionBody::new(BinaryReader::new(input.data, input.module_offset));
61            let Ok(mut operators) = body.get_operators_reader() else {
62                continue;
63            };
64            while !operators.eof() {
65                let offset = operators.original_position();
66                if operators.read().is_err() {
67                    return Err("Wasm parsing error".to_string());
68                }
69                let address = offset.checked_sub(code_base).ok_or_else(|| {
70                    format!(
71                        "Wasm operator offset {offset} precedes code section offset {code_base}"
72                    )
73                })? as u64;
74                let Ok(Some(location)) = context.find_location(address) else {
75                    continue;
76                };
77                let (Some(file), Some(line)) = (location.file, location.line) else {
78                    continue;
79                };
80                let path = PathBuf::from(file);
81                let directory = path
82                    .parent()
83                    .and_then(|p| p.to_str().map(|p| p.to_string()))
84                    .unwrap_or_default();
85                let filename = path
86                    .file_name()
87                    .and_then(|p| p.to_str().map(|p| p.to_string()))
88                    .unwrap_or_default();
89                locations.insert(
90                    offset,
91                    SourceLocation {
92                        file: filename,
93                        directory,
94                        line,
95                        column: location.column.unwrap_or(0),
96                    },
97                );
98            }
99        }
100
101        Ok(Self { locations })
102    }
103
104    /// Return the source location for a module-relative Wasm offset.
105    pub fn get(&self, wasm_offset: usize) -> Option<&SourceLocation> {
106        self.locations.get(&wasm_offset)
107    }
108
109    /// Return the first mapped source location in a function body.
110    pub fn first_in_function(&self, body: &FunctionBodyData<'_>) -> Option<&SourceLocation> {
111        self.locations
112            .range(body.module_offset..body.module_offset.saturating_add(body.data.len()))
113            .next()
114            .map(|(_, location)| location)
115    }
116}