risc0_zkvm/host/server/exec/
executor.rs1use std::{cell::RefCell, io::Write, rc::Rc, sync::Arc, time::Instant};
16
17use anyhow::{Context as _, Result};
18use risc0_binfmt::{MemoryImage, Program};
19use risc0_circuit_rv32im::prove::emu::{
20 addr::ByteAddr,
21 exec::{
22 Executor, Syscall as NewSyscall, SyscallContext as NewSyscallContext,
23 DEFAULT_SEGMENT_LIMIT_PO2,
24 },
25};
26use risc0_core::scope;
27use risc0_zkp::core::digest::Digest;
28use risc0_zkvm_platform::{fileno, memory::GUEST_MAX_MEM, PAGE_SIZE};
29use tempfile::tempdir;
30
31use crate::{
32 host::client::env::SegmentPath, Assumptions, ExecutorEnv, FileSegmentRef, Output, Segment,
33 SegmentRef, Session,
34};
35
36use super::{
37 profiler::Profiler,
38 syscall::{SyscallContext, SyscallTable},
39};
40
41pub struct ExecutorImpl<'a> {
45 env: ExecutorEnv<'a>,
46 image: MemoryImage,
47 pub(crate) syscall_table: SyscallTable<'a>,
48 profiler: Option<Rc<RefCell<Profiler>>>,
49}
50
51impl<'a> ExecutorImpl<'a> {
52 pub fn new(env: ExecutorEnv<'a>, image: MemoryImage) -> Result<Self> {
60 Self::with_details(env, image, None)
61 }
62
63 pub fn from_elf(mut env: ExecutorEnv<'a>, elf: &[u8]) -> Result<Self> {
80 let program = Program::load_elf(elf, GUEST_MAX_MEM as u32)?;
81 let image = MemoryImage::new(&program, PAGE_SIZE as u32)?;
82
83 let profiler = if env.pprof_out.is_some() {
84 let profiler = Rc::new(RefCell::new(Profiler::new(elf, None)?));
85 env.trace.push(profiler.clone());
86 Some(profiler)
87 } else {
88 None
89 };
90
91 Self::with_details(env, image, profiler)
92 }
93
94 fn with_details(
95 env: ExecutorEnv<'a>,
96 image: MemoryImage,
97 profiler: Option<Rc<RefCell<Profiler>>>,
98 ) -> Result<Self> {
99 let syscall_table = SyscallTable::from_env(&env);
100 Ok(Self {
101 env,
102 image,
103 syscall_table,
104 profiler,
105 })
106 }
107
108 pub fn run(&mut self) -> Result<Session> {
111 if self.env.segment_path.is_none() {
112 self.env.segment_path = Some(SegmentPath::TempDir(Arc::new(tempdir()?)));
113 }
114
115 let path = self.env.segment_path.clone().unwrap();
116 self.run_with_callback(|segment| Ok(Box::new(FileSegmentRef::new(&segment, &path)?)))
117 }
118
119 pub fn run_with_callback<F>(&mut self, mut callback: F) -> Result<Session>
122 where
123 F: FnMut(Segment) -> Result<Box<dyn SegmentRef>>,
124 {
125 scope!("execute");
126
127 let journal = Journal::default();
128 self.env
129 .posix_io
130 .borrow_mut()
131 .with_write_fd(fileno::JOURNAL, journal.clone());
132
133 let segment_limit_po2 = self
134 .env
135 .segment_limit_po2
136 .unwrap_or(DEFAULT_SEGMENT_LIMIT_PO2 as u32) as usize;
137
138 let mut refs = Vec::new();
139 let mut exec = Executor::new(
140 self.image.clone(),
141 self,
142 self.env.input_digest,
143 self.env.trace.clone(),
144 );
145
146 let start_time = Instant::now();
147 let result = exec.run(segment_limit_po2, self.env.session_limit, |inner| {
148 let output = inner
149 .exit_code
150 .expects_output()
151 .then(|| -> Option<Result<_>> {
152 inner
153 .output_digest
154 .and_then(|digest| {
155 (digest != Digest::ZERO).then(|| journal.buf.borrow().clone())
156 })
157 .map(|journal| {
158 Ok(Output {
159 journal: journal.into(),
160 assumptions: Assumptions(
161 self.syscall_table
162 .assumptions_used
163 .borrow()
164 .iter()
165 .map(|(a, _)| a.clone().into())
166 .collect::<Vec<_>>(),
167 )
168 .into(),
169 })
170 })
171 })
172 .flatten()
173 .transpose()?;
174
175 let segment = Segment {
176 index: inner.index as u32,
177 inner,
178 output,
179 };
180 let segment_ref = callback(segment)?;
181 refs.push(segment_ref);
182 Ok(())
183 })?;
184 let elapsed = start_time.elapsed();
185
186 let session_journal = result
188 .output_digest
189 .and_then(|digest| (digest != Digest::ZERO).then(|| journal.buf.take()));
190 if !result.exit_code.expects_output() && session_journal.is_some() {
191 tracing::debug!(
192 "dropping non-empty journal due to exit code {:?}: 0x{}",
193 result.exit_code,
194 hex::encode(journal.buf.borrow().as_slice())
195 );
196 };
197
198 let assumptions = self.syscall_table.assumptions_used.take();
201 let pending_zkrs = self.syscall_table.pending_zkrs.take();
202 let pending_keccaks = self.syscall_table.pending_keccaks.take();
203
204 if let Some(profiler) = self.profiler.take() {
205 let report = profiler.borrow_mut().finalize_to_vec();
206 std::fs::write(self.env.pprof_out.as_ref().unwrap(), report)?;
207 }
208
209 self.image = result.post_image.clone();
210 let syscall_metrics = self.syscall_table.metrics.borrow().clone();
211
212 let session = Session::new(
213 refs,
214 self.env.input_digest.unwrap_or_default(),
215 session_journal,
216 result.exit_code,
217 result.post_image,
218 assumptions,
219 result.user_cycles,
220 result.paging_cycles,
221 result.reserved_cycles,
222 result.total_cycles,
223 result.pre_state,
224 result.post_state,
225 pending_zkrs,
226 pending_keccaks,
227 result.ecall_metrics,
228 syscall_metrics,
229 );
230
231 tracing::info!("execution time: {elapsed:?}");
232 session.log();
233
234 Ok(session)
235 }
236}
237
238struct ContextAdapter<'a, 'b> {
239 ctx: &'b mut dyn NewSyscallContext,
240 syscall_table: SyscallTable<'a>,
241}
242
243impl<'a, 'b> SyscallContext<'a> for ContextAdapter<'a, 'b> {
244 fn get_pc(&self) -> u32 {
245 self.ctx.get_pc()
246 }
247
248 fn get_cycle(&self) -> u64 {
249 self.ctx.get_cycle()
250 }
251
252 fn load_register(&mut self, idx: usize) -> u32 {
253 self.ctx.peek_register(idx).unwrap()
254 }
255
256 fn load_page(&mut self, page_idx: u32) -> Result<Vec<u8>> {
257 self.ctx.peek_page(page_idx)
258 }
259
260 fn load_u8(&mut self, addr: ByteAddr) -> Result<u8> {
261 self.ctx.peek_u8(addr)
262 }
263
264 fn load_u32(&mut self, addr: ByteAddr) -> Result<u32> {
265 self.ctx.peek_u32(addr)
266 }
267
268 fn syscall_table(&self) -> &SyscallTable<'a> {
269 &self.syscall_table
270 }
271}
272
273impl<'a> NewSyscall for ExecutorImpl<'a> {
274 fn syscall(
275 &self,
276 syscall: &str,
277 ctx: &mut dyn NewSyscallContext,
278 into_guest: &mut [u32],
279 ) -> Result<(u32, u32)> {
280 let mut ctx = ContextAdapter {
281 ctx,
282 syscall_table: self.syscall_table.clone(),
283 };
284 self.syscall_table
285 .get_syscall(syscall)
286 .context(format!("Unknown syscall: {syscall:?}"))?
287 .borrow_mut()
288 .syscall(syscall, &mut ctx, into_guest)
289 }
290}
291
292#[derive(Clone, Default)]
294struct Journal {
295 buf: Rc<RefCell<Vec<u8>>>,
296}
297
298impl Write for Journal {
299 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
300 self.buf.borrow_mut().write(bytes)
301 }
302
303 fn flush(&mut self) -> std::io::Result<()> {
304 self.buf.borrow_mut().flush()
305 }
306}