wasmtime_cranelift/debug/transform/
line_program.rs

1use super::address_transform::AddressTransform;
2use super::attr::clone_attr_string;
3use super::{Reader, TransformError};
4use anyhow::{Context, Error};
5use gimli::{
6    write, DebugLine, DebugLineOffset, DebugLineStr, DebugStr, DebugStrOffsets,
7    DebuggingInformationEntry, LineEncoding, Unit,
8};
9use wasmtime_environ::{DefinedFuncIndex, EntityRef};
10
11#[derive(Debug)]
12enum SavedLineProgramRow {
13    Normal {
14        address: u64,
15        op_index: u64,
16        file_index: u64,
17        line: u64,
18        column: u64,
19        discriminator: u64,
20        is_stmt: bool,
21        basic_block: bool,
22        prologue_end: bool,
23        epilogue_begin: bool,
24        isa: u64,
25    },
26    EndOfSequence(u64),
27}
28
29#[derive(Debug)]
30struct FuncRows {
31    index: DefinedFuncIndex,
32    sorted_rows: Vec<(u64, SavedLineProgramRow)>,
33}
34
35#[derive(Debug, Eq, PartialEq)]
36enum ReadLineProgramState {
37    SequenceEnded,
38    ReadSequence(DefinedFuncIndex),
39    IgnoreSequence,
40}
41
42pub(crate) fn clone_line_program<R>(
43    unit: &Unit<R, R::Offset>,
44    root: &DebuggingInformationEntry<R>,
45    addr_tr: &AddressTransform,
46    out_encoding: gimli::Encoding,
47    debug_str: &DebugStr<R>,
48    debug_str_offsets: &DebugStrOffsets<R>,
49    debug_line_str: &DebugLineStr<R>,
50    debug_line: &DebugLine<R>,
51    out_strings: &mut write::StringTable,
52) -> Result<(write::LineProgram, DebugLineOffset, Vec<write::FileId>, u64), Error>
53where
54    R: Reader,
55{
56    let offset = match root.attr_value(gimli::DW_AT_stmt_list)? {
57        Some(gimli::AttributeValue::DebugLineRef(offset)) => offset,
58        _ => {
59            return Err(TransformError("Debug line offset is not found").into());
60        }
61    };
62    let comp_dir = root.attr_value(gimli::DW_AT_comp_dir)?;
63    let comp_name = root.attr_value(gimli::DW_AT_name)?;
64    let out_comp_dir = match &comp_dir {
65        Some(comp_dir) => Some(clone_attr_string(
66            comp_dir,
67            gimli::DW_FORM_strp,
68            unit,
69            debug_str,
70            debug_str_offsets,
71            debug_line_str,
72            out_strings,
73        )?),
74        None => None,
75    };
76    let out_comp_name = clone_attr_string(
77        comp_name.as_ref().context("missing DW_AT_name attribute")?,
78        gimli::DW_FORM_strp,
79        unit,
80        debug_str,
81        debug_str_offsets,
82        debug_line_str,
83        out_strings,
84    )?;
85
86    let program = debug_line.program(
87        offset,
88        unit.header.address_size(),
89        comp_dir.and_then(|val| val.string_value(&debug_str)),
90        comp_name.and_then(|val| val.string_value(&debug_str)),
91    );
92    if let Ok(program) = program {
93        let header = program.header();
94        let file_index_base = if header.version() < 5 { 1 } else { 0 };
95        assert!(header.version() <= 5, "not supported 6");
96        let line_encoding = LineEncoding {
97            minimum_instruction_length: header.minimum_instruction_length(),
98            maximum_operations_per_instruction: header.maximum_operations_per_instruction(),
99            default_is_stmt: header.default_is_stmt(),
100            line_base: header.line_base(),
101            line_range: header.line_range(),
102        };
103        let mut out_program = write::LineProgram::new(
104            out_encoding,
105            line_encoding,
106            out_comp_dir.unwrap_or_else(|| write::LineString::String(Vec::new())),
107            out_comp_name,
108            None,
109        );
110        let mut dirs = Vec::new();
111        dirs.push(out_program.default_directory());
112        for dir_attr in header.include_directories() {
113            let dir_id = out_program.add_directory(clone_attr_string(
114                dir_attr,
115                gimli::DW_FORM_string,
116                unit,
117                debug_str,
118                debug_str_offsets,
119                debug_line_str,
120                out_strings,
121            )?);
122            dirs.push(dir_id);
123        }
124        let mut files = Vec::new();
125        // Since we are outputting DWARF-4, perform base change.
126        let directory_index_correction = if header.version() >= 5 { 1 } else { 0 };
127        for file_entry in header.file_names() {
128            let dir_index = file_entry.directory_index() + directory_index_correction;
129            let dir_id = dirs[dir_index as usize];
130            let file_id = out_program.add_file(
131                clone_attr_string(
132                    &file_entry.path_name(),
133                    gimli::DW_FORM_string,
134                    unit,
135                    debug_str,
136                    debug_str_offsets,
137                    debug_line_str,
138                    out_strings,
139                )?,
140                dir_id,
141                None,
142            );
143            files.push(file_id);
144        }
145
146        let mut rows = program.rows();
147        let mut func_rows = Vec::new();
148        let mut saved_rows: Vec<(u64, SavedLineProgramRow)> = Vec::new();
149        let mut state = ReadLineProgramState::SequenceEnded;
150        while let Some((_header, row)) = rows.next_row()? {
151            if state == ReadLineProgramState::IgnoreSequence {
152                if row.end_sequence() {
153                    state = ReadLineProgramState::SequenceEnded;
154                }
155                continue;
156            }
157            let saved_row = if row.end_sequence() {
158                let index = match state {
159                    ReadLineProgramState::ReadSequence(index) => index,
160                    _ => panic!(),
161                };
162                saved_rows.sort_by_key(|r| r.0);
163                func_rows.push(FuncRows {
164                    index,
165                    sorted_rows: saved_rows,
166                });
167
168                saved_rows = Vec::new();
169                state = ReadLineProgramState::SequenceEnded;
170                SavedLineProgramRow::EndOfSequence(row.address())
171            } else {
172                if state == ReadLineProgramState::SequenceEnded {
173                    // Discard sequences for non-existent code.
174                    if row.address() == 0 {
175                        state = ReadLineProgramState::IgnoreSequence;
176                        continue;
177                    }
178                    match addr_tr.find_func_index(row.address()) {
179                        Some(index) => {
180                            state = ReadLineProgramState::ReadSequence(index);
181                        }
182                        None => {
183                            // Some non-existent address found.
184                            state = ReadLineProgramState::IgnoreSequence;
185                            continue;
186                        }
187                    }
188                }
189                SavedLineProgramRow::Normal {
190                    address: row.address(),
191                    op_index: row.op_index(),
192                    file_index: row.file_index(),
193                    line: row.line().map(|nonzero| nonzero.get()).unwrap_or(0),
194                    column: match row.column() {
195                        gimli::ColumnType::LeftEdge => 0,
196                        gimli::ColumnType::Column(val) => val.get(),
197                    },
198                    discriminator: row.discriminator(),
199                    is_stmt: row.is_stmt(),
200                    basic_block: row.basic_block(),
201                    prologue_end: row.prologue_end(),
202                    epilogue_begin: row.epilogue_begin(),
203                    isa: row.isa(),
204                }
205            };
206            saved_rows.push((row.address(), saved_row));
207        }
208
209        for FuncRows {
210            index,
211            sorted_rows: saved_rows,
212        } in func_rows
213        {
214            let map = match addr_tr.map().get(index) {
215                Some(map) if map.len > 0 => map,
216                _ => {
217                    continue; // no code generated
218                }
219            };
220            let symbol = index.index();
221            let base_addr = map.offset;
222            out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
223            // TODO track and place function declaration line here
224            let mut last_address = None;
225            for addr_map in map.addresses.iter() {
226                let saved_row = match saved_rows.binary_search_by_key(&addr_map.wasm, |i| i.0) {
227                    Ok(i) => Some(&saved_rows[i].1),
228                    Err(i) => {
229                        if i > 0 {
230                            Some(&saved_rows[i - 1].1)
231                        } else {
232                            None
233                        }
234                    }
235                };
236                if let Some(SavedLineProgramRow::Normal {
237                    address,
238                    op_index,
239                    file_index,
240                    line,
241                    column,
242                    discriminator,
243                    is_stmt,
244                    basic_block,
245                    prologue_end,
246                    epilogue_begin,
247                    isa,
248                }) = saved_row
249                {
250                    // Ignore duplicates
251                    if Some(*address) != last_address {
252                        let address_offset = if last_address.is_none() {
253                            // Extend first entry to the function declaration
254                            // TODO use the function declaration line instead
255                            0
256                        } else {
257                            (addr_map.generated - base_addr) as u64
258                        };
259                        out_program.row().address_offset = address_offset;
260                        out_program.row().op_index = *op_index;
261                        out_program.row().file = files[(file_index - file_index_base) as usize];
262                        out_program.row().line = *line;
263                        out_program.row().column = *column;
264                        out_program.row().discriminator = *discriminator;
265                        out_program.row().is_statement = *is_stmt;
266                        out_program.row().basic_block = *basic_block;
267                        out_program.row().prologue_end = *prologue_end;
268                        out_program.row().epilogue_begin = *epilogue_begin;
269                        out_program.row().isa = *isa;
270                        out_program.generate_row();
271                        last_address = Some(*address);
272                    }
273                }
274            }
275            let end_addr = (map.offset + map.len) as u64;
276            out_program.end_sequence(end_addr);
277        }
278        Ok((out_program, offset, files, file_index_base))
279    } else {
280        Err(TransformError("Valid line program not found").into())
281    }
282}