1use super::JumpTable;
2use crate::opcode;
3use bitvec::{bitvec, order::Lsb0, vec::BitVec};
4use primitives::Bytes;
5use std::vec::Vec;
6
7#[cfg(target_arch = "aarch64")]
8mod aarch64;
9#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
10mod x86;
11
12#[inline]
14pub(crate) fn analyze_legacy(bytecode: &[u8]) -> JumpTable {
15 let mut jumps: BitVec<u8> = bitvec![u8, Lsb0; 0; bytecode.len()];
16 let table = jumps.as_raw_mut_slice();
17 let pc = analyze_simd(bytecode, table);
18 analyze_scalar(bytecode, table, pc);
19 JumpTable::new(jumps)
20}
21
22#[inline]
26fn analyze_scalar(code: &[u8], table: &mut [u8], pc: usize) {
27 let range = code.as_ptr_range();
28 let start = range.start;
29 let mut iterator = start.wrapping_add(pc);
31 let end = range.end;
32
33 while iterator < end {
34 let last_byte = unsafe { *iterator };
35 if last_byte == opcode::JUMPDEST {
36 let offset = unsafe { iterator.offset_from_unsigned(start) };
38 unsafe { *table.get_unchecked_mut(offset / 8) |= 1 << (offset % 8) };
40 iterator = unsafe { iterator.add(1) };
41 } else {
42 let push_offset = last_byte.wrapping_sub(opcode::PUSH1);
43 if push_offset < 32 {
44 iterator = iterator.wrapping_add(push_offset as usize + 2);
47 } else {
48 iterator = unsafe { iterator.add(1) };
50 }
51 }
52 }
53}
54
55#[inline]
58fn analyze_simd(code: &[u8], table: &mut [u8]) -> usize {
59 if code.len() < 16 {
60 return 0;
61 }
62 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
63 {
64 x86::analyze(code, table)
65 }
66 #[cfg(target_arch = "aarch64")]
67 {
68 aarch64::analyze(code, table)
69 }
70 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
71 {
72 let _ = table;
73 0
74 }
75}
76
77#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
79trait Kernel {
80 type Bits: Into<u128>;
82
83 type Entry: Entry;
85
86 const LEN: usize = 8 * core::mem::size_of::<Self::Bits>();
88
89 unsafe fn block(ptr: *const u8, entry: &mut Self::Entry) -> Self::Bits;
92}
93
94#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
102#[inline(always)]
103unsafe fn analyze_blocks<K: Kernel>(
104 code: &[u8],
105 table: &mut [u8],
106 (mut pc, entry): (usize, usize),
107) -> (usize, usize) {
108 let mut entry = unsafe { K::Entry::new(entry) };
109 while pc + K::LEN <= code.len() {
110 unsafe {
112 let bits = K::block(code.as_ptr().add(pc), &mut entry)
113 .into()
114 .to_le_bytes();
115 let dst = table.as_mut_ptr().add(pc / 8);
116 core::ptr::copy_nonoverlapping(bits.as_ptr(), dst, K::LEN / 8);
117 }
118 pc += K::LEN;
119 }
120 (pc, unsafe { entry.offset() })
121}
122
123#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
125trait Entry: Copy {
126 unsafe fn new(offset: usize) -> Self;
128
129 unsafe fn offset(self) -> usize;
131}
132
133#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
135const PUSHX: i8 = opcode::PUSH1 as i8 - 1;
136
137#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
139const END_OFFSET: u8 = opcode::PUSH1 - 2;
140
141#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
143const fn lanes<const N: usize>(period: usize, start: u8) -> [u8; N] {
144 let mut lanes = [0; N];
145 let mut i = 0;
146 while i < N {
147 lanes[i] = start.wrapping_add((i % period) as u8);
148 i += 1;
149 }
150 lanes
151}
152
153#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
156#[inline(always)]
157unsafe fn uncarried<E: Entry>(jumpdests: u64, entry: &mut E) -> u64 {
158 let carried = unsafe { entry.offset() };
159 *entry = unsafe { E::new(0) };
160 jumpdests >> carried << carried
162}
163
164const PADDING: usize = 33;
166
167pub(crate) fn pad_legacy(bytecode: Bytes) -> Bytes {
169 if bytecode.is_empty() {
170 return Bytes::from_static(&[opcode::STOP]);
171 }
172 if bytecode.ends_with(&[0; PADDING]) {
173 return bytecode;
174 }
175
176 let padded_len = bytecode.len() + PADDING;
177 match bytecode.0.try_into_mut() {
178 Ok(mut bytecode) => {
179 bytecode.resize(padded_len, 0);
180 bytecode.freeze().into()
181 }
182 Err(bytecode) => {
183 let mut padded = Vec::with_capacity(padded_len);
184 padded.extend_from_slice(&bytecode);
185 padded.resize(padded_len, 0);
186 padded.into()
187 }
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use crate::opcode::OpCode;
195 use rand::{rngs::StdRng, RngExt, SeedableRng};
196 use std::vec;
197
198 fn reference(code: &[u8]) -> Vec<u8> {
200 let mut table = vec![0; code.len().div_ceil(8)];
201 let mut pc = 0;
202 while pc < code.len() {
203 let last = code[pc];
204 if last == opcode::JUMPDEST {
205 table[pc / 8] |= 1 << (pc % 8);
206 }
207 let opcode = OpCode::new_or_unknown(last);
208 pc += 1 + if opcode.is_push() {
209 opcode.info().immediate_size() as usize
210 } else {
211 0
212 };
213 }
214 table
215 }
216
217 fn random_code(rng: &mut StdRng, len: usize) -> Vec<u8> {
219 const DENSE: &[u8] = &[
220 opcode::PUSH1,
221 opcode::PUSH1,
222 opcode::PUSH2,
223 opcode::PUSH4,
224 opcode::PUSH32,
225 opcode::JUMPDEST,
226 opcode::JUMPDEST,
227 opcode::STOP,
228 opcode::ADD,
229 opcode::DUPN,
230 0x80,
231 0xff,
232 ];
233 let kind = rng.random_range(0..6);
234 let mut code = Vec::with_capacity(len);
235 while code.len() < len {
236 let byte = match kind {
237 0 => rng.random(),
238 1 => DENSE[rng.random_range(0..DENSE.len())],
239 2 => match rng.random() {
241 opcode::PUSH1..=opcode::PUSH32 => opcode::JUMPDEST,
242 byte => byte,
243 },
244 3 => [opcode::PUSH1, opcode::JUMPDEST, opcode::ADD][rng.random_range(0..3)],
245 4 => match rng.random_range(0..64) {
247 0 => rng.random_range(opcode::PUSH1..=opcode::PUSH32),
248 1..8 => opcode::JUMPDEST,
249 _ => opcode::ADD,
250 },
251 _ => {
253 let byte = rng.random();
254 code.push(byte);
255 if (opcode::PUSH1..=opcode::PUSH32).contains(&byte) {
256 for _ in 0..=byte - opcode::PUSH1 {
257 code.push(rng.random());
258 }
259 }
260 continue;
261 }
262 };
263 code.push(byte);
264 }
265 code.truncate(len);
266 code
267 }
268
269 pub(super) fn check_simd(simd: impl Fn(&[u8], &mut [u8]) -> usize) {
272 let check = |code: &[u8]| {
273 let mut table = vec![0; code.len().div_ceil(8)];
274 let pc = simd(code, &mut table);
275 analyze_scalar(code, &mut table, pc);
276 assert_eq!(table, reference(code), "{}", primitives::hex::encode(code));
277 };
278
279 let endings: [&[u8]; 4] = [
281 &[],
282 &[opcode::STOP],
283 &[opcode::DUPN],
284 &[opcode::DUPN, opcode::STOP],
285 ];
286 for len in 16usize..160 {
287 for at in len.saturating_sub(40)..len {
288 for push in opcode::PUSH1..=opcode::PUSH32 {
289 for ending in endings.into_iter().filter(|ending| at + ending.len() < len) {
290 let mut code = vec![opcode::JUMPDEST; len];
291 code[at] = push;
292 code[len - ending.len()..].copy_from_slice(ending);
293 check(&code);
294 }
295 }
296 }
297 }
298
299 let mut rng = StdRng::seed_from_u64(0);
300 for i in 0..600 {
301 let len = if i < 400 {
302 i
303 } else {
304 rng.random_range(400..5000)
305 };
306 for _ in 0..8 {
307 check(&random_code(&mut rng, len));
308 }
309 }
310 }
311
312 #[test]
313 fn test_simd_matches_reference() {
314 check_simd(analyze_simd);
315 }
316
317 #[test]
318 fn test_scalar_matches_reference() {
319 check_simd(|_, _| 0);
320 }
321
322 #[test]
323 fn test_bytecode_ends_with_stop_still_padded() {
324 let bytecode = vec![
325 opcode::PUSH1,
326 0x01,
327 opcode::PUSH1,
328 0x02,
329 opcode::ADD,
330 opcode::STOP,
331 ];
332 let padded_bytecode = pad_legacy(bytecode.clone().into());
333 assert_eq!(padded_bytecode.len(), bytecode.len() + 33);
334 }
335
336 #[test]
337 fn test_bytecode_ends_without_stop_requires_padding() {
338 let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH1, 0x02, opcode::ADD];
339 let padded_bytecode = pad_legacy(bytecode.clone().into());
340 assert_eq!(padded_bytecode.len(), bytecode.len() + 33);
341 }
342
343 #[test]
344 fn test_bytecode_ends_with_push16() {
345 let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH16];
346 let padded_bytecode = pad_legacy(bytecode.clone().into());
347 assert_eq!(padded_bytecode.len(), bytecode.len() + 33);
348 }
349
350 #[test]
351 fn test_bytecode_ends_with_push2() {
352 let bytecode = vec![opcode::PUSH1, 0x01, opcode::PUSH2, 0x02];
353 let padded_bytecode = pad_legacy(bytecode.clone().into());
354 assert_eq!(padded_bytecode.len(), bytecode.len() + 33);
355 }
356
357 #[test]
358 fn test_bytecode_with_jumpdest_at_start() {
359 let bytecode = vec![opcode::JUMPDEST, opcode::PUSH1, 0x01, opcode::STOP];
360 let jump_table = analyze_legacy(&bytecode);
361 assert!(jump_table.is_valid(0)); }
363
364 #[test]
365 fn test_bytecode_with_jumpdest_after_push() {
366 let bytecode = vec![opcode::PUSH1, 0x01, opcode::JUMPDEST, opcode::STOP];
367 let jump_table = analyze_legacy(&bytecode);
368 assert!(jump_table.is_valid(2)); }
370
371 #[test]
372 fn test_bytecode_with_multiple_jumpdests() {
373 let bytecode = vec![
374 opcode::JUMPDEST,
375 opcode::PUSH1,
376 0x01,
377 opcode::JUMPDEST,
378 opcode::STOP,
379 ];
380 let jump_table = analyze_legacy(&bytecode);
381 assert!(jump_table.is_valid(0)); assert!(jump_table.is_valid(3)); }
384
385 #[test]
386 fn test_bytecode_with_max_push32() {
387 let bytecode = vec![opcode::PUSH32];
388 let padded_bytecode = pad_legacy(bytecode.clone().into());
389 assert_eq!(padded_bytecode.len(), bytecode.len() + 33); }
391
392 #[test]
393 fn test_truncated_pushes_are_padded_without_inbounds_pointer_advance() {
394 for push in opcode::PUSH1..=opcode::PUSH32 {
395 let bytecode = vec![push];
396 let padded_bytecode = pad_legacy(bytecode.clone().into());
397 let push_immediate_len = (push - opcode::PUSH1 + 1) as usize;
398 assert_eq!(padded_bytecode.len(), bytecode.len() + 33);
399 assert!(padded_bytecode.len() > bytecode.len() + push_immediate_len);
400 }
401 }
402
403 #[test]
404 fn test_bytecode_with_invalid_opcode() {
405 let bytecode = vec![0xFF, opcode::STOP]; let jump_table = analyze_legacy(&bytecode);
407 assert!(!jump_table.is_valid(0)); }
409
410 #[test]
411 fn test_bytecode_with_sequential_pushes() {
412 let bytecode = vec![
413 opcode::PUSH1,
414 0x01,
415 opcode::PUSH2,
416 0x02,
417 0x03,
418 opcode::PUSH4,
419 0x04,
420 0x05,
421 0x06,
422 0x07,
423 opcode::STOP,
424 ];
425 let jump_table = analyze_legacy(&bytecode);
426 let padded_bytecode = pad_legacy(bytecode.clone().into());
427 assert_eq!(padded_bytecode.len(), bytecode.len() + 33);
428 assert!(!jump_table.is_valid(0)); assert!(!jump_table.is_valid(2)); assert!(!jump_table.is_valid(5)); }
432
433 #[test]
434 fn test_bytecode_with_jumpdest_in_push_data() {
435 let bytecode = vec![
436 opcode::PUSH2,
437 opcode::JUMPDEST, 0x02,
439 opcode::STOP,
440 ];
441 let jump_table = analyze_legacy(&bytecode);
442 assert!(!jump_table.is_valid(1)); }
444
445 #[test]
446 fn test_bytecode_ends_with_immediate_opcode_and_stop_requires_padding() {
447 for op in [opcode::SWAPN, opcode::DUPN, opcode::EXCHANGE] {
451 for bytecode in [vec![op], vec![op, opcode::STOP]] {
452 let original_len = bytecode.len();
453 let padded_bytecode = pad_legacy(bytecode.into());
454 assert_eq!(padded_bytecode.len(), original_len + 33);
455 assert_eq!(padded_bytecode[0], op);
456 assert_eq!(padded_bytecode[1], opcode::STOP);
457 assert_eq!(padded_bytecode[2], opcode::STOP);
458 }
459 }
460 }
461
462 #[test]
463 fn padding_zero_suffix_boundary() {
464 for len in [1, 32, 33, 34, 65] {
465 let raw = Bytes::from(vec![0; len]);
466 let padded = pad_legacy(raw.clone());
467 if len >= 33 {
468 assert_eq!(padded.len(), len);
469 assert_eq!(padded.as_ptr(), raw.as_ptr());
470 } else {
471 assert_eq!(padded.len(), len + 33);
472 }
473 assert!(padded.iter().all(|&byte| byte == 0));
474 }
475
476 for nonzero in 0..33 {
478 let mut raw = vec![0; 33];
479 raw[nonzero] = opcode::JUMPDEST;
480 let padded = pad_legacy(raw.clone().into());
481 assert_eq!(padded.len(), 66);
482 assert_eq!(&padded[..33], &raw);
483 assert_eq!(&padded[33..], &[0; 33]);
484 }
485 }
486
487 #[test]
488 fn padding_reuses_owned_capacity() {
489 let mut raw = Vec::with_capacity(34);
490 raw.push(opcode::PUSH32);
491 let raw = Bytes::from(raw);
492 let ptr = raw.as_ptr();
493 let padded = pad_legacy(raw);
494 assert_eq!(padded.as_ptr(), ptr);
495 assert_eq!(padded.len(), 34);
496 assert_eq!(&padded[1..], &[0; 33]);
497 }
498
499 #[test]
500 fn padding_releases_shared_input() {
501 let raw = Bytes::copy_from_slice(&[opcode::PUSH32]);
502 let padded = pad_legacy(raw.clone());
503 assert!(raw.is_unique());
504 assert_ne!(padded.as_ptr(), raw.as_ptr());
505 assert_eq!(&raw[..], &[opcode::PUSH32]);
506 assert_eq!(padded.len(), 34);
507 assert_eq!(&padded[1..], &[0; 33]);
508 }
509}