ckb_vm/machine/
trace.rs

1use super::{
2    super::{
3        decoder::build_decoder,
4        elf::ProgramMetadata,
5        instructions::{
6            execute_with_thread, extract_opcode, handle_invalid_op, instruction_length,
7            is_basic_block_end_instruction, Instruction, Register, Thread, ThreadFactory,
8        },
9        Error,
10    },
11    CoreMachine, DefaultMachine, DefaultMachineRunner, Machine, SupportMachine, VERSION2,
12};
13use bytes::Bytes;
14
15// The number of trace items to keep
16const TRACE_SIZE: usize = 8192;
17// Quick bit-mask to truncate a value in trace size range
18const TRACE_MASK: usize = TRACE_SIZE - 1;
19// The maximum number of instructions to cache in a trace item
20const TRACE_ITEM_LENGTH: usize = 16;
21// Shifts to truncate a value so 2 traces has the minimal chance of sharing code.
22const TRACE_ADDRESS_SHIFTS: usize = 2;
23
24struct Trace<Inner: Machine> {
25    address: u64,
26    length: usize,
27    instruction_count: u8,
28    instructions: [Instruction; TRACE_ITEM_LENGTH],
29    threads: [Thread<Inner>; TRACE_ITEM_LENGTH],
30}
31
32impl<Inner: Machine> Default for Trace<Inner> {
33    fn default() -> Self {
34        Trace {
35            address: 0,
36            length: 0,
37            instruction_count: 0,
38            instructions: [0; TRACE_ITEM_LENGTH],
39            threads: [handle_invalid_op::<Inner>; TRACE_ITEM_LENGTH],
40        }
41    }
42}
43
44#[inline(always)]
45fn calculate_slot(addr: u64) -> usize {
46    (addr as usize >> TRACE_ADDRESS_SHIFTS) & TRACE_MASK
47}
48
49pub struct TraceMachine<Inner: SupportMachine> {
50    pub machine: DefaultMachine<Inner>,
51
52    factory: ThreadFactory<DefaultMachine<Inner>>,
53    traces: Vec<Trace<DefaultMachine<Inner>>>,
54}
55
56impl<Inner: SupportMachine> CoreMachine for TraceMachine<Inner> {
57    type REG = <Inner as CoreMachine>::REG;
58    type MEM = <Inner as CoreMachine>::MEM;
59
60    fn pc(&self) -> &Self::REG {
61        self.machine.pc()
62    }
63
64    fn update_pc(&mut self, pc: Self::REG) {
65        self.machine.update_pc(pc);
66    }
67
68    fn commit_pc(&mut self) {
69        self.machine.commit_pc();
70    }
71
72    fn memory(&self) -> &Self::MEM {
73        self.machine.memory()
74    }
75
76    fn memory_mut(&mut self) -> &mut Self::MEM {
77        self.machine.memory_mut()
78    }
79
80    fn registers(&self) -> &[Self::REG] {
81        self.machine.registers()
82    }
83
84    fn set_register(&mut self, idx: usize, value: Self::REG) {
85        self.machine.set_register(idx, value)
86    }
87
88    fn isa(&self) -> u8 {
89        self.machine.isa()
90    }
91
92    fn version(&self) -> u32 {
93        self.machine.version()
94    }
95}
96
97impl<Inner: SupportMachine> Machine for TraceMachine<Inner> {
98    fn ecall(&mut self) -> Result<(), Error> {
99        self.machine.ecall()
100    }
101
102    fn ebreak(&mut self) -> Result<(), Error> {
103        self.machine.ebreak()
104    }
105}
106
107impl<Inner: SupportMachine> DefaultMachineRunner for TraceMachine<Inner> {
108    type Inner = Inner;
109
110    fn new(machine: DefaultMachine<Inner>) -> Self {
111        Self {
112            machine,
113            factory: ThreadFactory::create(),
114            traces: vec![],
115        }
116    }
117
118    fn machine(&self) -> &DefaultMachine<Inner> {
119        &self.machine
120    }
121
122    fn machine_mut(&mut self) -> &mut DefaultMachine<Inner> {
123        &mut self.machine
124    }
125
126    fn run(&mut self) -> Result<i8, Error> {
127        let mut decoder = build_decoder::<Inner::REG>(self.isa(), self.version());
128        self.machine.set_running(true);
129        // For current trace size this is acceptable, however we might want
130        // to tweak the code here if we choose to use a larger trace size or
131        // larger trace item length.
132        self.traces.resize_with(TRACE_SIZE, Trace::default);
133        while self.machine.running() {
134            if self.machine.pause.has_interrupted() {
135                self.machine.pause.free();
136                return Err(Error::Pause);
137            }
138            if self.machine.reset_signal() {
139                decoder.reset_instructions_cache();
140                for i in self.traces.iter_mut() {
141                    *i = Trace::default()
142                }
143            }
144            let pc = self.machine.pc().to_u64();
145            let slot = calculate_slot(pc);
146            // This is to replicate a bug in x64 VM
147            let address_match = if self.machine.version() < VERSION2 {
148                (pc as u32 as u64) == self.traces[slot].address
149            } else {
150                pc == self.traces[slot].address
151            };
152            if (!address_match) || self.traces[slot].instruction_count == 0 {
153                self.traces[slot] = Trace::default();
154                let mut current_pc = pc;
155                let mut i = 0;
156                while i < TRACE_ITEM_LENGTH {
157                    let instruction = decoder.decode(self.machine.memory_mut(), current_pc)?;
158                    let end_instruction = is_basic_block_end_instruction(instruction);
159                    current_pc += u64::from(instruction_length(instruction));
160                    self.traces[slot].instructions[i] = instruction;
161                    self.traces[slot].threads[i] = self.factory[extract_opcode(instruction)];
162                    i += 1;
163                    if end_instruction {
164                        break;
165                    }
166                }
167                self.traces[slot].address = pc;
168                self.traces[slot].length = (current_pc - pc) as usize;
169                self.traces[slot].instruction_count = i as u8;
170            }
171            for i in 0..self.traces[slot].instruction_count {
172                let inst = self.traces[slot].instructions[i as usize];
173                let cycles = self.machine.instruction_cycle_func()(inst);
174                self.machine.add_cycles(cycles)?;
175                execute_with_thread(
176                    inst,
177                    &mut self.machine,
178                    &self.traces[slot].threads[i as usize],
179                )?;
180            }
181        }
182        Ok(self.machine.exit_code())
183    }
184}
185
186impl<Inner: SupportMachine> TraceMachine<Inner> {
187    pub fn load_program(
188        &mut self,
189        program: &Bytes,
190        args: impl ExactSizeIterator<Item = Result<Bytes, Error>>,
191    ) -> Result<u64, Error> {
192        self.machine.load_program(program, args)
193    }
194
195    pub fn load_program_with_metadata(
196        &mut self,
197        program: &Bytes,
198        metadata: &ProgramMetadata,
199        args: impl ExactSizeIterator<Item = Result<Bytes, Error>>,
200    ) -> Result<u64, Error> {
201        self.machine
202            .load_program_with_metadata(program, metadata, args)
203    }
204
205    pub fn set_max_cycles(&mut self, cycles: u64) {
206        self.machine.inner_mut().set_max_cycles(cycles)
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_trace_constant_rules() {
216        assert!(TRACE_SIZE.is_power_of_two());
217        assert_eq!(TRACE_MASK, TRACE_SIZE - 1);
218        assert!(TRACE_ITEM_LENGTH.is_power_of_two());
219        assert!(TRACE_ITEM_LENGTH <= 255);
220    }
221}