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, 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> TraceMachine<Inner> {
108    pub fn new(machine: DefaultMachine<Inner>) -> Self {
109        Self {
110            machine,
111            factory: ThreadFactory::create(),
112            traces: vec![],
113        }
114    }
115
116    pub fn load_program(
117        &mut self,
118        program: &Bytes,
119        args: impl ExactSizeIterator<Item = Result<Bytes, Error>>,
120    ) -> Result<u64, Error> {
121        self.machine.load_program(program, args)
122    }
123
124    pub fn load_program_with_metadata(
125        &mut self,
126        program: &Bytes,
127        metadata: &ProgramMetadata,
128        args: impl ExactSizeIterator<Item = Result<Bytes, Error>>,
129    ) -> Result<u64, Error> {
130        self.machine
131            .load_program_with_metadata(program, metadata, args)
132    }
133
134    pub fn set_max_cycles(&mut self, cycles: u64) {
135        self.machine.inner_mut().set_max_cycles(cycles)
136    }
137
138    pub fn run(&mut self) -> Result<i8, Error> {
139        let mut decoder = build_decoder::<Inner::REG>(self.isa(), self.version());
140        self.machine.set_running(true);
141        // For current trace size this is acceptable, however we might want
142        // to tweak the code here if we choose to use a larger trace size or
143        // larger trace item length.
144        self.traces.resize_with(TRACE_SIZE, Trace::default);
145        while self.machine.running() {
146            if self.machine.pause.has_interrupted() {
147                self.machine.pause.free();
148                return Err(Error::Pause);
149            }
150            if self.machine.reset_signal() {
151                decoder.reset_instructions_cache();
152                for i in self.traces.iter_mut() {
153                    *i = Trace::default()
154                }
155            }
156            let pc = self.machine.pc().to_u64();
157            let slot = calculate_slot(pc);
158            // This is to replicate a bug in x64 VM
159            let address_match = if self.machine.version() < VERSION2 {
160                (pc as u32 as u64) == self.traces[slot].address
161            } else {
162                pc == self.traces[slot].address
163            };
164            if (!address_match) || self.traces[slot].instruction_count == 0 {
165                self.traces[slot] = Trace::default();
166                let mut current_pc = pc;
167                let mut i = 0;
168                while i < TRACE_ITEM_LENGTH {
169                    let instruction = decoder.decode(self.machine.memory_mut(), current_pc)?;
170                    let end_instruction = is_basic_block_end_instruction(instruction);
171                    current_pc += u64::from(instruction_length(instruction));
172                    self.traces[slot].instructions[i] = instruction;
173                    self.traces[slot].threads[i] = self.factory[extract_opcode(instruction)];
174                    i += 1;
175                    if end_instruction {
176                        break;
177                    }
178                }
179                self.traces[slot].address = pc;
180                self.traces[slot].length = (current_pc - pc) as usize;
181                self.traces[slot].instruction_count = i as u8;
182            }
183            for i in 0..self.traces[slot].instruction_count {
184                let inst = self.traces[slot].instructions[i as usize];
185                let cycles = self.machine.instruction_cycle_func()(inst);
186                self.machine.add_cycles(cycles)?;
187                execute_with_thread(
188                    inst,
189                    &mut self.machine,
190                    &self.traces[slot].threads[i as usize],
191                )?;
192            }
193        }
194        Ok(self.machine.exit_code())
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_trace_constant_rules() {
204        assert!(TRACE_SIZE.is_power_of_two());
205        assert_eq!(TRACE_MASK, TRACE_SIZE - 1);
206        assert!(TRACE_ITEM_LENGTH.is_power_of_two());
207        assert!(TRACE_ITEM_LENGTH <= 255);
208    }
209}