cfx_vm_interpreter/interpreter/
shared_cache.rs1use crate::instructions::{self, Instruction};
18use bit_set::BitSet;
19use cfx_types::H256;
20use keccak_hash::KECCAK_EMPTY;
21use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
22use memory_cache::MemoryLruCache;
23use parking_lot::Mutex;
24use std::sync::Arc;
25
26#[cfg(test)]
27use rustc_hex::FromHex;
28
29const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024;
30
31#[derive(Clone)]
34struct Bits(Arc<BitSet>);
35
36impl MallocSizeOf for Bits {
37 fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize {
38 self.0.capacity() * 8
40 }
41}
42
43#[derive(Clone)]
44struct CacheItem {
45 jump_destination: Bits,
46 sub_entrypoint: Bits,
47}
48
49impl MallocSizeOf for CacheItem {
50 fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
51 self.jump_destination.size_of(ops) + self.sub_entrypoint.size_of(ops)
52 }
53}
54
55pub struct SharedCache<const CANCUN: bool> {
57 jump_destinations: Mutex<MemoryLruCache<H256, CacheItem>>,
58}
59
60impl<const CANCUN: bool> SharedCache<CANCUN> {
61 pub fn new(max_size: usize) -> Self {
64 SharedCache {
65 jump_destinations: Mutex::new(MemoryLruCache::new(max_size)),
66 }
67 }
68
69 pub fn jump_and_sub_destinations(
71 &self, code_hash: &H256, code: &[u8],
72 ) -> (Arc<BitSet>, Arc<BitSet>) {
73 if code_hash == &KECCAK_EMPTY {
74 let cache_item = Self::find_jump_and_sub_destinations(code);
75 return (
76 cache_item.jump_destination.0,
77 cache_item.sub_entrypoint.0,
78 );
79 }
80
81 if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) {
82 return (d.jump_destination.0.clone(), d.sub_entrypoint.0.clone());
83 }
84
85 let d = Self::find_jump_and_sub_destinations(code);
86 self.jump_destinations.lock().insert(*code_hash, d.clone());
87
88 (d.jump_destination.0, d.sub_entrypoint.0)
89 }
90
91 fn find_jump_and_sub_destinations(code: &[u8]) -> CacheItem {
92 let mut jump_dests = BitSet::with_capacity(code.len());
93 let mut sub_entrypoints = BitSet::with_capacity(code.len());
94 let mut position = 0;
95
96 while position < code.len() {
97 let instruction = Instruction::from_u8(code[position]);
98
99 if let Some(instruction) = instruction {
100 match instruction {
101 instructions::JUMPDEST => {
102 jump_dests.insert(position);
103 }
104 instructions::BEGINSUB_TLOAD if !CANCUN => {
105 sub_entrypoints.insert(position);
106 }
107 _ => {
108 if let Some(push_bytes) = instruction.push_bytes() {
109 position += push_bytes;
110 }
111 }
112 }
113 }
114 position += 1;
115 }
116
117 jump_dests.shrink_to_fit();
118 CacheItem {
119 jump_destination: Bits(Arc::new(jump_dests)),
120 sub_entrypoint: Bits(Arc::new(sub_entrypoints)),
121 }
122 }
123}
124
125impl<const CANCUN: bool> Default for SharedCache<CANCUN> {
126 fn default() -> Self { SharedCache::new(DEFAULT_CACHE_SIZE) }
127}
128
129#[test]
130fn test_find_jump_destinations() {
131 let code: Vec<u8> = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap();
142
143 let cache_item =
145 SharedCache::<false>::find_jump_and_sub_destinations(&code);
146
147 assert!(cache_item
149 .jump_destination
150 .0
151 .iter()
152 .eq(vec![66].into_iter()));
153 assert!(cache_item.sub_entrypoint.0.is_empty());
154}
155
156#[test]
157fn test_find_jump_destinations_not_in_data_segments() {
158 let code: Vec<u8> = "600656605B565B6004".from_hex().unwrap();
168
169 let cache_item =
171 SharedCache::<false>::find_jump_and_sub_destinations(&code);
172
173 assert!(cache_item.jump_destination.0.iter().eq(vec![6].into_iter()));
175 assert!(cache_item.sub_entrypoint.0.is_empty());
176}
177
178#[test]
179fn test_find_sub_entrypoints() {
180 let code: Vec<u8> =
184 "6800000000000000000c5e005c60115e5d5c5d".from_hex().unwrap();
185
186 let cache_item =
188 SharedCache::<false>::find_jump_and_sub_destinations(&code);
189
190 assert!(cache_item.jump_destination.0.is_empty());
192 assert!(cache_item
193 .sub_entrypoint
194 .0
195 .iter()
196 .eq(vec![12, 17].into_iter()));
197}
198
199#[test]
200fn test_find_jump_and_sub_allowing_unknown_opcodes() {
201 assert!(Instruction::from_u8(0xcc) == None);
203
204 let code: Vec<u8> = "5BCC5C".from_hex().unwrap();
210
211 let cache_item =
213 SharedCache::<false>::find_jump_and_sub_destinations(&code);
214
215 assert!(cache_item.jump_destination.0.iter().eq(vec![0].into_iter()));
217 assert!(cache_item.sub_entrypoint.0.iter().eq(vec![2].into_iter()));
218}