wasmtime_cranelift/debug/transform/
address_transform.rs

1use crate::{CompiledFunctions, FunctionAddressMap};
2use gimli::write;
3use std::collections::BTreeMap;
4use std::iter::FromIterator;
5use wasmtime_environ::{DefinedFuncIndex, EntityRef, FilePos, PrimaryMap, WasmFileInfo};
6
7pub type GeneratedAddress = usize;
8pub type WasmAddress = u64;
9
10/// Contains mapping of the generated address to its original
11/// source location.
12#[derive(Debug)]
13pub struct AddressMap {
14    pub generated: GeneratedAddress,
15    pub wasm: WasmAddress,
16}
17
18/// Information about generated function code: its body start,
19/// length, and instructions addresses.
20#[derive(Debug)]
21pub struct FunctionMap {
22    pub offset: GeneratedAddress,
23    pub len: GeneratedAddress,
24    pub wasm_start: WasmAddress,
25    pub wasm_end: WasmAddress,
26    pub addresses: Box<[AddressMap]>,
27}
28
29/// Mapping of the source location to its generated code range.
30#[derive(Debug)]
31struct Position {
32    wasm_pos: WasmAddress,
33    gen_start: GeneratedAddress,
34    gen_end: GeneratedAddress,
35}
36
37/// Mapping of continuous range of source location to its generated
38/// code. The positions are always in ascending order for search.
39#[derive(Debug)]
40struct Range {
41    wasm_start: WasmAddress,
42    wasm_end: WasmAddress,
43    gen_start: GeneratedAddress,
44    gen_end: GeneratedAddress,
45    positions: Box<[Position]>,
46}
47
48type RangeIndex = usize;
49
50/// Helper function address lookup data. Contains ranges start positions
51/// index and ranges data. The multiple ranges can include the same
52/// original source position. The index (B-Tree) uses range start
53/// position as a key. The index values reference the ranges array.
54/// The item are ordered RangeIndex.
55#[derive(Debug)]
56struct FuncLookup {
57    index: Vec<(WasmAddress, Box<[RangeIndex]>)>,
58    ranges: Box<[Range]>,
59}
60
61/// Mapping of original functions to generated code locations/ranges.
62#[derive(Debug)]
63struct FuncTransform {
64    start: WasmAddress,
65    end: WasmAddress,
66    index: DefinedFuncIndex,
67    lookup: FuncLookup,
68}
69
70/// Module functions mapping to generated code.
71#[derive(Debug)]
72pub struct AddressTransform {
73    map: PrimaryMap<DefinedFuncIndex, FunctionMap>,
74    func: Vec<(WasmAddress, FuncTransform)>,
75}
76
77/// Returns a wasm bytecode offset in the code section from SourceLoc.
78fn get_wasm_code_offset(loc: FilePos, code_section_offset: u64) -> WasmAddress {
79    // Code section size <= 4GB, allow wrapped SourceLoc to recover the overflow.
80    loc.file_offset()
81        .unwrap()
82        .wrapping_sub(code_section_offset as u32) as WasmAddress
83}
84
85fn build_function_lookup(
86    ft: &FunctionAddressMap,
87    code_section_offset: u64,
88) -> (WasmAddress, WasmAddress, FuncLookup) {
89    assert!(code_section_offset <= ft.start_srcloc.file_offset().unwrap().into());
90    let fn_start = get_wasm_code_offset(ft.start_srcloc, code_section_offset);
91    let fn_end = get_wasm_code_offset(ft.end_srcloc, code_section_offset);
92    assert!(fn_start <= fn_end);
93
94    // Build ranges of continuous source locations. The new ranges starts when
95    // non-descending order is interrupted. Assuming the same origin location can
96    // be present in multiple ranges.
97    let mut range_wasm_start = fn_start;
98    let mut range_gen_start = ft.body_offset;
99    let mut last_wasm_pos = range_wasm_start;
100    let mut ranges = Vec::new();
101    let mut ranges_index = BTreeMap::new();
102    let mut current_range = Vec::new();
103    let mut last_gen_inst_empty = false;
104    for (i, t) in ft.instructions.iter().enumerate() {
105        if t.srcloc.file_offset().is_none() {
106            continue;
107        }
108
109        let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
110        assert!(fn_start <= offset);
111        assert!(offset <= fn_end);
112
113        let inst_gen_start = t.code_offset as usize;
114        let inst_gen_end = match ft.instructions.get(i + 1) {
115            Some(i) => i.code_offset as usize,
116            None => ft.body_len as usize,
117        };
118
119        if last_wasm_pos > offset {
120            // Start new range.
121            ranges_index.insert(range_wasm_start, ranges.len());
122            ranges.push(Range {
123                wasm_start: range_wasm_start,
124                wasm_end: last_wasm_pos,
125                gen_start: range_gen_start,
126                gen_end: inst_gen_start,
127                positions: current_range.into_boxed_slice(),
128            });
129            range_wasm_start = offset;
130            range_gen_start = inst_gen_start;
131            current_range = Vec::new();
132            last_gen_inst_empty = false;
133        }
134        if last_gen_inst_empty && current_range.last().unwrap().gen_start == inst_gen_start {
135            // It is possible that previous inst_gen_start == inst_gen_end, so
136            // make an attempt to merge all such positions with current one.
137            if inst_gen_start < inst_gen_end {
138                let last = current_range.last_mut().unwrap();
139                last.gen_end = inst_gen_end;
140                last_gen_inst_empty = false;
141            }
142        } else {
143            // Continue existing range: add new wasm->generated code position.
144            current_range.push(Position {
145                wasm_pos: offset,
146                gen_start: inst_gen_start,
147                gen_end: inst_gen_end,
148            });
149            // Track if last position was empty (see if-branch above).
150            last_gen_inst_empty = inst_gen_start == inst_gen_end;
151        }
152        last_wasm_pos = offset;
153    }
154    let last_gen_addr = ft.body_offset + ft.body_len as usize;
155    ranges_index.insert(range_wasm_start, ranges.len());
156    ranges.push(Range {
157        wasm_start: range_wasm_start,
158        wasm_end: fn_end,
159        gen_start: range_gen_start,
160        gen_end: last_gen_addr,
161        positions: current_range.into_boxed_slice(),
162    });
163
164    // Making ranges lookup faster by building index: B-tree with every range
165    // start position that maps into list of active ranges at this position.
166    let ranges = ranges.into_boxed_slice();
167    let mut active_ranges = Vec::new();
168    let mut index = BTreeMap::new();
169    let mut last_wasm_pos = None;
170    for (wasm_start, range_index) in ranges_index {
171        if Some(wasm_start) == last_wasm_pos {
172            active_ranges.push(range_index);
173            continue;
174        }
175        if let Some(position) = last_wasm_pos {
176            let mut sorted_ranges = active_ranges.clone();
177            sorted_ranges.sort();
178            index.insert(position, sorted_ranges.into_boxed_slice());
179        }
180        active_ranges.retain(|r| ranges[*r].wasm_end.cmp(&wasm_start) != std::cmp::Ordering::Less);
181        active_ranges.push(range_index);
182        last_wasm_pos = Some(wasm_start);
183    }
184    active_ranges.sort();
185    index.insert(last_wasm_pos.unwrap(), active_ranges.into_boxed_slice());
186    let index = Vec::from_iter(index.into_iter());
187    (fn_start, fn_end, FuncLookup { index, ranges })
188}
189
190fn build_function_addr_map(
191    funcs: &CompiledFunctions,
192    code_section_offset: u64,
193) -> PrimaryMap<DefinedFuncIndex, FunctionMap> {
194    let mut map = PrimaryMap::new();
195    for (_, f) in funcs {
196        let ft = &f.address_map;
197        let mut fn_map = Vec::new();
198        for t in ft.instructions.iter() {
199            if t.srcloc.file_offset().is_none() {
200                continue;
201            }
202            let offset = get_wasm_code_offset(t.srcloc, code_section_offset);
203            fn_map.push(AddressMap {
204                generated: t.code_offset as usize,
205                wasm: offset,
206            });
207        }
208
209        if cfg!(debug_assertions) {
210            // fn_map is sorted by the generated field -- see FunctionAddressMap::instructions.
211            for i in 1..fn_map.len() {
212                assert!(fn_map[i - 1].generated <= fn_map[i].generated);
213            }
214        }
215
216        map.push(FunctionMap {
217            offset: ft.body_offset,
218            len: ft.body_len as usize,
219            wasm_start: get_wasm_code_offset(ft.start_srcloc, code_section_offset),
220            wasm_end: get_wasm_code_offset(ft.end_srcloc, code_section_offset),
221            addresses: fn_map.into_boxed_slice(),
222        });
223    }
224    map
225}
226
227// Utility iterator to find all ranges starts for specific Wasm address.
228// The iterator returns generated addresses sorted by RangeIndex.
229struct TransformRangeStartIter<'a> {
230    addr: WasmAddress,
231    indices: &'a [RangeIndex],
232    ranges: &'a [Range],
233}
234
235impl<'a> TransformRangeStartIter<'a> {
236    fn new(func: &'a FuncTransform, addr: WasmAddress) -> Self {
237        let found = match func
238            .lookup
239            .index
240            .binary_search_by(|entry| entry.0.cmp(&addr))
241        {
242            Ok(i) => Some(&func.lookup.index[i].1),
243            Err(i) => {
244                if i > 0 {
245                    Some(&func.lookup.index[i - 1].1)
246                } else {
247                    None
248                }
249            }
250        };
251        if let Some(range_indices) = found {
252            TransformRangeStartIter {
253                addr,
254                indices: range_indices,
255                ranges: &func.lookup.ranges,
256            }
257        } else {
258            unreachable!();
259        }
260    }
261}
262
263impl<'a> Iterator for TransformRangeStartIter<'a> {
264    type Item = (GeneratedAddress, RangeIndex);
265    fn next(&mut self) -> Option<Self::Item> {
266        if let Some((first, tail)) = self.indices.split_first() {
267            let range_index = *first;
268            let range = &self.ranges[range_index];
269            self.indices = tail;
270            let address = match range
271                .positions
272                .binary_search_by(|a| a.wasm_pos.cmp(&self.addr))
273            {
274                Ok(i) => range.positions[i].gen_start,
275                Err(i) => {
276                    if i == 0 {
277                        range.gen_start
278                    } else {
279                        range.positions[i - 1].gen_end
280                    }
281                }
282            };
283            Some((address, range_index))
284        } else {
285            None
286        }
287    }
288}
289
290// Utility iterator to find all ranges ends for specific Wasm address.
291// The iterator returns generated addresses sorted by RangeIndex.
292struct TransformRangeEndIter<'a> {
293    addr: WasmAddress,
294    indices: &'a [RangeIndex],
295    ranges: &'a [Range],
296}
297
298impl<'a> TransformRangeEndIter<'a> {
299    fn new(func: &'a FuncTransform, addr: WasmAddress) -> Self {
300        let found = match func
301            .lookup
302            .index
303            .binary_search_by(|entry| entry.0.cmp(&addr))
304        {
305            Ok(i) => Some(&func.lookup.index[i].1),
306            Err(i) => {
307                if i > 0 {
308                    Some(&func.lookup.index[i - 1].1)
309                } else {
310                    None
311                }
312            }
313        };
314        if let Some(range_indices) = found {
315            TransformRangeEndIter {
316                addr,
317                indices: range_indices,
318                ranges: &func.lookup.ranges,
319            }
320        } else {
321            unreachable!();
322        }
323    }
324}
325
326impl<'a> Iterator for TransformRangeEndIter<'a> {
327    type Item = (GeneratedAddress, RangeIndex);
328    fn next(&mut self) -> Option<Self::Item> {
329        while let Some((first, tail)) = self.indices.split_first() {
330            let range_index = *first;
331            let range = &self.ranges[range_index];
332            self.indices = tail;
333            if range.wasm_start >= self.addr {
334                continue;
335            }
336            let address = match range
337                .positions
338                .binary_search_by(|a| a.wasm_pos.cmp(&self.addr))
339            {
340                Ok(i) => range.positions[i].gen_end,
341                Err(i) => {
342                    if i == range.positions.len() {
343                        range.gen_end
344                    } else {
345                        range.positions[i].gen_start
346                    }
347                }
348            };
349            return Some((address, range_index));
350        }
351        None
352    }
353}
354
355// Utility iterator to iterate by translated function ranges.
356pub struct TransformRangeIter<'a> {
357    func: &'a FuncTransform,
358    start_it: TransformRangeStartIter<'a>,
359    end_it: TransformRangeEndIter<'a>,
360    last_start: Option<(GeneratedAddress, RangeIndex)>,
361    last_end: Option<(GeneratedAddress, RangeIndex)>,
362    last_item: Option<(GeneratedAddress, GeneratedAddress)>,
363}
364
365impl<'a> TransformRangeIter<'a> {
366    fn new(func: &'a FuncTransform, start: WasmAddress, end: WasmAddress) -> Self {
367        let mut start_it = TransformRangeStartIter::new(func, start);
368        let last_start = start_it.next();
369        let mut end_it = TransformRangeEndIter::new(func, end);
370        let last_end = end_it.next();
371        TransformRangeIter {
372            func,
373            start_it,
374            end_it,
375            last_start,
376            last_end,
377            last_item: None,
378        }
379    }
380}
381
382impl<'a> Iterator for TransformRangeIter<'a> {
383    type Item = (GeneratedAddress, GeneratedAddress);
384    fn next(&mut self) -> Option<Self::Item> {
385        loop {
386            // Merge TransformRangeStartIter and TransformRangeEndIter data using
387            // FuncLookup index's field propery to be sorted by RangeIndex.
388            let (start, end, range_index): (
389                Option<GeneratedAddress>,
390                Option<GeneratedAddress>,
391                RangeIndex,
392            ) = {
393                match (self.last_start.as_ref(), self.last_end.as_ref()) {
394                    (Some((s, sri)), Some((e, eri))) => {
395                        if sri == eri {
396                            // Start and end RangeIndex matched.
397                            (Some(*s), Some(*e), *sri)
398                        } else if sri < eri {
399                            (Some(*s), None, *sri)
400                        } else {
401                            (None, Some(*e), *eri)
402                        }
403                    }
404                    (Some((s, sri)), None) => (Some(*s), None, *sri),
405                    (None, Some((e, eri))) => (None, Some(*e), *eri),
406                    (None, None) => {
407                        // Reached ends for start and end iterators.
408                        return None;
409                    }
410                }
411            };
412            let range_start = match start {
413                Some(range_start) => {
414                    // Consume start iterator.
415                    self.last_start = self.start_it.next();
416                    range_start
417                }
418                None => {
419                    let range = &self.func.lookup.ranges[range_index];
420                    range.gen_start
421                }
422            };
423            let range_end = match end {
424                Some(range_end) => {
425                    // Consume end iterator.
426                    self.last_end = self.end_it.next();
427                    range_end
428                }
429                None => {
430                    let range = &self.func.lookup.ranges[range_index];
431                    range.gen_end
432                }
433            };
434
435            if cfg!(debug_assertions) {
436                match self.last_item.replace((range_start, range_end)) {
437                    Some((_, last_end)) => debug_assert!(last_end <= range_start),
438                    None => (),
439                }
440            }
441
442            if range_start < range_end {
443                return Some((range_start, range_end));
444            }
445            // Throw away empty ranges.
446            debug_assert!(range_start == range_end);
447        }
448    }
449}
450
451impl AddressTransform {
452    pub fn new(funcs: &CompiledFunctions, wasm_file: &WasmFileInfo) -> Self {
453        let code_section_offset = wasm_file.code_section_offset;
454
455        let mut func = BTreeMap::new();
456        for (i, f) in funcs {
457            let ft = &f.address_map;
458            let (fn_start, fn_end, lookup) = build_function_lookup(ft, code_section_offset);
459
460            func.insert(
461                fn_start,
462                FuncTransform {
463                    start: fn_start,
464                    end: fn_end,
465                    index: i,
466                    lookup,
467                },
468            );
469        }
470
471        let map = build_function_addr_map(funcs, code_section_offset);
472        let func = Vec::from_iter(func.into_iter());
473        AddressTransform { map, func }
474    }
475
476    fn find_func(&self, addr: u64) -> Option<&FuncTransform> {
477        // TODO check if we need to include end address
478        let func = match self.func.binary_search_by(|entry| entry.0.cmp(&addr)) {
479            Ok(i) => &self.func[i].1,
480            Err(i) => {
481                if i > 0 {
482                    &self.func[i - 1].1
483                } else {
484                    return None;
485                }
486            }
487        };
488        if addr >= func.start {
489            return Some(func);
490        }
491        None
492    }
493
494    pub fn find_func_index(&self, addr: u64) -> Option<DefinedFuncIndex> {
495        self.find_func(addr).map(|f| f.index)
496    }
497
498    pub fn translate_raw(&self, addr: u64) -> Option<(DefinedFuncIndex, GeneratedAddress)> {
499        if addr == 0 {
500            // It's normally 0 for debug info without the linked code.
501            return None;
502        }
503        if let Some(func) = self.find_func(addr) {
504            if addr == func.end {
505                // Clamp last address to the end to extend translation to the end
506                // of the function.
507                let map = &self.map[func.index];
508                return Some((func.index, map.len));
509            }
510            let first_result = TransformRangeStartIter::new(func, addr).next();
511            first_result.map(|(address, _)| (func.index, address))
512        } else {
513            // Address was not found: function was not compiled?
514            None
515        }
516    }
517
518    pub fn can_translate_address(&self, addr: u64) -> bool {
519        self.translate(addr).is_some()
520    }
521
522    pub fn translate(&self, addr: u64) -> Option<write::Address> {
523        self.translate_raw(addr)
524            .map(|(func_index, address)| write::Address::Symbol {
525                symbol: func_index.index(),
526                addend: address as i64,
527            })
528    }
529
530    pub fn translate_ranges_raw<'a>(
531        &'a self,
532        start: u64,
533        end: u64,
534    ) -> Option<(DefinedFuncIndex, impl Iterator<Item = (usize, usize)> + 'a)> {
535        if start == 0 {
536            // It's normally 0 for debug info without the linked code.
537            return None;
538        }
539        if let Some(func) = self.find_func(start) {
540            let result = TransformRangeIter::new(func, start, end);
541            return Some((func.index, result));
542        }
543        // Address was not found: function was not compiled?
544        None
545    }
546
547    pub fn translate_ranges<'a>(
548        &'a self,
549        start: u64,
550        end: u64,
551    ) -> impl Iterator<Item = (write::Address, u64)> + 'a {
552        enum TranslateRangesResult<'a> {
553            Empty,
554            Raw {
555                symbol: usize,
556                it: Box<dyn Iterator<Item = (usize, usize)> + 'a>,
557            },
558        }
559        impl<'a> Iterator for TranslateRangesResult<'a> {
560            type Item = (write::Address, u64);
561            fn next(&mut self) -> Option<Self::Item> {
562                match self {
563                    TranslateRangesResult::Empty => None,
564                    TranslateRangesResult::Raw { symbol, it } => match it.next() {
565                        Some((start, end)) => {
566                            debug_assert!(start < end);
567                            Some((
568                                write::Address::Symbol {
569                                    symbol: *symbol,
570                                    addend: start as i64,
571                                },
572                                (end - start) as u64,
573                            ))
574                        }
575                        None => None,
576                    },
577                }
578            }
579        }
580
581        match self.translate_ranges_raw(start, end) {
582            Some((func_index, ranges)) => TranslateRangesResult::Raw {
583                symbol: func_index.index(),
584                it: Box::new(ranges),
585            },
586            None => TranslateRangesResult::Empty,
587        }
588    }
589
590    pub fn map(&self) -> &PrimaryMap<DefinedFuncIndex, FunctionMap> {
591        &self.map
592    }
593
594    pub fn func_range(&self, index: DefinedFuncIndex) -> (GeneratedAddress, GeneratedAddress) {
595        let map = &self.map[index];
596        (map.offset, map.offset + map.len)
597    }
598
599    pub fn func_source_range(&self, index: DefinedFuncIndex) -> (WasmAddress, WasmAddress) {
600        let map = &self.map[index];
601        (map.wasm_start, map.wasm_end)
602    }
603}
604
605#[cfg(test)]
606mod tests {
607    use super::{build_function_lookup, get_wasm_code_offset, AddressTransform};
608    use crate::{CompiledFunction, FunctionAddressMap};
609    use cranelift_entity::PrimaryMap;
610    use gimli::write::Address;
611    use std::iter::FromIterator;
612    use std::mem;
613    use wasmtime_environ::{FilePos, InstructionAddressMap, WasmFileInfo};
614
615    #[test]
616    fn test_get_wasm_code_offset() {
617        let offset = get_wasm_code_offset(FilePos::new(3), 1);
618        assert_eq!(2, offset);
619        let offset = get_wasm_code_offset(FilePos::new(16), 0xF000_0000);
620        assert_eq!(0x1000_0010, offset);
621        let offset = get_wasm_code_offset(FilePos::new(1), 0x20_8000_0000);
622        assert_eq!(0x8000_0001, offset);
623    }
624
625    fn create_simple_func(wasm_offset: u32) -> FunctionAddressMap {
626        FunctionAddressMap {
627            instructions: vec![
628                InstructionAddressMap {
629                    srcloc: FilePos::new(wasm_offset + 2),
630                    code_offset: 5,
631                },
632                InstructionAddressMap {
633                    srcloc: FilePos::default(),
634                    code_offset: 8,
635                },
636                InstructionAddressMap {
637                    srcloc: FilePos::new(wasm_offset + 7),
638                    code_offset: 15,
639                },
640                InstructionAddressMap {
641                    srcloc: FilePos::default(),
642                    code_offset: 23,
643                },
644            ]
645            .into(),
646            start_srcloc: FilePos::new(wasm_offset),
647            end_srcloc: FilePos::new(wasm_offset + 10),
648            body_offset: 0,
649            body_len: 30,
650        }
651    }
652
653    #[test]
654    fn test_build_function_lookup_simple() {
655        let input = create_simple_func(11);
656        let (start, end, lookup) = build_function_lookup(&input, 1);
657        assert_eq!(10, start);
658        assert_eq!(20, end);
659
660        assert_eq!(1, lookup.index.len());
661        let index_entry = lookup.index.into_iter().next().unwrap();
662        assert_eq!((10u64, vec![0].into_boxed_slice()), index_entry);
663        assert_eq!(1, lookup.ranges.len());
664        let range = &lookup.ranges[0];
665        assert_eq!(10, range.wasm_start);
666        assert_eq!(20, range.wasm_end);
667        assert_eq!(0, range.gen_start);
668        assert_eq!(30, range.gen_end);
669        let positions = &range.positions;
670        assert_eq!(2, positions.len());
671        assert_eq!(12, positions[0].wasm_pos);
672        assert_eq!(5, positions[0].gen_start);
673        assert_eq!(8, positions[0].gen_end);
674        assert_eq!(17, positions[1].wasm_pos);
675        assert_eq!(15, positions[1].gen_start);
676        assert_eq!(23, positions[1].gen_end);
677    }
678
679    #[test]
680    fn test_build_function_lookup_two_ranges() {
681        let mut input = create_simple_func(11);
682        // append instruction with same srcloc as input.instructions[0]
683        let mut list = Vec::from(mem::take(&mut input.instructions));
684        list.push(InstructionAddressMap {
685            srcloc: FilePos::new(11 + 2),
686            code_offset: 23,
687        });
688        list.push(InstructionAddressMap {
689            srcloc: FilePos::default(),
690            code_offset: 26,
691        });
692        input.instructions = list.into();
693        let (start, end, lookup) = build_function_lookup(&input, 1);
694        assert_eq!(10, start);
695        assert_eq!(20, end);
696
697        assert_eq!(2, lookup.index.len());
698        let index_entries = Vec::from_iter(lookup.index.into_iter());
699        assert_eq!((10u64, vec![0].into_boxed_slice()), index_entries[0]);
700        assert_eq!((12u64, vec![0, 1].into_boxed_slice()), index_entries[1]);
701        assert_eq!(2, lookup.ranges.len());
702
703        let range = &lookup.ranges[0];
704        assert_eq!(10, range.wasm_start);
705        assert_eq!(17, range.wasm_end);
706        assert_eq!(0, range.gen_start);
707        assert_eq!(23, range.gen_end);
708        let positions = &range.positions;
709        assert_eq!(2, positions.len());
710        assert_eq!(12, positions[0].wasm_pos);
711        assert_eq!(5, positions[0].gen_start);
712        assert_eq!(8, positions[0].gen_end);
713        assert_eq!(17, positions[1].wasm_pos);
714        assert_eq!(15, positions[1].gen_start);
715        assert_eq!(23, positions[1].gen_end);
716
717        let range = &lookup.ranges[1];
718        assert_eq!(12, range.wasm_start);
719        assert_eq!(20, range.wasm_end);
720        assert_eq!(23, range.gen_start);
721        assert_eq!(30, range.gen_end);
722        let positions = &range.positions;
723        assert_eq!(1, positions.len());
724        assert_eq!(12, positions[0].wasm_pos);
725        assert_eq!(23, positions[0].gen_start);
726        assert_eq!(26, positions[0].gen_end);
727    }
728
729    #[test]
730    fn test_addr_translate() {
731        let func = CompiledFunction {
732            address_map: create_simple_func(11),
733            ..Default::default()
734        };
735        let input = PrimaryMap::from_iter([&func]);
736        let at = AddressTransform::new(
737            &input,
738            &WasmFileInfo {
739                path: None,
740                code_section_offset: 1,
741                imported_func_count: 0,
742                funcs: Vec::new(),
743            },
744        );
745
746        let addr = at.translate(10);
747        assert_eq!(
748            Some(Address::Symbol {
749                symbol: 0,
750                addend: 0,
751            }),
752            addr
753        );
754
755        let addr = at.translate(20);
756        assert_eq!(
757            Some(Address::Symbol {
758                symbol: 0,
759                addend: 30,
760            }),
761            addr
762        );
763
764        let addr = at.translate(0);
765        assert_eq!(None, addr);
766
767        let addr = at.translate(12);
768        assert_eq!(
769            Some(Address::Symbol {
770                symbol: 0,
771                addend: 5,
772            }),
773            addr
774        );
775
776        let addr = at.translate(18);
777        assert_eq!(
778            Some(Address::Symbol {
779                symbol: 0,
780                addend: 23,
781            }),
782            addr
783        );
784    }
785}