diff --git a/executor/programs/rust/ef_io_demo/src/main.rs b/executor/programs/rust/ef_io_demo/src/main.rs index ef0690398..c784b9668 100644 --- a/executor/programs/rust/ef_io_demo/src/main.rs +++ b/executor/programs/rust/ef_io_demo/src/main.rs @@ -1,15 +1,25 @@ -// Demo guest exercising the EF zkVM IO interface (`read_input` / `write_output`). +// Demo guest exercising the EF zkVM IO interface (`read_input` / `write_output` / +// `write_log`). // // Reads the private input via the EF zero-copy `read_input` shim, then emits it // back as the public output in TWO `write_output` calls (split in halves) to // exercise the multi-call concatenation requirement of the EF spec. +// +// Also makes one `write_log` call. That channel is a no-op on Lambda VM, so this +// proves the symbol contract rather than any output: a portable guest calling +// `write_log` links and runs. It is the only thing that does — the symbol is +// dead-stripped from guests that never call it. use lambda_vm_syscalls as syscalls; +/// Diagnostic text for the `write_log` call. Must be valid UTF-8 per the EF spec. +const LOG_MESSAGE: &str = "ef_io_demo: echoing private input to public output\n"; + pub fn main() { let mut buf_ptr: *const u8 = core::ptr::null(); let mut buf_size: usize = 0; unsafe { syscalls::ef_io::read_input(&mut buf_ptr, &mut buf_size); + syscalls::ef_io::write_log(LOG_MESSAGE.as_ptr(), LOG_MESSAGE.len()); } if buf_size > 0 { diff --git a/syscalls/src/ef_io.rs b/syscalls/src/ef_io.rs index dabf7818d..6d77afebf 100644 --- a/syscalls/src/ef_io.rs +++ b/syscalls/src/ef_io.rs @@ -1,11 +1,16 @@ //! EF zkVM IO interface: //! -//! Two C-callable functions that match the EF standard so portable applications -//! compile unchanged across zkVMs: +//! Three C-callable functions that match the EF standard so portable +//! applications compile unchanged across zkVMs: //! //! - `read_input`: returns a zero-copy pointer + size to the private input. //! - `write_output`: appends bytes to the public output. Multiple calls //! concatenate. +//! - `write_log`: diagnostic (`println`-style) UTF-8 text for the host. Not +//! part of the statement being proven. Specified by +//! , which amends the +//! interface above from two functions to three; until it merges, the linked +//! README documents only the first two. //! //! On Lambda VM these map to: //! - `read_input` → memory-mapped private input region at `0xFF000000` @@ -13,6 +18,10 @@ //! - `write_output` → ECALL #64 (Commit). The trace builder maintains a //! running commitment index in synthetic register `x254`, so multiple //! ECALLs naturally concatenate at the proof level. +//! - `write_log` → nothing at all. The standard lets proving environments that +//! don't expose diagnostics ignore these calls, and the `Print` ecall it +//! would otherwise use has no Ecall-bus receiver (see `write_log`'s doc +//! comment and `syscalls::print_string`). #[cfg(target_arch = "riscv64")] use core::arch::asm; @@ -67,6 +76,26 @@ pub unsafe extern "C" fn write_output(output: *const u8, size: usize) { } } +/// EF IO: emit `utf8_len` bytes of UTF-8 diagnostic text (`println`-style) for +/// the host. Intentionally a no-op: it emits no ecall and reads no memory. +/// +/// The natural lowering would be the `Print` ecall (a7=1), but that ecall has +/// no receiver on the Ecall bus, so emitting it unbalances the LogUp argument +/// and every proof using it fails to verify — the same reason +/// `syscalls::print_string` is a no-op. Ignoring the call is conforming: per +/// the spec the log text "does not contribute to the public output, is not part +/// of the statement being proven", and calls "may be ignored by production +/// proving environments that do not expose diagnostics". Like the rest of the +/// interface it cannot fail; there is no error code. +/// +/// # Safety +/// +/// `utf8_bytes` must point to `utf8_len` readable bytes of guest memory. The +/// requirement is nominal — the no-op never dereferences the pointer. +#[cfg(target_arch = "riscv64")] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn write_log(_utf8_bytes: *const u8, _utf8_len: usize) {} + /// Host-side stub — Lambda VM's IO interface is only implemented for the /// `riscv64` guest target. Not exported with C linkage on host so the /// generic name doesn't collide with C dependencies in test builds. @@ -82,3 +111,11 @@ pub fn read_input(_buf_ptr: *mut *const u8, _buf_size: *mut usize) { pub fn write_output(_output: *const u8, _size: usize) { unimplemented!("write_output is only implemented for riscv64 targets"); } + +/// Host-side stub — Lambda VM's IO interface is only implemented for the +/// `riscv64` guest target. Not exported with C linkage on host so the +/// generic name doesn't collide with C dependencies in test builds. +#[cfg(not(target_arch = "riscv64"))] +pub fn write_log(_utf8_bytes: *const u8, _utf8_len: usize) { + unimplemented!("write_log is only implemented for riscv64 targets"); +}