Skip to main content

i8051_disassembler/
region.rs

1use i8051::{ControlFlow, Instruction};
2use std::collections::BTreeMap;
3use std::range::Range;
4
5use crate::address::{
6    AddressSpace, AddressValue, PhysicalAddr, Xref, branch_target, branch_target_operand_index,
7    xrefs_from_instruction, xrefs_to_target,
8};
9use crate::command::Command;
10use crate::db::{
11    Equivalent, EquivalentAt, EquivalentRange, Error, Function, Line, OperandOverride,
12};
13use crate::labels::{ImplicitLabels, LabelCollector, Labels};
14
15#[derive(Debug, Clone)]
16pub enum ByteRange {
17    Mapped(String, usize, Vec<u8>),
18    Constant(AddressValue, u8),
19}
20
21pub struct Region {
22    byte_ranges: BTreeMap<AddressValue, ByteRange>,
23    equivalents: BTreeMap<AddressValue, EquivalentRange>,
24    labels: BTreeMap<AddressValue, String>,
25    comments: BTreeMap<AddressValue, String>,
26    functions: BTreeMap<AddressValue, Function>,
27}
28
29impl Region {
30    pub fn new() -> Self {
31        Self {
32            byte_ranges: BTreeMap::new(),
33            equivalents: BTreeMap::new(),
34            labels: BTreeMap::new(),
35            comments: BTreeMap::new(),
36            functions: BTreeMap::new(),
37        }
38    }
39
40    pub fn set_bytes(
41        &mut self,
42        file: &str,
43        file_offset: usize,
44        offset: AddressValue,
45        bytes: &[u8],
46    ) {
47        self.map_bytes(file, file_offset, offset, bytes);
48    }
49
50    pub fn map_bytes(
51        &mut self,
52        file: &str,
53        file_offset: usize,
54        offset: AddressValue,
55        bytes: &[u8],
56    ) {
57        if !bytes.is_empty() {
58            self.clear_bytes(offset, bytes.len() as AddressValue);
59        }
60        self.byte_ranges.insert(
61            offset,
62            ByteRange::Mapped(file.to_string(), file_offset, bytes.to_vec()),
63        );
64    }
65
66    pub fn set_constant(&mut self, offset: AddressValue, size: AddressValue, value: u8) {
67        if size == 0 {
68            return;
69        }
70        self.clear_bytes(offset, size);
71        self.byte_ranges
72            .insert(offset, ByteRange::Constant(size, value));
73    }
74
75    pub(crate) fn snapshot_byte_ranges(
76        &self,
77        offset: AddressValue,
78        size: AddressValue,
79    ) -> Vec<(AddressValue, ByteRange)> {
80        if size == 0 {
81            return Vec::new();
82        }
83        let end = offset.saturating_add(size);
84        self.byte_ranges
85            .iter()
86            .filter(|(start, range)| range_end(**start, range) > offset && **start < end)
87            .map(|(start, range)| (*start as AddressValue, range.clone()))
88            .collect()
89    }
90
91    pub fn clear_bytes(&mut self, offset: AddressValue, size: AddressValue) {
92        if size == 0 {
93            return;
94        }
95        let end = offset.saturating_add(size);
96        let mut kept = BTreeMap::new();
97
98        for (&start, range) in &self.byte_ranges {
99            match range {
100                ByteRange::Mapped(file, file_offset, data) => {
101                    let range_end = start.saturating_add(data.len() as AddressValue);
102                    if range_end <= offset || start >= end {
103                        kept.insert(start, range.clone());
104                        continue;
105                    }
106                    if start < offset {
107                        let keep_len = offset - start;
108                        kept.insert(
109                            start,
110                            ByteRange::Mapped(
111                                file.clone(),
112                                *file_offset,
113                                data[..keep_len as usize].to_vec(),
114                            ),
115                        );
116                    }
117                    if range_end > end {
118                        let skip = end.saturating_sub(start);
119                        kept.insert(
120                            end,
121                            ByteRange::Mapped(
122                                file.clone(),
123                                file_offset.saturating_add(skip as usize),
124                                data[skip as _..].to_vec(),
125                            ),
126                        );
127                    }
128                }
129                ByteRange::Constant(count, value) => {
130                    let range_end = start.saturating_add(*count);
131                    if range_end <= offset || start >= end {
132                        kept.insert(start, range.clone());
133                        continue;
134                    }
135                    if start < offset {
136                        kept.insert(start, ByteRange::Constant(offset - start, *value));
137                    }
138                    if range_end > end {
139                        kept.insert(end, ByteRange::Constant(range_end - end, *value));
140                    }
141                }
142            }
143        }
144
145        self.byte_ranges = kept;
146    }
147
148    pub fn set_equivalent(
149        &mut self,
150        offset: AddressValue,
151        equivalent: Equivalent,
152    ) -> Result<&EquivalentRange, Error> {
153        if matches!(self.equivalent_at(offset), EquivalentAt::Defined { .. }) {
154            return Err(Error::NotUndefined(offset));
155        }
156
157        let span = self.equivalent_span(offset, &equivalent)?;
158        self.validate_equivalent_bounds(offset, span)?;
159        self.validate_no_equivalent_overlap(offset, span)?;
160
161        self.equivalents.insert(
162            offset,
163            EquivalentRange {
164                end: offset.saturating_add(span),
165                equivalent,
166            },
167        );
168        Ok(&self.equivalents[&offset])
169    }
170
171    pub fn clear_equivalents(&mut self, offset: AddressValue, size: AddressValue) {
172        if size == 0 {
173            return;
174        }
175        let end = offset.saturating_add(size);
176        self.equivalents
177            .retain(|&start, range| range.end <= offset || start >= end);
178    }
179
180    pub fn snapshot_equivalents(
181        &self,
182        offset: AddressValue,
183        size: AddressValue,
184    ) -> Vec<(AddressValue, EquivalentRange)> {
185        if size == 0 {
186            return Vec::new();
187        }
188        let end = offset.saturating_add(size);
189        self.equivalents
190            .iter()
191            .filter(|(start, range)| ranges_overlap(**start, range.end, offset, end))
192            .map(|(&start, range)| (start, range.clone()))
193            .collect()
194    }
195
196    pub fn get_equivalent(&self, offset: AddressValue) -> EquivalentAt<'_> {
197        self.equivalent_at(offset)
198    }
199
200    fn equivalent_at(&self, offset: AddressValue) -> EquivalentAt<'_> {
201        if let Some((&start, range)) = self.equivalents.range(..=offset).next_back() {
202            if offset < range.end {
203                return EquivalentAt::Defined { start, range };
204            }
205        }
206        EquivalentAt::Undefined(self.undefined_range_at(offset))
207    }
208
209    fn undefined_range_at(&self, offset: AddressValue) -> Range<AddressValue> {
210        let after = offset.saturating_add(1);
211        let next_eq = self.equivalents.range(after..).next().map(|(&k, _)| k);
212        let next_lbl = self.labels.range(after..).next().map(|(&k, _)| k);
213        let next_cmt = self.comments.range(after..).next().map(|(&k, _)| k);
214        let end = [Some(self.end()), next_eq, next_lbl, next_cmt]
215            .into_iter()
216            .flatten()
217            .min()
218            .unwrap_or(self.end());
219        (offset..end).into()
220    }
221
222    pub fn set_label(&mut self, offset: AddressValue, label: &str) {
223        self.labels
224            .insert(offset as AddressValue, label.to_string());
225    }
226
227    pub fn clear_label(&mut self, offset: AddressValue) {
228        self.labels.remove(&(offset as AddressValue));
229    }
230
231    pub fn get_label(&self, offset: AddressValue) -> Option<&str> {
232        self.labels
233            .get(&(offset as AddressValue))
234            .map(String::as_str)
235    }
236
237    pub fn set_comment(&mut self, offset: AddressValue, comment: &str) {
238        self.comments
239            .insert(offset as AddressValue, comment.to_string());
240    }
241
242    pub fn clear_comment(&mut self, offset: AddressValue) {
243        self.comments.remove(&(offset as AddressValue));
244    }
245
246    pub fn get_comment(&self, offset: AddressValue) -> Option<&str> {
247        self.comments
248            .get(&(offset as AddressValue))
249            .map(String::as_str)
250    }
251
252    pub fn set_function(&mut self, function: Function) {
253        self.functions
254            .insert(function.addr.offset as AddressValue, function);
255    }
256
257    pub fn get_function(&self, offset: AddressValue) -> Option<&Function> {
258        self.functions.get(&(offset as AddressValue))
259    }
260
261    pub fn clear_function(&mut self, offset: AddressValue) {
262        self.functions.remove(&(offset as AddressValue));
263    }
264
265    pub fn bytes_at(&self, offset: AddressValue, size: AddressValue) -> Vec<u8> {
266        (0..size)
267            .filter_map(|i| self.read_byte(offset + i))
268            .collect()
269    }
270
271    pub(crate) fn render(
272        &self,
273        space: AddressSpace,
274        implicit_labels: &ImplicitLabels,
275    ) -> Vec<Line> {
276        let mut lines = Vec::new();
277        let start = self.start();
278        let end = self.end();
279        if start >= end {
280            return lines;
281        }
282
283        let default_labels = Labels::default();
284        let labels = implicit_labels.get(&space).unwrap_or(&default_labels);
285
286        let mut addr = start;
287        let mut need_org = true;
288        while addr < end {
289            if need_org {
290                lines.push(Line::Org { addr });
291                lines.push(Line::Blank);
292                need_org = false;
293            }
294
295            if let Some(function) = self.get_function(addr) {
296                lines.push(Line::Function {
297                    addr,
298                    name: function.name.clone(),
299                    signature: function.signature.clone(),
300                    length: function.length,
301                    noreturn: function.noreturn,
302                });
303            }
304            if let Some(comment) = self.get_comment(addr) {
305                lines.push(Line::Comment {
306                    addr,
307                    text: comment.to_string(),
308                });
309            }
310            if let Some(label) = self.get_label(addr) {
311                lines.push(Line::Label {
312                    addr,
313                    name: label.to_string(),
314                });
315            } else if let Some(label) = labels.get(&addr) {
316                lines.push(Line::Label {
317                    addr,
318                    name: label.to_string(),
319                });
320            }
321
322            match self.get_equivalent(addr) {
323                EquivalentAt::Defined { start: _, range } => match &range.equivalent {
324                    Equivalent::Code(overrides) => {
325                        let insn = self
326                            .decode_at(addr)
327                            .expect("validated code equivalent must decode");
328                        let text = self.format_instruction(addr, &insn, overrides);
329                        lines.push(Line::Instruction {
330                            addr,
331                            text,
332                            bytes: insn.bytes().to_vec(),
333                        });
334                        addr = range.end;
335                    }
336                    Equivalent::Data(data_type, size) => {
337                        let bytes = self.bytes_at(addr, *size);
338                        lines.push(Line::Data {
339                            addr,
340                            data_type: data_type.clone(),
341                            bytes,
342                        });
343                        addr = range.end;
344                    }
345                },
346                EquivalentAt::Undefined(undefined) => {
347                    let span = self.raw_run_until_next_annotation(addr, undefined.end, &labels);
348                    if span == 0 {
349                        if let Some((&next_mapped, _)) =
350                            self.byte_ranges.range(addr.saturating_add(1)..).next()
351                        {
352                            if next_mapped < undefined.end {
353                                addr = next_mapped;
354                                need_org = true;
355                                continue;
356                            }
357                        }
358                        addr += 1;
359                        continue;
360                    }
361                    let bytes = self.bytes_at(addr, span);
362                    lines.push(Line::Raw { addr, bytes });
363                    addr += span;
364                }
365            }
366        }
367
368        lines
369    }
370
371    pub(crate) fn to_commands(&self, space: AddressSpace) -> Vec<Command> {
372        let mut commands = Vec::new();
373        for (&offset, range) in &self.byte_ranges {
374            match range {
375                ByteRange::Mapped(file, file_offset, data) => {
376                    commands.push(Command::map_bytes(
377                        space,
378                        offset,
379                        file.clone(),
380                        *file_offset,
381                        data.len() as AddressValue,
382                    ));
383                }
384                ByteRange::Constant(size, value) => {
385                    commands.push(Command::set_constant_bytes(space, offset, *size, *value));
386                }
387            }
388        }
389        for (&offset, equivalent_range) in &self.equivalents {
390            commands.push(Command::set_equivalent(
391                space,
392                offset,
393                equivalent_range.equivalent.clone(),
394            ));
395        }
396        for (&offset, label) in &self.labels {
397            commands.push(Command::set_label(space, offset, label.clone()));
398        }
399        for (&offset, comment) in &self.comments {
400            commands.push(Command::set_comment(space, offset, comment.clone()));
401        }
402        for (&offset, function) in &self.functions {
403            commands.push(Command::set_function(space, offset, function.clone()));
404        }
405        commands
406    }
407
408    pub(crate) fn xrefs_to(&self, space: AddressSpace, target: &PhysicalAddr) -> Vec<Xref> {
409        let mut xrefs = Vec::new();
410        for (&offset, equivalent_range) in &self.equivalents {
411            if !matches!(equivalent_range.equivalent, Equivalent::Code(_)) {
412                continue;
413            }
414            let Some(instruction) = self.decode_at(offset) else {
415                continue;
416            };
417            let source = PhysicalAddr {
418                space,
419                offset: offset as AddressValue,
420            };
421            xrefs.extend(xrefs_to_target(&instruction, source, target));
422        }
423        xrefs
424    }
425
426    pub(crate) fn xrefs_from(&self, source: &PhysicalAddr) -> Vec<Xref> {
427        let offset = source.offset;
428        let Some(equivalent_range) = self.equivalents.get(&offset) else {
429            return Vec::new();
430        };
431        if !matches!(equivalent_range.equivalent, Equivalent::Code(_)) {
432            return Vec::new();
433        }
434        let Some(instruction) = self.decode_at(offset) else {
435            return Vec::new();
436        };
437        xrefs_from_instruction(&instruction, *source)
438    }
439
440    /// Collect all the necessary references for this region.
441    pub(crate) fn collect_refs(&self, space: AddressSpace, refs: &mut LabelCollector) {
442        for (&offset, equivalent_range) in &self.equivalents {
443            if !matches!(equivalent_range.equivalent, Equivalent::Code(_)) {
444                continue;
445            }
446            let Some(instruction) = self.decode_at(offset) else {
447                continue;
448            };
449            xrefs_from_instruction(&instruction, PhysicalAddr { space, offset })
450                .into_iter()
451                .for_each(|xref| {
452                    if self.get_label(xref.to.offset).is_none() {
453                        refs.collect(xref.to.space, xref.to.offset, None);
454                    }
455                });
456        }
457    }
458
459    fn start(&self) -> AddressValue {
460        [
461            self.byte_ranges.keys().copied().min(),
462            self.equivalents.keys().copied().min(),
463            self.labels.keys().copied().min(),
464            self.comments.keys().copied().min(),
465        ]
466        .into_iter()
467        .flatten()
468        .min()
469        .unwrap_or(0) as AddressValue
470    }
471
472    fn end(&self) -> AddressValue {
473        let mut end = 0;
474        for (&start, range) in &self.byte_ranges {
475            let range_end = match range {
476                ByteRange::Mapped(_, _, data) => start.saturating_add(data.len() as AddressValue),
477                ByteRange::Constant(size, _) => start.saturating_add(*size),
478            };
479            end = end.max(range_end);
480        }
481        for range in self.equivalents.values() {
482            end = end.max(range.end);
483        }
484        end.max(self.start()) as AddressValue
485    }
486
487    fn read_byte(&self, offset: AddressValue) -> Option<u8> {
488        let (&start, range) = self.byte_ranges.range(..=offset).next_back()?;
489        match range {
490            ByteRange::Mapped(_, _, data) => data.get((offset - start) as usize).copied(),
491            ByteRange::Constant(size, value) if offset - start < *size => Some(*value),
492            ByteRange::Constant(_, _) => None,
493        }
494    }
495
496    fn decode_at(&self, address: AddressValue) -> Option<Instruction> {
497        let mut available = Vec::with_capacity(Instruction::MAX_LENGTH);
498
499        for i in 0..Instruction::MAX_LENGTH as AddressValue {
500            if let Some(b) = self.read_byte(address + i) {
501                available.push(b);
502            } else {
503                break;
504            }
505        }
506        if available.is_empty() {
507            return None;
508        }
509
510        let ins = Instruction::decode_from_bytes(address as _, &available);
511        if ins.len() > available.len() {
512            return None;
513        }
514
515        Some(ins)
516    }
517
518    pub(crate) fn equivalent_span(
519        &self,
520        offset: AddressValue,
521        equivalent: &Equivalent,
522    ) -> Result<AddressValue, Error> {
523        match equivalent {
524            Equivalent::Code(_) => self
525                .decode_at(offset)
526                .map(|insn| insn.len() as AddressValue)
527                .ok_or(Error::InvalidEquivalent),
528            Equivalent::Data(_, size) => Ok(*size),
529        }
530    }
531
532    fn validate_equivalent_bounds(
533        &self,
534        offset: AddressValue,
535        span: AddressValue,
536    ) -> Result<(), Error> {
537        for i in 0..span {
538            if self.read_byte(offset + i).is_none() {
539                return Err(Error::InvalidAddress(offset + i));
540            }
541        }
542        Ok(())
543    }
544
545    fn validate_no_equivalent_overlap(
546        &self,
547        offset: AddressValue,
548        span: AddressValue,
549    ) -> Result<(), Error> {
550        let end = offset.saturating_add(span);
551        if let Some((&other_start, other)) = self.equivalents.range(..end).next_back() {
552            if other.end > offset {
553                return Err(Error::Overlap(other_start));
554            }
555        }
556        Ok(())
557    }
558
559    fn raw_run_until_next_annotation(
560        &self,
561        addr: AddressValue,
562        limit: AddressValue,
563        implicit_labels: &Labels,
564    ) -> AddressValue {
565        let after = addr.saturating_add(1);
566        let mut boundary = limit;
567        if let Some((&start, _)) = self.equivalents.range(after..).next() {
568            boundary = boundary.min(start);
569        }
570        if let Some((&start, _)) = self.labels.range(after..).next() {
571            boundary = boundary.min(start);
572        }
573        if let Some((&start, _)) = self.comments.range(after..).next() {
574            boundary = boundary.min(start);
575        }
576        if let Some((&start, _)) = implicit_labels.range(after..).next() {
577            boundary = boundary.min(start);
578        }
579
580        let mut end = addr;
581        while end < boundary {
582            if self.read_byte(end).is_none() {
583                break;
584            }
585            end += 1;
586        }
587        end - addr
588    }
589
590    fn format_instruction(
591        &self,
592        _addr: AddressValue,
593        insn: &Instruction,
594        overrides: &[Option<OperandOverride>],
595    ) -> String {
596        let decoded = insn.as_string();
597        let mut merged = overrides.to_vec();
598
599        if let (Some(target), Some(idx)) = (branch_target(insn), branch_target_operand_index(insn))
600        {
601            while merged.len() <= idx {
602                merged.push(None);
603            }
604            if merged[idx].is_none() {
605                if let Some(label) = self.get_label(target) {
606                    merged[idx] = Some(OperandOverride::Label(label.to_string()));
607                }
608            }
609        }
610
611        let text = if merged.iter().all(|o| o.is_none()) {
612            decoded
613        } else {
614            apply_operand_overrides(&decoded, &merged)
615        };
616        sdas_indent_instruction(&text)
617    }
618
619    /// Auto-disassembles code addresses recursively. Will not modify any address that already
620    /// has an equivalent.
621    pub fn auto_disassemble(&mut self, start: u32) -> Vec<AddressValue> {
622        let mut addresses = Vec::new();
623        let mut queue = Vec::new();
624        queue.push(start);
625        while let Some(addr) = queue.pop() {
626            if self.get_equivalent(addr).is_defined() {
627                continue;
628            }
629            addresses.push(addr);
630            let Ok(_) = self.set_equivalent(addr, Equivalent::Code(vec![])) else {
631                return addresses;
632            };
633            if let Some(ins) = self.decode_at(addr) {
634                let flow = ins.control_flow();
635                match flow {
636                    ControlFlow::Continue(addr) => queue.push(addr),
637                    ControlFlow::Call(next, addr) => {
638                        queue.push(next);
639                        queue.push(addr);
640                    }
641                    ControlFlow::Choice(next, addr) => {
642                        queue.push(next);
643                        queue.push(addr);
644                    }
645                    ControlFlow::Diverge => {
646                        continue;
647                    }
648                }
649            }
650        }
651        addresses
652    }
653}
654
655fn sdas_indent_instruction(text: &str) -> String {
656    if let Some((mnemonic, operands)) = text.split_once(' ') {
657        format!("    {mnemonic:<8}{operands}")
658    } else {
659        format!("    {text}")
660    }
661}
662
663fn split_instruction(decoded: &str) -> (&str, Vec<&str>) {
664    let (mnemonic, rest) = decoded.split_once(' ').unwrap_or((decoded, ""));
665    let operands = if rest.is_empty() {
666        Vec::new()
667    } else {
668        rest.split(',').map(str::trim).collect()
669    };
670    (mnemonic, operands)
671}
672
673fn apply_operand_overrides(decoded: &str, overrides: &[Option<OperandOverride>]) -> String {
674    if overrides.is_empty() {
675        return decoded.to_string();
676    }
677    let (mnemonic, operands) = split_instruction(decoded);
678    let mut out = mnemonic.to_string();
679    let operand_count = operands.len().max(overrides.len());
680    for idx in 0..operand_count {
681        if idx > 0 {
682            out.push(',');
683        } else {
684            out.push(' ');
685        }
686        match overrides.get(idx).and_then(|o| o.as_ref()) {
687            Some(OperandOverride::Label(label)) => out.push_str(label),
688            Some(OperandOverride::LabelOffset { label, offset }) => {
689                if *offset >= 0 {
690                    out.push_str(&format!("{label}+{offset}"));
691                } else {
692                    out.push_str(&format!("{label}{offset}"));
693                }
694            }
695            Some(OperandOverride::Text(text)) => out.push_str(text),
696            None => {
697                if let Some(default) = operands.get(idx) {
698                    out.push_str(default);
699                }
700            }
701        }
702    }
703    out
704}
705
706fn ranges_overlap(
707    a_start: AddressValue,
708    a_end: AddressValue,
709    b_start: AddressValue,
710    b_end: AddressValue,
711) -> bool {
712    a_start < b_end && b_start < a_end
713}
714
715fn range_end(start: AddressValue, range: &ByteRange) -> AddressValue {
716    match range {
717        ByteRange::Mapped(_, _, data) => start.saturating_add(data.len() as AddressValue),
718        ByteRange::Constant(size, _) => start.saturating_add(*size),
719    }
720}
721
722#[cfg(test)]
723mod tests {
724    use super::*;
725    use crate::db::{DataType, OperandOverride};
726
727    #[test]
728    fn overlapping_equivalents_are_rejected() {
729        let mut region = Region::new();
730        region.set_bytes(
731            "test.bin",
732            0,
733            0,
734            &[0x02, 0x00, 0x10, 0x74, 0x01, 0x00, 0x00, 0x00],
735        );
736        region.set_equivalent(0, Equivalent::Code(vec![])).unwrap();
737        assert!(matches!(
738            region.set_equivalent(1, Equivalent::Code(vec![])),
739            Err(Error::NotUndefined(1))
740        ));
741        region.set_equivalent(6, Equivalent::Code(vec![])).unwrap();
742        assert!(matches!(
743            region.set_equivalent(4, Equivalent::Data(DataType::Byte, 3)),
744            Err(Error::Overlap(6))
745        ));
746    }
747
748    #[test]
749    fn clear_bytes_splits_straddling_range() {
750        let mut region = Region::new();
751        region.set_bytes("test.bin", 0, 0, &[1, 2, 3, 4, 5]);
752        region.clear_bytes(1, 2);
753        assert_eq!(region.bytes_at(0, 5), vec![1, 4, 5]);
754    }
755
756    #[test]
757    fn decode_at_does_not_require_bytes_at_zero() {
758        let mut region = Region::new();
759        region.set_bytes("test.bin", 0, 0x100, &[0x74, 0x42]);
760        let insn = region.decode_at(0x100).unwrap();
761        assert_eq!(insn.len(), 2);
762        assert_eq!(insn.as_string(), "MOV A,#0x42");
763    }
764
765    #[test]
766    fn decode_at_requires_full_instruction_length() {
767        let mut region = Region::new();
768        region.set_bytes("test.bin", 0, 0, &[0x02, 0x00]);
769        assert!(
770            region.decode_at(0).is_none(),
771            "Expected None, got {:?}",
772            region.decode_at(0).unwrap().as_string()
773        );
774    }
775
776    #[test]
777    fn operand_override_preserves_other_operands() {
778        let mut region = Region::new();
779        region.set_bytes("test.bin", 0, 0, &[0xB5, 0x20, 0x10]);
780        region.set_label(0x13, "target");
781        region
782            .set_equivalent(
783                0,
784                Equivalent::Code(vec![
785                    None,
786                    None,
787                    Some(OperandOverride::Label("target".into())),
788                ]),
789            )
790            .unwrap();
791        let implicit_labels = ImplicitLabels::default();
792        let lines = region.render(AddressSpace::Code, &implicit_labels);
793        let insn = lines
794            .iter()
795            .find_map(|line| match line {
796                Line::Instruction { text, .. } => Some(text.clone()),
797                _ => None,
798            })
799            .unwrap();
800        assert!(insn.contains("RAM(32),target"));
801    }
802
803    #[test]
804    fn render_emits_org_after_unmapped_gap() {
805        let mut region = Region::new();
806        region.set_bytes("test.bin", 0, 0, &[1, 2, 3]);
807        region.set_bytes("test.bin", 3, 0x10, &[4, 5]);
808        let implicit_labels = ImplicitLabels::default();
809        let lines = region.render(AddressSpace::Code, &implicit_labels);
810        let orgs: Vec<_> = lines
811            .iter()
812            .filter_map(|line| match line {
813                Line::Org { addr } => Some(*addr),
814                _ => None,
815            })
816            .collect();
817        assert_eq!(orgs, vec![0, 0x10]);
818    }
819}