sp1_core_executor/
report.rs1use std::{
2 fmt::{Display, Formatter, Result as FmtResult},
3 ops::{Add, AddAssign},
4};
5
6use enum_map::{EnumArray, EnumMap};
7use hashbrown::HashMap;
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 events::{generate_execution_report, MemInstrEvent, PrecompileEvent, SyscallEvent},
12 ITypeRecord, Opcode, SyscallCode,
13};
14
15const GAS_NORMALIZATION_FACTOR: u64 = 191;
18
19#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct ExecutionReport {
26 pub opcode_counts: Box<EnumMap<Opcode, u64>>,
28 pub syscall_counts: Box<EnumMap<SyscallCode, u64>>,
30 pub cycle_tracker: HashMap<String, u64>,
32 pub invocation_tracker: HashMap<String, u64>,
34 pub touched_memory_addresses: u64,
36 pub exit_code: u64,
38 pub(crate) gas: Option<u64>,
40}
41
42impl ExecutionReport {
43 #[must_use]
45 pub fn total_instruction_count(&self) -> u64 {
46 self.opcode_counts.values().sum()
47 }
48
49 #[must_use]
51 pub fn total_syscall_count(&self) -> u64 {
52 self.syscall_counts.values().sum()
53 }
54
55 #[must_use]
57 pub fn total_record_size(&self) -> u64 {
58 let avg_opcode_record_size = std::mem::size_of::<(MemInstrEvent, ITypeRecord)>();
62 let total_opcode_records_size_bytes =
63 self.opcode_counts.values().sum::<u64>() * avg_opcode_record_size as u64;
64
65 let syscall_avg_record_size = std::mem::size_of::<(SyscallEvent, PrecompileEvent)>() + 512;
68 let total_syscall_records_size_bytes =
69 self.syscall_counts.values().sum::<u64>() * syscall_avg_record_size as u64;
70
71 total_opcode_records_size_bytes + total_syscall_records_size_bytes
72 }
73
74 #[must_use]
77 pub fn gas(&self) -> Option<u64> {
78 self.gas.map(|g| g * 10 / GAS_NORMALIZATION_FACTOR)
80 }
81}
82
83fn counts_add_assign<K, V>(lhs: &mut EnumMap<K, V>, rhs: EnumMap<K, V>)
85where
86 K: EnumArray<V>,
87 V: AddAssign,
88{
89 for (k, v) in rhs {
90 lhs[k] += v;
91 }
92}
93
94impl AddAssign for ExecutionReport {
95 fn add_assign(&mut self, rhs: Self) {
96 counts_add_assign(&mut self.opcode_counts, *rhs.opcode_counts);
97 counts_add_assign(&mut self.syscall_counts, *rhs.syscall_counts);
98 self.touched_memory_addresses += rhs.touched_memory_addresses;
99
100 for (label, count) in rhs.cycle_tracker {
102 *self.cycle_tracker.entry(label).or_insert(0) += count;
103 }
104 for (label, count) in rhs.invocation_tracker {
105 *self.invocation_tracker.entry(label).or_insert(0) += count;
106 }
107
108 self.gas = match (self.gas, rhs.gas) {
110 (Some(c1), Some(c2)) => Some(c1 + c2),
111 (Some(g), None) | (None, Some(g)) => Some(g),
112 (None, None) => None,
113 };
114
115 self.exit_code |= rhs.exit_code;
117 }
118}
119
120impl Add for ExecutionReport {
121 type Output = Self;
122
123 fn add(mut self, rhs: Self) -> Self::Output {
124 self += rhs;
125 self
126 }
127}
128
129impl Display for ExecutionReport {
130 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
131 if let Some(gas) = self.gas() {
132 writeln!(f, "gas: {gas:?}")?;
133 }
134 writeln!(f, "opcode counts ({} total instructions):", self.total_instruction_count())?;
135 for line in generate_execution_report(self.opcode_counts.as_ref()) {
136 writeln!(f, " {line}")?;
137 }
138
139 writeln!(f, "syscall counts ({} total syscall instructions):", self.total_syscall_count())?;
140 for line in generate_execution_report(self.syscall_counts.as_ref()) {
141 writeln!(f, " {line}")?;
142 }
143
144 Ok(())
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 fn populated_report() -> ExecutionReport {
153 let mut report = ExecutionReport::default();
154 report.opcode_counts[Opcode::ADD] = 7;
155 report.opcode_counts[Opcode::SUB] = 3;
156 report.syscall_counts[SyscallCode::HALT] = 1;
157 report.cycle_tracker.insert("setup".to_string(), 100);
158 report.invocation_tracker.insert("setup".to_string(), 2);
159 report.touched_memory_addresses = 42;
160 report.exit_code = 0;
161 report.gas = Some(1_000);
162 report
163 }
164
165 #[test]
168 fn execution_report_json_round_trip() {
169 let report = populated_report();
170 let json = serde_json::to_string(&report).expect("serialize");
171 let decoded: ExecutionReport = serde_json::from_str(&json).expect("deserialize");
172 assert_eq!(report, decoded);
173 }
174
175 #[test]
179 fn execution_report_bincode_round_trip() {
180 let report = populated_report();
181 let bytes = bincode::serialize(&report).expect("serialize");
182 let decoded: ExecutionReport = bincode::deserialize(&bytes).expect("deserialize");
183 assert_eq!(report, decoded);
184 }
185
186 #[test]
189 fn execution_report_default_and_none_gas_round_trip() {
190 let report = ExecutionReport::default();
191 assert_eq!(report.gas, None);
192
193 let json: ExecutionReport =
194 serde_json::from_str(&serde_json::to_string(&report).expect("ser")).expect("de");
195 assert_eq!(report, json);
196
197 let bin: ExecutionReport =
198 bincode::deserialize(&bincode::serialize(&report).expect("ser")).expect("de");
199 assert_eq!(report, bin);
200 }
201}