1use crate::codegen::ExtFunc;
8use core::fmt::Display;
9pub use table::JumpTable;
10
11mod pc;
12mod relocate;
13mod table;
14mod target;
15
16#[derive(Clone, Debug, PartialEq, Eq)]
18pub enum Jump {
19 Label(u16),
21 Func(u32),
23 ExtFunc(ExtFunc),
25}
26
27impl Display for Jump {
28 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
29 match self {
30 Jump::Label(offset) => write!(f, "Label(0x{offset:x})"),
31 Jump::Func(index) => write!(f, "Func({index})"),
32 Jump::ExtFunc(_) => write!(f, "ExtFunc"),
33 }
34 }
35}
36
37impl Jump {
38 pub fn is_label(&self) -> bool {
40 matches!(self, Jump::Label { .. })
41 }
42
43 pub fn is_call(&self) -> bool {
45 !self.is_label()
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use crate::jump::{Jump, JumpTable};
52 use smallvec::smallvec;
53
54 #[allow(unused)]
55 fn init_tracing() {
56 tracing_subscriber::fmt()
57 .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
58 .without_time()
59 .compact()
60 .try_init()
61 .ok();
62 }
63
64 fn assert_target_shift_vs_relocation(mut table: JumpTable) -> anyhow::Result<()> {
65 let mut buffer = smallvec![0; table.max_target() as usize];
67
68 table.shift_targets()?;
70
71 let max_target = table.max_target();
73
74 table.relocate(&mut buffer)?;
76
77 assert_eq!(buffer.len(), max_target as usize);
78 Ok(())
79 }
80
81 #[test]
82 fn test_target_shift_vs_relocation() -> anyhow::Result<()> {
83 let mut table = JumpTable::default();
84
85 table.register(0x10, Jump::Label(0x20)); table.register(0x30, Jump::Label(0x40)); assert_target_shift_vs_relocation(table)
90 }
91
92 #[test]
93 fn test_multiple_internal_calls() -> anyhow::Result<()> {
94 let mut table = JumpTable::default();
95
96 table.register(0x10, Jump::Label(0x100)); table.register(0x20, Jump::Label(0x100)); assert_target_shift_vs_relocation(table)
101 }
102
103 #[test]
104 fn test_nested_function_calls() -> anyhow::Result<()> {
105 let mut table = JumpTable::default();
106
107 table.register(0x100, Jump::Label(0x200)); table.register(0x110, Jump::Label(0x300)); table.register(0x200, Jump::Label(0x400)); let mut buffer = smallvec![0; table.max_target() as usize];
113 table.relocate(&mut buffer)?;
114
115 assert_eq!(buffer[0x100], 0x61); assert_eq!(buffer[0x113], 0x61); assert_eq!(buffer[0x206], 0x61); Ok(())
121 }
122
123 #[test]
124 fn test_label_call_interaction() -> anyhow::Result<()> {
125 init_tracing();
126 let mut table = JumpTable::default();
127
128 table.func.insert(1, 0x317);
129 table.label(0x10, 0x12);
130 table.call(0x11, 1);
131
132 let mut buffer = smallvec![0; table.max_target() as usize];
133 table.relocate(&mut buffer)?;
134
135 assert_eq!(buffer[0x11], 0x17, "{buffer:?}");
136 assert_eq!(buffer[0x14], 0x03, "{buffer:?}");
137 assert_eq!(buffer[0x15], 0x1c, "{buffer:?}");
138 Ok(())
139 }
140
141 #[test]
142 fn test_large_target_offset_calculation() -> anyhow::Result<()> {
143 let mut table = JumpTable::default();
144
145 table.register(0x10, Jump::Label(0x80));
147
148 table.register(0x20, Jump::Label(0x100));
150
151 table.register(0x30, Jump::Label(0x1000));
153
154 let mut buffer = smallvec![0; table.max_target() as usize];
155 table.relocate(&mut buffer)?;
156
157 assert_eq!(buffer[0x11], 0x88); assert_eq!(buffer[0x23], 0x01); assert_eq!(buffer[0x24], 0x08); assert_eq!(buffer[0x36], 0x10); assert_eq!(buffer[0x37], 0x08); Ok(())
168 }
169
170 #[test]
171 fn test_sequential_large_jumps() -> anyhow::Result<()> {
172 let mut table = JumpTable::default();
173
174 for i in 0..20 {
177 let target = 0x100 + (i * 0x20);
178 table.register(0x10 + i, Jump::Label(target));
179 }
180
181 let mut buffer = smallvec![0; table.max_target() as usize];
182 table.relocate(&mut buffer)?;
183
184 assert_eq!(buffer[0x10], 0x61); assert_eq!(buffer[0x11], 0x01); assert_eq!(buffer[0x12], 0x3c); assert_eq!(0x013c, 0x100 + 20 * 3);
189
190 let last_idx = 0x10 + 19 + 19 * 3;
192 assert_eq!(buffer[last_idx], 0x61); assert_eq!(buffer[last_idx + 1], 0x03); assert_eq!(buffer[last_idx + 2], 0x9c); assert_eq!(0x039c, 0x100 + 0x20 * 19 + 20 * 3);
196
197 Ok(())
198 }
199
200 #[test]
201 fn test_dispatcher_jump_targets() -> anyhow::Result<()> {
202 let mut table = JumpTable::default();
203 let selectors = 5;
204
205 for i in 0..selectors {
207 let i = i as u16;
208 let check_pc = 0x10 + i * 0x20;
209 let target_pc = 0x100 + i * 0x40;
210
211 table.register(check_pc, Jump::Label(check_pc + 0x10));
213 table.register(check_pc + 0x10, Jump::Label(target_pc));
214 }
215
216 let mut buffer = smallvec![0; table.max_target() as usize];
217 table.relocate(&mut buffer)?;
218
219 let mut total_offset = 0;
221 for i in 0..selectors {
222 let check_pc = 0x10 + i * 0x20 + total_offset;
223 let check_pc_offset = if check_pc + 0x10 > 0xff { 3 } else { 2 };
224
225 let func_pc = check_pc + 0x10 + check_pc_offset;
226
227 let check_jump = buffer[check_pc];
228 let func_jump = buffer[func_pc];
229
230 assert_eq!(check_jump, if func_pc > 0xff { 0x61 } else { 0x60 });
231 assert_eq!(func_jump, 0x61);
232
233 total_offset += check_pc_offset + 3;
235 }
236
237 Ok(())
238 }
239}