zingen/jump/
target.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//! Target related operations of the jump table.
//!
//! This module provides functions to retrieve and shift the target program counters
//! associated with various jump types.

use crate::{
    jump::{Jump, JumpTable},
    Error, Result,
};

impl JumpTable {
    /// Retrieves the target of a given jump.
    ///
    /// This function returns the target program counter based on the type of jump
    /// (offset, label, function, or external function).
    pub fn target(&self, jump: &Jump) -> Result<u16> {
        match jump {
            Jump::Label(label) => Ok(*label),
            Jump::Func(func) => Ok(*self.func.get(func).ok_or(Error::FuncNotFound(*func))?),
            Jump::ExtFunc(ext) => Ok(self.code.offset_of(ext).ok_or(Error::ExtFuncNotFound)?),
        }
    }

    /// Shifts the target program counters as a pre-calculation step.
    ///
    /// This function calculates the target program counter from the original program
    /// counter and the target, adjusting for any offsets.
    pub fn shift_targets(&mut self) -> Result<()> {
        let mut total_offset = 0;
        let mut target_sizes = Vec::new();
        let jumps = self.jump.clone();

        // First pass: calculate all target sizes and accumulate offsets
        for (original_pc, jump) in jumps.iter() {
            let pc = original_pc + total_offset;
            let raw_target = self.target(jump)?;

            // Calculate the absolute target including future offsets
            let target = if raw_target > *original_pc {
                raw_target + total_offset
            } else {
                raw_target
            };

            // Calculate instruction size based on absolute target value
            let instr_size = if target > 0xff {
                3 // PUSH2 + 2 bytes
            } else {
                2 // PUSH1 + 1 byte
            };

            target_sizes.push((pc, instr_size));
            total_offset += instr_size;
        }

        // Second pass: apply shifts with accumulated offsets
        total_offset = 0;
        for (pc, size) in target_sizes {
            tracing::debug!("shift target at pc=0x{pc:x} with size={size}");
            self.shift_target(pc, size)?;
            total_offset += size;
        }

        Ok(())
    }

    /// Shifts the program counter of targets with the given pointer and offset.
    ///
    /// This function handles the shifting of the code section, label targets, and
    /// function targets.
    pub fn shift_target(&mut self, ptr: u16, offset: u16) -> Result<()> {
        // First shift the code section
        self.code.shift(offset);

        // Only shift targets that are after ptr
        self.shift_label_target(ptr, offset)?;
        self.shift_func_target(ptr, offset)
    }

    /// Shifts the program counter for functions.
    pub fn shift_func_target(&mut self, ptr: u16, offset: u16) -> Result<()> {
        self.func.iter_mut().try_for_each(|(index, target)| {
            if *target > ptr {
                let next_target = *target + offset;
                tracing::trace!(
                    "shift Func({index}) target with offset={offset}: 0x{target:x}(0x{ptr:x}) -> 0x{:x}",
                    next_target
                );

                *target = next_target;
            }

            Ok(())
        })
    }

    /// Shifts the program counter for labels.
    pub fn shift_label_target(&mut self, ptr: u16, offset: u16) -> Result<()> {
        for (_, jump) in self.jump.iter_mut() {
            let Jump::Label(target) = jump else {
                continue;
            };

            // Only shift targets that come after ptr AND
            // only if the jump instruction itself comes after ptr
            if *target > ptr {
                let next_target = *target + offset;
                tracing::trace!(
                    "shift Label target with offset={offset}: 0x{target:x}(0x{ptr:x}) -> 0x{:x}",
                    next_target,
                );
                *target = next_target;
            }
        }
        Ok(())
    }
}