risc0_zkvm_platform/
syscall.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#[cfg(target_os = "zkvm")]
16use core::arch::asm;
17use core::{cmp::min, ffi::CStr, ptr::null_mut, slice, str::Utf8Error};
18
19use crate::WORD_SIZE;
20
21pub mod ecall {
22    pub const HALT: u32 = 0;
23    pub const INPUT: u32 = 1;
24    pub const SOFTWARE: u32 = 2;
25    pub const SHA: u32 = 3;
26    pub const BIGINT: u32 = 4;
27    pub const USER: u32 = 5;
28    pub const BIGINT2: u32 = 6;
29    pub const POSEIDON2: u32 = 7;
30}
31
32pub mod halt {
33    pub const TERMINATE: u32 = 0;
34    pub const PAUSE: u32 = 1;
35    pub const SPLIT: u32 = 2;
36}
37
38pub mod reg_abi {
39    pub const REG_ZERO: usize = 0; // zero constant
40    pub const REG_RA: usize = 1; // return address
41    pub const REG_SP: usize = 2; // stack pointer
42    pub const REG_GP: usize = 3; // global pointer
43    pub const REG_TP: usize = 4; // thread pointer
44    pub const REG_T0: usize = 5; // temporary
45    pub const REG_T1: usize = 6; // temporary
46    pub const REG_T2: usize = 7; // temporary
47    pub const REG_S0: usize = 8; // saved register
48    pub const REG_FP: usize = 8; // frame pointer
49    pub const REG_S1: usize = 9; // saved register
50    pub const REG_A0: usize = 10; // fn arg / return value
51    pub const REG_A1: usize = 11; // fn arg / return value
52    pub const REG_A2: usize = 12; // fn arg
53    pub const REG_A3: usize = 13; // fn arg
54    pub const REG_A4: usize = 14; // fn arg
55    pub const REG_A5: usize = 15; // fn arg
56    pub const REG_A6: usize = 16; // fn arg
57    pub const REG_A7: usize = 17; // fn arg
58    pub const REG_S2: usize = 18; // saved register
59    pub const REG_S3: usize = 19; // saved register
60    pub const REG_S4: usize = 20; // saved register
61    pub const REG_S5: usize = 21; // saved register
62    pub const REG_S6: usize = 22; // saved register
63    pub const REG_S7: usize = 23; // saved register
64    pub const REG_S8: usize = 24; // saved register
65    pub const REG_S9: usize = 25; // saved register
66    pub const REG_S10: usize = 26; // saved register
67    pub const REG_S11: usize = 27; // saved register
68    pub const REG_T3: usize = 28; // temporary
69    pub const REG_T4: usize = 29; // temporary
70    pub const REG_T5: usize = 30; // temporary
71    pub const REG_T6: usize = 31; // temporary
72    pub const REG_MAX: usize = 32; // maximum number of registers
73}
74
75pub mod keccak_mode {
76    pub const KECCAK_PERMUTE: u32 = 0;
77    pub const KECCAK_PROVE: u32 = 1;
78}
79
80pub const DIGEST_WORDS: usize = 8;
81pub const DIGEST_BYTES: usize = WORD_SIZE * DIGEST_WORDS;
82
83pub const KECCACK_STATE_BYTES: usize = 200;
84pub const KECCACK_STATE_WORDS: usize = 200 / WORD_SIZE;
85pub const KECCACK_STATE_DWORDS: usize = 200 / 8;
86
87/// Number of words in each cycle received using the SOFTWARE ecall
88pub const IO_CHUNK_WORDS: usize = 4;
89
90// Limit syscall buffers so that the Executor doesn't get into an infinite
91// split situation.
92pub const MAX_BUF_BYTES: usize = 4 * 1024;
93pub const MAX_BUF_WORDS: usize = MAX_BUF_BYTES / WORD_SIZE;
94pub const MAX_SHA_COMPRESS_BLOCKS: usize = 1000;
95
96pub mod bigint {
97    pub const OP_MULTIPLY: u32 = 0;
98
99    /// BigInt width, in bits, handled by the BigInt accelerator circuit.
100    pub const WIDTH_BITS: usize = 256;
101
102    /// BigInt width, in bytes, handled by the BigInt accelerator circuit.
103    pub const WIDTH_BYTES: usize = WIDTH_BITS / 8;
104
105    /// BigInt width, in words, handled by the BigInt accelerator circuit.
106    pub const WIDTH_WORDS: usize = WIDTH_BYTES / crate::WORD_SIZE;
107}
108
109/// A UTF-8 NUL-terminated name of a syscall with static lifetime.
110#[derive(Clone, Copy, Debug)]
111#[repr(transparent)]
112pub struct SyscallName(*const u8);
113
114/// Construct a SyscallName declaration at compile time.
115///
116/// ```rust
117/// use risc0_zkvm_platform::declare_syscall;
118///
119/// declare_syscall!(SYS_MY_SYSTEM_CALL);
120/// ```
121#[macro_export]
122macro_rules! declare_syscall {
123    (
124        $(#[$meta:meta])*
125        $vis:vis $name:ident
126    ) => {
127        // Go through `CStr` to avoid `unsafe` in the caller.
128        $(#[$meta])*
129        $vis const $name: $crate::syscall::SyscallName = match ::core::ffi::CStr::from_bytes_until_nul(
130            concat!(module_path!(), "::", stringify!($name), "\0").as_bytes(),
131        ) {
132            Ok(c_str) => match $crate::syscall::SyscallName::from_c_str(c_str) {
133                Ok(name) => name,
134                Err(_) => unreachable!(),
135            },
136            Err(_) => unreachable!(),
137        };
138    };
139}
140
141pub mod nr {
142    declare_syscall!(pub SYS_ARGC);
143    declare_syscall!(pub SYS_ARGV);
144    declare_syscall!(pub SYS_CYCLE_COUNT);
145    declare_syscall!(pub SYS_EXIT);
146    declare_syscall!(pub SYS_FORK);
147    declare_syscall!(pub SYS_GETENV);
148    declare_syscall!(pub SYS_KECCAK);
149    declare_syscall!(pub SYS_LOG);
150    declare_syscall!(pub SYS_PANIC);
151    declare_syscall!(pub SYS_PIPE);
152    declare_syscall!(pub SYS_PROVE_KECCAK);
153    declare_syscall!(pub SYS_PROVE_ZKR);
154    declare_syscall!(pub SYS_RANDOM);
155    declare_syscall!(pub SYS_READ);
156    declare_syscall!(pub SYS_VERIFY_INTEGRITY);
157    declare_syscall!(pub SYS_VERIFY_INTEGRITY2);
158    declare_syscall!(pub SYS_WRITE);
159}
160
161impl SyscallName {
162    /// Converts a static C string to a system call name, if it is UTF-8.
163    #[inline]
164    pub const fn from_c_str(c_str: &'static CStr) -> Result<Self, Utf8Error> {
165        match c_str.to_str() {
166            Ok(_) => Ok(unsafe { Self::from_bytes_with_nul(c_str.as_ptr().cast()) }),
167            Err(error) => Err(error),
168        }
169    }
170
171    /// Converts a raw UTF-8 C string pointer to a system call name.
172    ///
173    /// # Safety
174    ///
175    /// The pointer must reference a static null-terminated UTF-8 string.
176    pub const unsafe fn from_bytes_with_nul(ptr: *const u8) -> Self {
177        Self(ptr)
178    }
179
180    pub fn as_ptr(&self) -> *const u8 {
181        self.0
182    }
183
184    pub fn as_str(&self) -> &str {
185        core::str::from_utf8(unsafe { core::ffi::CStr::from_ptr(self.as_ptr().cast()).to_bytes() })
186            .unwrap()
187    }
188}
189
190impl AsRef<str> for SyscallName {
191    fn as_ref(&self) -> &str {
192        self.as_str()
193    }
194}
195
196/// Returned registers (a0, a1) from a syscall invocation.
197#[repr(C)]
198pub struct Return(pub u32, pub u32);
199
200macro_rules! impl_syscall {
201    ($func_name:ident
202     // Ugh, unfortunately we can't make this a regular macro list since the asm macro
203     // doesn't expand register names so in($register) doesn't work.
204     $(, $a0:ident
205       $(, $a1:ident
206         $(, $a2: ident
207           $(, $a3: ident
208             $(, $a4: ident
209             )?
210           )?
211         )?
212       )?
213     )?) => {
214        /// Invoke a raw system call
215        ///
216        /// # Safety
217        ///
218        /// `from_host` must be aligned and dereferenceable.
219        #[cfg_attr(feature = "export-syscalls", no_mangle)]
220        pub unsafe extern "C" fn $func_name(syscall: SyscallName,
221                                 from_host: *mut u32,
222                                 from_host_words: usize
223                                 $(,$a0: u32
224                                   $(,$a1: u32
225                                     $(,$a2: u32
226                                       $(,$a3: u32
227                                         $(,$a4: u32
228                                         )?
229                                       )?
230                                     )?
231                                   )?
232                                 )?
233        ) -> Return {
234            #[cfg(target_os = "zkvm")] {
235                let a0: u32;
236                let a1: u32;
237                ::core::arch::asm!(
238                    "ecall",
239                    in("t0") $crate::syscall::ecall::SOFTWARE,
240                    inlateout("a0") from_host => a0,
241                    inlateout("a1") from_host_words => a1,
242                    in("a2") syscall.as_ptr()
243                        $(,in("a3") $a0
244                          $(,in("a4") $a1
245                            $(,in("a5") $a2
246                              $(,in("a6") $a3
247                                $(,in("a7") $a4
248                                )?
249                              )?
250                            )?
251                          )?
252                        )?);
253                Return(a0, a1)
254            }
255            #[cfg(not(target_os = "zkvm"))]
256            unimplemented!()
257        }
258    }
259}
260
261impl_syscall!(syscall_0);
262impl_syscall!(syscall_1, a3);
263impl_syscall!(syscall_2, a3, a4);
264impl_syscall!(syscall_3, a3, a4, a5);
265impl_syscall!(syscall_4, a3, a4, a5, a6);
266impl_syscall!(syscall_5, a3, a4, a5, a6, a7);
267
268fn ecall_1(t0: u32, a0: u32, a1: u32) {
269    #[cfg(target_os = "zkvm")]
270    unsafe {
271        asm!(
272            "ecall",
273            in("t0") t0,
274            in("a0") a0,
275            in("a1") a1,
276        )
277    };
278    #[cfg(not(target_os = "zkvm"))]
279    {
280        core::hint::black_box((t0, a0, a1));
281        unimplemented!()
282    }
283}
284
285fn ecall_3(t0: u32, a0: u32, a1: u32, a2: u32, a3: u32) {
286    #[cfg(target_os = "zkvm")]
287    unsafe {
288        asm!(
289            "ecall",
290            in("t0") t0,
291            in("a0") a0,
292            in("a1") a1,
293            in("a2") a2,
294            in("a3") a3,
295        )
296    };
297    #[cfg(not(target_os = "zkvm"))]
298    {
299        core::hint::black_box((t0, a0, a1, a2, a3));
300        unimplemented!()
301    }
302}
303
304fn ecall_4(t0: u32, a0: u32, a1: u32, a2: u32, a3: u32, a4: u32) {
305    #[cfg(target_os = "zkvm")]
306    unsafe {
307        asm!(
308            "ecall",
309            in("t0") t0,
310            in("a0") a0,
311            in("a1") a1,
312            in("a2") a2,
313            in("a3") a3,
314            in("a4") a4,
315        )
316    };
317    #[cfg(not(target_os = "zkvm"))]
318    {
319        core::hint::black_box((t0, a0, a1, a2, a3, a4));
320        unimplemented!()
321    }
322}
323
324/// # Safety
325///
326/// `out_state` must be aligned and dereferenceable.
327// [inline(never)] is added to mitigate potentially leaking information about program execution
328// through the final value of the program counter (pc) on halt where there is more than one
329// location in the program where `sys_halt` is called. As long as the halt instruction only exists
330// in one place within the program, the pc will always be the same invariant with input.
331#[inline(never)]
332#[cfg_attr(feature = "export-syscalls", no_mangle)]
333pub extern "C" fn sys_halt(user_exit: u8, out_state: *const [u32; DIGEST_WORDS]) -> ! {
334    ecall_1(
335        ecall::HALT,
336        halt::TERMINATE | ((user_exit as u32) << 8),
337        out_state as u32,
338    );
339    unreachable!();
340}
341
342/// # Safety
343///
344/// `out_state` must be aligned and dereferenceable.
345// [inline(never)] is added to mitigate potentially leaking information about program execution
346// through the final value of the program counter (pc) on pause where there is more than one
347// location in the program where `sys_pause` is called. As long as the pause instruction only exists
348// in one place within the program, the pc will always be the same invariant with input.
349#[inline(never)]
350#[cfg_attr(feature = "export-syscalls", no_mangle)]
351pub unsafe extern "C" fn sys_pause(user_exit: u8, out_state: *const [u32; DIGEST_WORDS]) {
352    ecall_1(
353        ecall::HALT,
354        halt::PAUSE | ((user_exit as u32) << 8),
355        out_state as u32,
356    );
357}
358
359#[cfg_attr(feature = "export-syscalls", no_mangle)]
360pub extern "C" fn sys_input(index: u32) -> u32 {
361    let t0 = ecall::INPUT;
362    let index = index & 0x07;
363    #[cfg(target_os = "zkvm")]
364    unsafe {
365        let a0: u32;
366        asm!(
367            "ecall",
368            in("t0") t0,
369            inlateout("a0") index => a0,
370        );
371        a0
372    }
373    #[cfg(not(target_os = "zkvm"))]
374    {
375        core::hint::black_box((t0, index));
376        unimplemented!()
377    }
378}
379
380/// # Safety
381///
382/// `out_state`, `in_state`, `block1_ptr`, and `block2_ptr` must be aligned and
383/// dereferenceable.
384#[cfg_attr(feature = "export-syscalls", no_mangle)]
385#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
386pub unsafe extern "C" fn sys_sha_compress(
387    out_state: *mut [u32; DIGEST_WORDS],
388    in_state: *const [u32; DIGEST_WORDS],
389    block1_ptr: *const [u32; DIGEST_WORDS],
390    block2_ptr: *const [u32; DIGEST_WORDS],
391) {
392    ecall_4(
393        ecall::SHA,
394        out_state as u32,
395        in_state as u32,
396        block1_ptr as u32,
397        block2_ptr as u32,
398        1,
399    );
400}
401
402/// # Safety
403///
404/// `out_state`, `in_state`, and `buf` must be aligned and dereferenceable.
405#[cfg_attr(feature = "export-syscalls", no_mangle)]
406#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
407pub unsafe extern "C" fn sys_sha_buffer(
408    out_state: *mut [u32; DIGEST_WORDS],
409    in_state: *const [u32; DIGEST_WORDS],
410    buf: *const u8,
411    count: u32,
412) {
413    let mut ptr = buf;
414    let mut count_remain = count;
415    let mut in_state = in_state;
416    while count_remain > 0 {
417        let count = min(count_remain, MAX_SHA_COMPRESS_BLOCKS as u32);
418        ecall_4(
419            ecall::SHA,
420            out_state as u32,
421            in_state as u32,
422            ptr as u32,
423            ptr.add(DIGEST_BYTES) as u32,
424            count,
425        );
426        count_remain -= count;
427        ptr = ptr.add(2 * DIGEST_BYTES * count as usize);
428        in_state = out_state;
429    }
430}
431
432/// # Safety
433///
434/// `state_addr`, `in_buf_addr`, and `out_buf_addr` must be word-aligned and
435/// dereferenceable.
436#[cfg_attr(feature = "export-syscalls", no_mangle)]
437#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
438pub unsafe extern "C" fn sys_poseidon2(
439    state_addr: *mut [u32; DIGEST_WORDS],
440    in_buf_addr: *const u8,
441    out_buf_addr: *mut [u32; DIGEST_WORDS],
442    bits_count: u32,
443) {
444    debug_assert!(state_addr as usize % WORD_SIZE == 0);
445    debug_assert!(in_buf_addr as usize % WORD_SIZE == 0);
446    debug_assert!(out_buf_addr as usize % WORD_SIZE == 0);
447
448    ecall_3(
449        ecall::POSEIDON2,
450        state_addr as u32 / WORD_SIZE as u32,
451        in_buf_addr as u32 / WORD_SIZE as u32,
452        out_buf_addr as u32 / WORD_SIZE as u32,
453        bits_count,
454    );
455}
456
457/// # Safety
458///
459/// `result`, `x`, `y`, and `modulus` must be aligned and dereferenceable.
460#[cfg_attr(feature = "export-syscalls", no_mangle)]
461#[cfg_attr(not(feature = "export-syscalls"), inline(always))]
462pub unsafe extern "C" fn sys_bigint(
463    result: *mut [u32; bigint::WIDTH_WORDS],
464    op: u32,
465    x: *const [u32; bigint::WIDTH_WORDS],
466    y: *const [u32; bigint::WIDTH_WORDS],
467    modulus: *const [u32; bigint::WIDTH_WORDS],
468) {
469    ecall_4(
470        ecall::BIGINT,
471        result as u32,
472        op,
473        x as u32,
474        y as u32,
475        modulus as u32,
476    );
477}
478
479/// # Safety
480///
481/// `recv_buf` must be aligned and dereferenceable.
482#[cfg_attr(feature = "export-syscalls", no_mangle)]
483pub unsafe extern "C" fn sys_rand(recv_buf: *mut u32, words: usize) {
484    syscall_0(nr::SYS_RANDOM, recv_buf, words);
485}
486
487/// # Safety
488///
489/// `msg_ptr` must be aligned and dereferenceable.
490#[cfg_attr(feature = "export-syscalls", no_mangle)]
491pub unsafe extern "C" fn sys_panic(msg_ptr: *const u8, len: usize) -> ! {
492    syscall_2(nr::SYS_PANIC, null_mut(), 0, msg_ptr as u32, len as u32);
493
494    // As a fallback for non-compliant hosts, issue an illegal instruction.
495    #[cfg(target_os = "zkvm")]
496    asm!("sw x0, 1(x0)");
497    unreachable!()
498}
499
500/// # Safety
501///
502/// `msg_ptr` must be aligned and dereferenceable.
503#[cfg_attr(feature = "export-syscalls", no_mangle)]
504pub unsafe extern "C" fn sys_log(msg_ptr: *const u8, len: usize) {
505    syscall_2(nr::SYS_LOG, null_mut(), 0, msg_ptr as u32, len as u32);
506}
507
508#[cfg_attr(feature = "export-syscalls", no_mangle)]
509pub extern "C" fn sys_cycle_count() -> u64 {
510    let Return(hi, lo) = unsafe { syscall_0(nr::SYS_CYCLE_COUNT, null_mut(), 0) };
511    ((hi as u64) << 32) + lo as u64
512}
513
514/// Reads the given number of bytes into the given buffer, posix-style.  Returns
515/// the number of bytes actually read.  On end of file, returns 0.
516///
517/// Like POSIX read, this is not guaranteed to read all bytes
518/// requested.  If we haven't reached EOF, it is however guaranteed to
519/// read at least one byte.
520///
521/// Users should prefer a higher-level abstraction.
522///
523/// # Safety
524///
525/// `recv_ptr` must be aligned and dereferenceable.
526#[cfg_attr(feature = "export-syscalls", no_mangle)]
527pub unsafe extern "C" fn sys_read(fd: u32, recv_ptr: *mut u8, nread: usize) -> usize {
528    // The SYS_READ system call can do a given number of word-aligned reads
529    // efficiently. The semantics of the system call are:
530    //
531    //   (nread, word) = syscall_2(nr::SYS_READ, outbuf,
532    //                             num_words_in_outbuf, fd, nbytes);
533    //
534    // This reads exactly nbytes from the file descriptor, and fills the words
535    // in outbuf, followed by up to 4 bytes returned in "word", and fills
536    // the rest with NULs.  It returns the number of bytes read.
537    //
538    // sys_read exposes this as a byte-aligned read by:
539    //   * Copies any unaligned bytes at the start or end of the region.
540
541    // Fills 0-3 bytes from a u32 into memory, returning the pointer afterwards.
542    unsafe fn fill_from_word(mut ptr: *mut u8, mut word: u32, nfill: usize) -> *mut u8 {
543        debug_assert!(nfill < 4, "nfill={nfill}");
544        for _ in 0..nfill {
545            *ptr = (word & 0xFF) as u8;
546            word >>= 8;
547            ptr = ptr.add(1);
548        }
549        ptr
550    }
551
552    // Determine how many bytes at the beginning of the buffer we have
553    // to read in order to become word-aligned.
554    let ptr_offset = (recv_ptr as usize) & (WORD_SIZE - 1);
555    let (main_ptr, main_requested, nread_first) = if ptr_offset == 0 {
556        (recv_ptr, nread, 0)
557    } else {
558        let unaligned_at_start = min(nread, WORD_SIZE - ptr_offset);
559        // Read unaligned bytes into "firstword".
560        let Return(nread_first, firstword) =
561            syscall_2(nr::SYS_READ, null_mut(), 0, fd, unaligned_at_start as u32);
562        debug_assert_eq!(nread_first as usize, unaligned_at_start);
563
564        // Align up to a word boundary to do the main copy.
565        let main_ptr = fill_from_word(recv_ptr, firstword, unaligned_at_start);
566        if nread == unaligned_at_start {
567            // We only read part of a word, and don't have to read any full words.
568            return nread;
569        }
570        (main_ptr, nread - unaligned_at_start, nread_first as usize)
571    };
572
573    // Copy in all of the word-aligned data
574    let main_words = main_requested / WORD_SIZE;
575    let (nread_main, lastword) =
576        sys_read_internal(fd, main_ptr as *mut u32, main_words, main_requested);
577    debug_assert!(nread_main <= main_requested);
578    let read_words = nread_main / WORD_SIZE;
579
580    // Copy in individual bytes after the word-aligned section.
581    let unaligned_at_end = main_requested % WORD_SIZE;
582
583    // The last 0-3 bytes are returned in lastword. Write those to complete the _requested_ read amount.
584    fill_from_word(
585        main_ptr.add(main_words * WORD_SIZE),
586        lastword,
587        unaligned_at_end,
588    );
589
590    nread_first + nread_main
591}
592
593/// Reads up to the given number of words into the buffer [recv_buf,
594/// recv_buf + nwords).  Returns the number of bytes actually read.
595/// sys_read_words is a more efficient interface than sys_read, but
596/// varies from POSIX semantics.  Notably:
597///
598/// * The read length is specified in words, not bytes.  (The output
599///   length is still returned in bytes)
600///
601/// * If not all data is available, `sys_read_words` will return a short read.
602///
603/// * recv_buf must be word-aligned.
604///
605/// * Return a short read in the case of EOF mid-way through.
606///
607/// # Safety
608///
609/// `recv_ptr' must be a word-aligned pointer and point to a region of
610/// `nwords' size.
611#[cfg_attr(feature = "export-syscalls", no_mangle)]
612pub unsafe extern "C" fn sys_read_words(fd: u32, recv_ptr: *mut u32, nwords: usize) -> usize {
613    sys_read_internal(fd, recv_ptr, nwords, nwords * WORD_SIZE).0
614}
615
616fn sys_read_internal(fd: u32, recv_ptr: *mut u32, nwords: usize, nbytes: usize) -> (usize, u32) {
617    let mut nwords_remain = nwords;
618    let mut nbytes_remain = nbytes;
619    let mut nread_total_bytes = 0;
620    let mut recv_ptr = recv_ptr;
621    let mut final_word = 0;
622    while nbytes_remain > 0 {
623        debug_assert!(
624            final_word == 0,
625            "host returned non-zero final word on a fully aligned read"
626        );
627        let chunk_len = min(nbytes_remain, MAX_BUF_BYTES) as u32;
628        let Return(nread_bytes, last_word) = unsafe {
629            syscall_2(
630                nr::SYS_READ,
631                recv_ptr,
632                min(nwords_remain, MAX_BUF_WORDS),
633                fd,
634                chunk_len,
635            )
636        };
637        let nread_bytes = nread_bytes as usize;
638        let nread_words = nread_bytes / WORD_SIZE;
639        recv_ptr = unsafe { recv_ptr.add(nread_words) };
640        final_word = last_word;
641        nwords_remain -= nread_words;
642        nread_total_bytes += nread_bytes;
643        nbytes_remain -= nread_bytes;
644        if nread_bytes < chunk_len as usize {
645            // We've reached EOF, and the host has returned a partial word.
646            break;
647        }
648    }
649    (nread_total_bytes, final_word)
650}
651
652/// # Safety
653///
654/// `write_ptr` must be aligned and dereferenceable.
655#[cfg_attr(feature = "export-syscalls", no_mangle)]
656pub unsafe extern "C" fn sys_write(fd: u32, write_ptr: *const u8, nbytes: usize) {
657    let mut nbytes_remain = nbytes;
658    let mut write_ptr = write_ptr;
659    while nbytes_remain > 0 {
660        let nbytes = min(nbytes_remain, MAX_BUF_BYTES);
661        syscall_3(
662            nr::SYS_WRITE,
663            null_mut(),
664            0,
665            fd,
666            write_ptr as u32,
667            nbytes as u32,
668        );
669        write_ptr = write_ptr.add(nbytes);
670        nbytes_remain -= nbytes;
671    }
672}
673
674// Some environment variable names are considered safe by default to use in the guest, provided by
675// the host, and are included in this list. It may be useful to allow guest developers to register
676// additional variable names as part of their guest program.
677const ALLOWED_ENV_VARNAMES: &[&[u8]] = &[
678    b"RUST_BACKTRACE",
679    b"RUST_LIB_BACKTRACE",
680    b"RISC0_KECCAK_PO2",
681];
682
683/// Retrieves the value of an environment variable, and stores as much
684/// of it as it can it in the memory at [out_words, out_words +
685/// out_nwords).
686///
687/// Returns the length of the value, in bytes, or usize::MAX if the variable is
688/// not set.
689///
690/// This is normally called twice to read an environment variable:
691/// Once to get the length of the value, and once to fill in allocated
692/// memory.
693///
694/// NOTE: Repeated calls to sys_getenv are not guaranteed to result in the same
695/// data being returned. Returned data is entirely in the control of the host.
696///
697/// # Safety
698///
699/// `out_words` and `varname` must be aligned and dereferenceable.
700#[cfg_attr(feature = "export-syscalls", no_mangle)]
701pub unsafe extern "C" fn sys_getenv(
702    out_words: *mut u32,
703    out_nwords: usize,
704    varname: *const u8,
705    varname_len: usize,
706) -> usize {
707    if cfg!(not(feature = "sys-getenv")) {
708        let mut allowed = false;
709        for allowed_varname in ALLOWED_ENV_VARNAMES {
710            let varname_buf = unsafe { slice::from_raw_parts(varname, varname_len) };
711            if *allowed_varname == varname_buf {
712                allowed = true;
713                break;
714            }
715        }
716        if !allowed {
717            const MSG_1: &[u8] = "sys_getenv not enabled for var".as_bytes();
718            unsafe { sys_log(MSG_1.as_ptr(), MSG_1.len()) };
719            unsafe { sys_log(varname, varname_len) };
720            const MSG_2: &[u8] = "sys_getenv is disabled; can be enabled with the sys-getenv feature flag on risc0-zkvm-platform".as_bytes();
721            unsafe { sys_panic(MSG_2.as_ptr(), MSG_2.len()) };
722        }
723    }
724    let Return(a0, _) = syscall_2(
725        nr::SYS_GETENV,
726        out_words,
727        out_nwords,
728        varname as u32,
729        varname_len as u32,
730    );
731    if a0 == u32::MAX {
732        usize::MAX
733    } else {
734        a0 as usize
735    }
736}
737
738/// Retrieves the count of arguments provided to program execution.
739///
740/// NOTE: Repeated calls to sys_argc are not guaranteed to result in the same
741/// data being returned. Returned data is entirely in the control of the host.
742#[cfg_attr(feature = "export-syscalls", no_mangle)]
743pub extern "C" fn sys_argc() -> usize {
744    if cfg!(not(feature = "sys-args")) {
745        const MSG: &[u8] = "sys_argc is disabled; can be enabled with the sys-args feature flag on risc0-zkvm-platform".as_bytes();
746        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
747    }
748    let Return(a0, _) = unsafe { syscall_0(nr::SYS_ARGC, null_mut(), 0) };
749    a0 as usize
750}
751
752/// Retrieves the argument with arg_index, and stores as much
753/// of it as it can it in the memory at [out_words, out_words +
754/// out_nwords).
755///
756/// Returns the length, in bytes, of the argument string. If the requested
757/// argument index does not exist (i.e. `arg_index` >= argc) then this syscall
758/// will not return.
759///
760/// This is normally called twice to read an argument: Once to get the length of
761/// the value, and once to fill in allocated memory.
762///
763/// NOTE: Repeated calls to sys_argv are not guaranteed to result in the same
764/// data being returned. Returned data is entirely in the control of the host.
765///
766/// # Safety
767///
768/// `out_words` must be aligned and dereferenceable.
769#[cfg_attr(feature = "export-syscalls", no_mangle)]
770pub unsafe extern "C" fn sys_argv(
771    out_words: *mut u32,
772    out_nwords: usize,
773    arg_index: usize,
774) -> usize {
775    if cfg!(not(feature = "sys-args")) {
776        const MSG: &[u8] = "sys_argv is disabled; can be enabled with the sys-args feature flag on risc0-zkvm-platform".as_bytes();
777        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
778    }
779    let Return(a0, _) = syscall_1(nr::SYS_ARGV, out_words, out_nwords, arg_index as u32);
780    a0 as usize
781}
782
783#[cfg_attr(feature = "export-syscalls", no_mangle)]
784#[deprecated]
785pub extern "C" fn sys_alloc_words(nwords: usize) -> *mut u32 {
786    unsafe { sys_alloc_aligned(WORD_SIZE * nwords, WORD_SIZE) as *mut u32 }
787}
788
789/// # Safety
790///
791/// This function should be safe to call, but clippy complains if it is not marked as `unsafe`.
792#[cfg(all(feature = "export-syscalls", not(target_os = "zkvm")))]
793#[no_mangle]
794pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 {
795    unimplemented!("sys_alloc_aligned called outside of target_os = zkvm");
796}
797
798/// # Safety
799///
800/// This function should be safe to call, but clippy complains if it is not marked as `unsafe`.
801#[cfg(all(
802    feature = "export-syscalls",
803    feature = "heap-embedded-alloc",
804    target_os = "zkvm"
805))]
806#[no_mangle]
807pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 {
808    use core::alloc::GlobalAlloc;
809    crate::heap::embedded::HEAP.alloc(core::alloc::Layout::from_size_align(bytes, align).unwrap())
810}
811
812/// # Safety
813///
814/// This function should be safe to call, but clippy complains if it is not marked as `unsafe`.
815#[cfg(all(
816    feature = "export-syscalls",
817    not(feature = "heap-embedded-alloc"),
818    target_os = "zkvm"
819))]
820#[no_mangle]
821pub unsafe extern "C" fn sys_alloc_aligned(bytes: usize, align: usize) -> *mut u8 {
822    crate::heap::bump::alloc_aligned(bytes, align)
823}
824
825/// Send a ReceiptClaim digest to the host to request verification.
826///
827/// A cooperative prover will only return if there is a verifying proof
828/// associated with that claim digest, and will always return a result code
829/// of 0 to register a0. The caller must encode the claim_digest into a
830/// public assumptions list for inclusion in the guest output.
831///
832/// # Safety
833///
834/// `claim_digest` must be aligned and dereferenceable.
835/// `control_root` must be aligned and dereferenceable.
836#[cfg(feature = "export-syscalls")]
837#[no_mangle]
838pub unsafe extern "C" fn sys_verify_integrity(
839    claim_digest: *const [u32; DIGEST_WORDS],
840    control_root: *const [u32; DIGEST_WORDS],
841) {
842    let mut to_host = [0u32; DIGEST_WORDS * 2];
843    to_host[..DIGEST_WORDS].copy_from_slice(claim_digest.as_ref().unwrap_unchecked());
844    to_host[DIGEST_WORDS..].copy_from_slice(control_root.as_ref().unwrap_unchecked());
845
846    let Return(a0, _) = unsafe {
847        // Send the claim_digest to the host via software ecall.
848        syscall_2(
849            nr::SYS_VERIFY_INTEGRITY,
850            null_mut(),
851            0,
852            to_host.as_ptr() as u32,
853            (DIGEST_BYTES * 2) as u32,
854        )
855    };
856
857    // Check to ensure the host indicated success by returning 0.
858    // This should always be the case. This check is included for
859    // forwards-compatibility.
860    if a0 != 0 {
861        const MSG: &[u8] = "sys_verify_integrity returned error result".as_bytes();
862        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
863    }
864}
865
866/// TODO: Send a ReceiptClaim digest to the host to request verification. Meant for proofs that use union.
867///
868/// # Safety
869///
870/// `claim_digest` must be aligned and dereferenceable.
871/// `control_root` must be aligned and dereferenceable.
872#[cfg(feature = "export-syscalls")]
873#[no_mangle]
874pub unsafe extern "C" fn sys_verify_integrity2(
875    claim_digest: *const [u32; DIGEST_WORDS],
876    control_root: *const [u32; DIGEST_WORDS],
877) {
878    let mut to_host = [0u32; DIGEST_WORDS * 2];
879    to_host[..DIGEST_WORDS].copy_from_slice(claim_digest.as_ref().unwrap_unchecked());
880    to_host[DIGEST_WORDS..].copy_from_slice(control_root.as_ref().unwrap_unchecked());
881
882    let Return(a0, _) = unsafe {
883        // Send the claim_digest to the host via software ecall.
884        syscall_2(
885            nr::SYS_VERIFY_INTEGRITY2,
886            null_mut(),
887            0,
888            to_host.as_ptr() as u32,
889            (DIGEST_BYTES * 2) as u32,
890        )
891    };
892
893    // Check to ensure the host indicated success by returning 0.
894    // This should always be the case. This check is included for
895    // forwards-compatibility.
896    if a0 != 0 {
897        const MSG: &[u8] = "sys_verify_integrity2 returned error result".as_bytes();
898        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
899    }
900}
901// Make sure we only get one of these since it's stateful.
902#[cfg(not(feature = "export-syscalls"))]
903extern "C" {
904    pub fn sys_alloc_aligned(nwords: usize, align: usize) -> *mut u8;
905}
906
907/// `sys_fork()` creates a new process by duplicating the calling process. The
908/// new process is referred to as the child process. The calling process is
909/// referred to as the parent process.
910///
911/// The child process and the parent process run in separate memory spaces. At
912/// the time of `sys_fork()` both memory spaces have the same content.
913///
914/// # Return Value
915///
916/// On success, the PID of the child process (1) is returned in the parent, and
917/// 0 is returned in the child. On failure, -1 is returned in the parent, no
918/// child process is created.
919#[cfg(feature = "export-syscalls")]
920#[no_mangle]
921pub extern "C" fn sys_fork() -> i32 {
922    let Return(a0, _) = unsafe { syscall_0(nr::SYS_FORK, null_mut(), 0) };
923    a0 as i32
924}
925
926/// `sys_pipe()` creates a pipe, a unidirectional data channel that can be used
927/// for interprocess communication. The pointer `pipefd` is used to return two
928/// file descriptors referring to the ends of the pipe. `pipefd[0]` refers to
929/// the read end of the pipe. `pipefd[1]` refers to the write end of the pipe.
930/// Data written to the write end of the pipe is buffered by the host until it
931/// is read from the read end of the pipe.
932///
933/// # Return Value
934///
935/// On success, zero is returned.  On error, -1 is returned, and `pipefd` is
936/// left unchanged.
937///
938/// # Safety
939///
940/// `pipefd` must be aligned, dereferenceable, and have capacity for 2 u32
941/// values.
942#[cfg(feature = "export-syscalls")]
943#[no_mangle]
944pub unsafe extern "C" fn sys_pipe(pipefd: *mut u32) -> i32 {
945    let Return(a0, _) = syscall_0(nr::SYS_PIPE, pipefd, 2);
946    a0 as i32
947}
948
949/// `sys_exit()` causes normal process termination.
950///
951/// Currently the `status` is unused and ignored.
952#[cfg(feature = "export-syscalls")]
953#[no_mangle]
954pub extern "C" fn sys_exit(status: i32) -> ! {
955    let Return(a0, _) = unsafe { syscall_0(nr::SYS_EXIT, null_mut(), 0) };
956    #[allow(clippy::empty_loop)]
957    loop {
958        // prevent dishonest provers from relying on the ability to prove the
959        // child process rather than the intended parent process.
960    }
961}
962
963/// Permute the keccak state on the host
964///
965/// # Safety
966#[cfg_attr(feature = "export-syscalls", no_mangle)]
967pub unsafe extern "C" fn sys_keccak(
968    in_state: *const [u64; KECCACK_STATE_DWORDS],
969    out_state: *mut [u64; KECCACK_STATE_DWORDS],
970) -> i32 {
971    let Return(a0, _) = syscall_3(
972        nr::SYS_KECCAK,
973        out_state as *mut u32,
974        KECCACK_STATE_WORDS,
975        keccak_mode::KECCAK_PERMUTE,
976        in_state as u32,
977        0,
978    );
979    a0 as i32
980}
981
982/// Executes the keccak circuit, and then executes the lift predicate
983/// in the recursion circuit.
984///
985/// This only triggers the execution of the circuits; it does not add
986/// any assumptions.  In order to prove that it executed correctly,
987/// users must calculate the claim digest and add it to the list of
988/// assumptions.
989///
990/// # Safety
991///
992/// `claim_digest` must be aligned and dereferenceable.
993/// `control_root` must be aligned and dereferenceable.
994/// `input` must be aligned and have `input_len` u32s dereferenceable
995#[cfg_attr(feature = "export-syscalls", no_mangle)]
996pub unsafe extern "C" fn sys_prove_keccak(
997    claim_digest: *const [u32; DIGEST_WORDS],
998    control_root: *const [u32; DIGEST_WORDS],
999) {
1000    let Return(a0, _) = unsafe {
1001        syscall_3(
1002            nr::SYS_KECCAK,
1003            null_mut(),
1004            0,
1005            keccak_mode::KECCAK_PROVE,
1006            claim_digest as u32,
1007            control_root as u32,
1008        )
1009    };
1010
1011    // Check to ensure the host indicated success by returning 0.
1012    // Currently, this should always be the case. This check is
1013    // included for forwards-compatibility.
1014    if a0 != 0 {
1015        const MSG: &[u8] = "sys_prove_keccak returned error result".as_bytes();
1016        unsafe { sys_panic(MSG.as_ptr(), MSG.len()) };
1017    }
1018}
1019
1020#[repr(C)]
1021pub struct BigIntBlobHeader {
1022    pub nondet_program_size: u32,
1023    pub verify_program_size: u32,
1024    pub consts_size: u32,
1025    pub temp_size: u32,
1026}
1027
1028macro_rules! impl_sys_bigint2 {
1029    ($func_name:ident, $a1:ident
1030        $(, $a2: ident
1031            $(, $a3: ident
1032                $(, $a4: ident
1033                    $(, $a5: ident
1034                        $(, $a6: ident)?
1035                    )?
1036                )?
1037            )?
1038        )?
1039    ) => {
1040        /// Invoke a bigint2 program.
1041        ///
1042        /// # Safety
1043        ///
1044        /// `blob_ptr` and all arguments must be aligned and dereferenceable.
1045        #[cfg_attr(feature = "export-syscalls", no_mangle)]
1046        pub unsafe extern "C" fn $func_name(blob_ptr: *const u8, a1: *const u32
1047            $(, $a2: *const u32
1048                $(, $a3: *const u32
1049                    $(, $a4: *const u32
1050                        $(, $a5: *const u32
1051                            $(, $a6: *const u32)?
1052                        )?
1053                    )?
1054                )?
1055            )?
1056        ) {
1057            #[cfg(target_os = "zkvm")]
1058            {
1059                let header = blob_ptr as *const $crate::syscall::BigIntBlobHeader;
1060                let nondet_program_ptr = (header.add(1)) as *const u32;
1061                let verify_program_ptr = nondet_program_ptr.add((*header).nondet_program_size as usize);
1062                let consts_ptr = verify_program_ptr.add((*header).verify_program_size as usize);
1063                let temp_space = ((*header).temp_size as usize) << 2;
1064
1065                ::core::arch::asm!(
1066                    "sub sp, sp, {temp_space}",
1067                    "ecall",
1068                    "add sp, sp, {temp_space}",
1069                    temp_space = in(reg) temp_space,
1070                    in("t0") ecall::BIGINT2,
1071                    in("t1") nondet_program_ptr,
1072                    in("t2") verify_program_ptr,
1073                    in("t3") consts_ptr,
1074                    in("a0") blob_ptr,
1075                    in("a1") a1,
1076                    $(in("a2") $a2,
1077                        $(in("a3") $a3,
1078                            $(in("a4") $a4,
1079                                $(in("a5") $a5,
1080                                    $(in("a6") $a6)?
1081                                )?
1082                            )?
1083                        )?
1084                    )?
1085                );
1086            }
1087
1088            #[cfg(not(target_os = "zkvm"))]
1089            unimplemented!()
1090        }
1091    }
1092}
1093
1094impl_sys_bigint2!(sys_bigint2_1, a1);
1095impl_sys_bigint2!(sys_bigint2_2, a1, a2);
1096impl_sys_bigint2!(sys_bigint2_3, a1, a2, a3);
1097impl_sys_bigint2!(sys_bigint2_4, a1, a2, a3, a4);
1098impl_sys_bigint2!(sys_bigint2_5, a1, a2, a3, a4, a5);
1099impl_sys_bigint2!(sys_bigint2_6, a1, a2, a3, a4, a5, a6);