mirror of
https://github.com/powdr-labs/powdr.git
synced 2026-01-10 10:28:06 -05:00
free input data (#2239)
This PR allows for free runtime data besides free compile time data, based on `initial_memory`. - Replaces `cbor` by `bincode` in prover queries (bincode is more efficient and cbor crashed with `u128`) - Allows the prover to pass a runtime initial memory, only possible with continuations (from https://github.com/powdr-labs/powdr/pull/2251, was already merged into here, see commits list) - Changes the `powdr` lib, which already always uses continuations, to always use this mechanism for prover data - Provides a new stdin-stream-like function to read inputs in sequence, like other zkVMs. - The function above is called `read_stdin` which I'm not super happy with, ideally it'd just be called `read` but the QueryCalldata function is already called `read`. I think we could just keep this as is and change later. --------- Co-authored-by: Lucas Clemente Vella <lvella@powdrlabs.com>
This commit is contained in:
10
.github/workflows/pr-tests.yml
vendored
10
.github/workflows/pr-tests.yml
vendored
@@ -146,8 +146,14 @@ jobs:
|
||||
run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2024-08-01-x86_64-unknown-linux-gnu
|
||||
- name: Install test dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y binutils-riscv64-unknown-elf lld
|
||||
- name: Run examples
|
||||
run: cargo run --profile pr-tests --example hello_world && cargo run --profile pr-tests --example sqrt_with_publics && cargo run --profile pr-tests --example fibonacci
|
||||
- name: Run examples that cargo accepts as examples
|
||||
run: cargo run --profile pr-tests --example hello_world && cargo run --profile pr-tests --example sqrt_with_publics
|
||||
- name: Run crate example serialized_inputs with the given branch
|
||||
run: cd powdr-test/examples/serialized-inputs && cargo run -r
|
||||
- name: Run crate example fibonacci with the given branch
|
||||
run: cd powdr-test/examples/fibonacci && cargo run -r
|
||||
- name: Run crate example fibonacci with the latest powdr release
|
||||
run: cd examples/fibonacci && cargo run -r
|
||||
|
||||
test_estark_polygon:
|
||||
needs: build
|
||||
|
||||
@@ -148,6 +148,8 @@ pub struct Pipeline<T: FieldElement> {
|
||||
arguments: Arguments<T>,
|
||||
/// The context for the host.
|
||||
host_context: HostContext,
|
||||
/// Initial memory given by the prover.
|
||||
initial_memory: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<T: FieldElement> Clone for Artifacts<T> {
|
||||
@@ -193,6 +195,7 @@ where
|
||||
pilo: false,
|
||||
arguments: Arguments::default(),
|
||||
host_context: ctx,
|
||||
initial_memory: vec![],
|
||||
}
|
||||
// We add the basic callback functionalities to support PrintChar and Hint.
|
||||
.add_query_callback(Arc::new(handle_simple_queries_callback()))
|
||||
@@ -322,6 +325,18 @@ impl<T: FieldElement> Pipeline<T> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds data to the initial memory given by the prover.
|
||||
/// This is a more efficient method of passing bytes from the host
|
||||
/// to the guest.
|
||||
pub fn add_to_initial_memory(mut self, data: Vec<u8>) -> Self {
|
||||
self.initial_memory.push(data);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn initial_memory(&self) -> &[Vec<u8>] {
|
||||
&self.initial_memory
|
||||
}
|
||||
|
||||
pub fn add_data<S: serde::Serialize>(self, channel: u32, data: &S) -> Self {
|
||||
let bytes = serde_cbor::to_vec(&data).unwrap();
|
||||
self.add_query_callback(Arc::new(serde_data_to_query_callback(channel, bytes)))
|
||||
|
||||
25
powdr-test/examples/fibonacci/Cargo.toml
Normal file
25
powdr-test/examples/fibonacci/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "fibonacci"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
simd = ["powdr/plonky3-simd"]
|
||||
|
||||
[dependencies]
|
||||
powdr = { path = "../../../powdr", features = ["plonky3"] }
|
||||
|
||||
serde = { version = "1.0", default-features = false, features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
"rc",
|
||||
] }
|
||||
serde_cbor = { version = "0.11.2", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
|
||||
env_logger = "0.10.2"
|
||||
log = "0.4.17"
|
||||
|
||||
[workspace]
|
||||
9
powdr-test/examples/fibonacci/guest/Cargo.toml
Normal file
9
powdr-test/examples/fibonacci/guest/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "powdr-guest"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
powdr-riscv-runtime = { path = "../../../../riscv-runtime", features = ["std"]}
|
||||
|
||||
[workspace]
|
||||
20
powdr-test/examples/fibonacci/guest/src/main.rs
Normal file
20
powdr-test/examples/fibonacci/guest/src/main.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use powdr_riscv_runtime;
|
||||
use powdr_riscv_runtime::commit;
|
||||
use powdr_riscv_runtime::io::{read, write};
|
||||
|
||||
fn fib(n: u32) -> u32 {
|
||||
if n <= 1 {
|
||||
return n;
|
||||
}
|
||||
fib(n - 1) + fib(n - 2)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Read input from stdin.
|
||||
let n: u32 = read();
|
||||
let r = fib(n);
|
||||
// Write result to stdout.
|
||||
write(1, r);
|
||||
// Commit the result as a public.
|
||||
commit::commit(r);
|
||||
}
|
||||
2
powdr-test/examples/fibonacci/rust-toolchain.toml
Normal file
2
powdr-test/examples/fibonacci/rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-09-21"
|
||||
@@ -5,10 +5,10 @@ fn main() {
|
||||
|
||||
let n = 11;
|
||||
let mut session = Session::builder()
|
||||
.guest_path("./examples/fibonacci/guest")
|
||||
.guest_path("./guest")
|
||||
.out_path("powdr-target")
|
||||
.build()
|
||||
.write(0, &n);
|
||||
.write(&n);
|
||||
|
||||
// Fast dry run to test execution.
|
||||
session.run();
|
||||
@@ -24,9 +24,6 @@ fn main() {
|
||||
let publics = session.publics();
|
||||
assert_eq!(
|
||||
publics,
|
||||
[
|
||||
555233681, 1854640251, 3298928347, 2857173302, 2660189392, 1608424695, 543896544,
|
||||
3870154745
|
||||
]
|
||||
[555233681, 1854640251, 3298928347, 2857173302, 2660189392, 1608424695, 543896544, 3870154745]
|
||||
);
|
||||
}
|
||||
22
powdr-test/examples/serialized-inputs/Cargo.toml
Normal file
22
powdr-test/examples/serialized-inputs/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "serialized-inputs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
simd = ["powdr/plonky3-simd"]
|
||||
|
||||
[dependencies]
|
||||
powdr = { path = "../../../powdr", features = ["plonky3"] }
|
||||
|
||||
serde = { version = "1.0", default-features = false, features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
"rc",
|
||||
] }
|
||||
|
||||
env_logger = "0.10.2"
|
||||
log = "0.4.17"
|
||||
|
||||
[workspace]
|
||||
15
powdr-test/examples/serialized-inputs/guest/Cargo.toml
Normal file
15
powdr-test/examples/serialized-inputs/guest/Cargo.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "powdr-guest"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
powdr-riscv-runtime = { path = "../../../../riscv-runtime", features = ["std"]}
|
||||
|
||||
serde = { version = "1.0", default-features = false, features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
"rc",
|
||||
] }
|
||||
|
||||
[workspace]
|
||||
16
powdr-test/examples/serialized-inputs/guest/src/main.rs
Normal file
16
powdr-test/examples/serialized-inputs/guest/src/main.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use powdr_riscv_runtime;
|
||||
use powdr_riscv_runtime::io::read;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct Data {
|
||||
numbers: Vec<u32>,
|
||||
sum: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let data: Data = read();
|
||||
let s: String = read();
|
||||
|
||||
assert_eq!(data.numbers.iter().sum::<u32>(), data.sum);
|
||||
assert_eq!(s, "test");
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-09-21"
|
||||
32
powdr-test/examples/serialized-inputs/src/main.rs
Normal file
32
powdr-test/examples/serialized-inputs/src/main.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use powdr::Session;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct Data {
|
||||
numbers: Vec<u32>,
|
||||
sum: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let some_data = Data {
|
||||
numbers: vec![1, 2, 3, 4, 5],
|
||||
sum: 15,
|
||||
};
|
||||
|
||||
let s: String = "test".to_string();
|
||||
|
||||
let mut session = Session::builder()
|
||||
.guest_path("./guest")
|
||||
.out_path("powdr-target")
|
||||
.chunk_size_log2(18)
|
||||
.build()
|
||||
.write(&some_data)
|
||||
.write(&s);
|
||||
|
||||
// Fast dry run to test execution.
|
||||
session.run();
|
||||
|
||||
// Uncomment to compute the proof.
|
||||
//session.prove();
|
||||
}
|
||||
@@ -25,6 +25,7 @@ serde = { version = "1.0", default-features = false, features = [
|
||||
"derive",
|
||||
"alloc",
|
||||
] }
|
||||
serde_cbor = "0.11.2"
|
||||
|
||||
[features]
|
||||
default = ["halo2", "plonky3"]
|
||||
@@ -39,7 +40,10 @@ estark-polygon = [
|
||||
stwo = ["powdr-backend/stwo", "powdr-pipeline/stwo"]
|
||||
|
||||
plonky3-simd = ["powdr-backend/plonky3-simd", "powdr-pipeline/plonky3-simd"]
|
||||
estark-starky-simd = ["powdr-backend/estark-starky-simd", "powdr-pipeline/estark-starky-simd"]
|
||||
estark-starky-simd = [
|
||||
"powdr-backend/estark-starky-simd",
|
||||
"powdr-pipeline/estark-starky-simd",
|
||||
]
|
||||
|
||||
[lints.clippy]
|
||||
uninlined_format_args = "deny"
|
||||
|
||||
@@ -125,9 +125,17 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<S: serde::Serialize>(self, channel: u32, data: &S) -> Self {
|
||||
Session {
|
||||
pipeline: self.pipeline.add_data(channel, data),
|
||||
pub fn write<S: serde::Serialize>(self, data: &S) -> Self {
|
||||
let bytes = serde_cbor::to_vec(&data).unwrap();
|
||||
Self {
|
||||
pipeline: self.pipeline.add_to_initial_memory(bytes),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_bytes(self, bytes: Vec<u8>) -> Self {
|
||||
Self {
|
||||
pipeline: self.pipeline.add_to_initial_memory(bytes),
|
||||
..self
|
||||
}
|
||||
}
|
||||
@@ -278,7 +286,8 @@ pub fn run(pipeline: &mut Pipeline<GoldilocksField>) {
|
||||
let start = Instant::now();
|
||||
|
||||
let asm = pipeline.compute_analyzed_asm().unwrap().clone();
|
||||
let initial_memory = riscv::continuations::load_initial_memory(&asm);
|
||||
let initial_memory = riscv::continuations::load_initial_memory(&asm, pipeline.initial_memory());
|
||||
|
||||
let trace_len = riscv_executor::execute_fast(
|
||||
&asm,
|
||||
initial_memory,
|
||||
|
||||
@@ -12,6 +12,7 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv
|
||||
serde_cbor = { version = "0.11.2", default-features = false, features = ["alloc"] }
|
||||
powdr-riscv-syscalls = { path = "../riscv-syscalls", version = "0.1.4" }
|
||||
getrandom = { version = "0.2", features = ["custom"], optional = true }
|
||||
spin = "0.9"
|
||||
|
||||
[features]
|
||||
std = ["serde/std", "serde_cbor/std"]
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
# Powdr linker script.
|
||||
#
|
||||
# If you are using powdr-riscv-runtime, it expects the symbols
|
||||
# "__global_pointer$" and "__powdr_stack_start" to be defined.
|
||||
# If you are using powdr-riscv-runtime, it expects the following
|
||||
# symbols to be defined:
|
||||
# __global_pointer$
|
||||
# __powdr_stack_start
|
||||
# __powdr_prover_data_init
|
||||
# __powdr_prover_data_end
|
||||
#
|
||||
# Where "__powdr_prover_data_init" and "__powdr_prover_data_end"
|
||||
# defines a region bigger than 1 page (2 KB), whose size is a
|
||||
# power of 2, and aligned to its size.
|
||||
#
|
||||
# This linker script provides usable definitions to these
|
||||
# symbols, with a 256 MB stack. If you are not building via
|
||||
# powdr-rs, you must manually specify "-C link-arg=-Tpowdr.x"
|
||||
# symbols, with a stack of almost 256 MB. If you are not building
|
||||
# via powdr-rs, you must manually specify "-C link-arg=-Tpowdr.x"
|
||||
# in rustc to use this linker script (e.g. via RUSTFLAGS).
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
# Data starts here, before is the stack.
|
||||
. = 0x10000100;
|
||||
# Stack starts backwards from here (one 2 KB page short of 256 MB)
|
||||
__powdr_stack_start = 0x10000000 - 0x800;
|
||||
|
||||
# The prover data (the second 256 MB chunk of the address space)
|
||||
__powdr_prover_data_start = 0x10000000;
|
||||
__powdr_prover_data_end = 0x20000000;
|
||||
|
||||
# Data starts here, one page after the prover data.
|
||||
. = 0x20000000 + 0x800;
|
||||
.data : {
|
||||
*(.data)
|
||||
PROVIDE( __global_pointer$ = . + 0x800 );
|
||||
@@ -20,8 +35,6 @@ SECTIONS
|
||||
|
||||
# Text addresses are fake in powdr, we use a different address space.
|
||||
.text : { *(.text) }
|
||||
|
||||
__powdr_stack_start = 0x10000000;
|
||||
}
|
||||
|
||||
# Specify the entry point function provided by powdr-riscv-runtime:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use core::arch::asm;
|
||||
use core::iter::FusedIterator;
|
||||
use core::slice;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
@@ -7,6 +9,94 @@ use powdr_riscv_syscalls::Syscall;
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// An iterator over the static prover data.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ProverDataReader {
|
||||
remaining_data: &'static [u32],
|
||||
}
|
||||
|
||||
use spin::Mutex;
|
||||
|
||||
static PROVER_DATA_READER: Mutex<Option<ProverDataReader>> = Mutex::new(None);
|
||||
|
||||
// Initialize the reader once and get a mutable reference
|
||||
fn get_prover_data_reader() -> spin::MutexGuard<'static, Option<ProverDataReader>> {
|
||||
let mut reader = PROVER_DATA_READER.lock();
|
||||
if reader.is_none() {
|
||||
*reader = Some(ProverDataReader::new());
|
||||
}
|
||||
reader
|
||||
}
|
||||
|
||||
impl ProverDataReader {
|
||||
/// Creates an iterator over the static prover data.
|
||||
///
|
||||
/// A newly created iterator will start at the beginning of the prover data.
|
||||
pub fn new() -> Self {
|
||||
extern "C" {
|
||||
// The prover data start and end symbols. Their addresses are set by the linker.
|
||||
static __powdr_prover_data_start: u32;
|
||||
static __powdr_prover_data_end: u32;
|
||||
}
|
||||
const POWDR_PAGE_SIZE: isize = 2048;
|
||||
|
||||
unsafe {
|
||||
// We skip the first page of the prover data, as it used as salt to
|
||||
// randomize its merkle tree node.
|
||||
let region_start: *const u32 = &__powdr_prover_data_start;
|
||||
let data_start = region_start.byte_offset(POWDR_PAGE_SIZE);
|
||||
let data_end: *const u32 = &__powdr_prover_data_end;
|
||||
|
||||
let prover_data_section =
|
||||
slice::from_raw_parts(data_start, data_end.offset_from(data_start) as usize);
|
||||
|
||||
// The first word of the prover data section is the total number of words the user wrote.
|
||||
let (&total_words, remaining_data) = prover_data_section.split_first().unwrap();
|
||||
|
||||
let remaining_data = &remaining_data[..total_words as usize];
|
||||
Self { remaining_data }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for ProverDataReader {
|
||||
type Item = &'static [u8];
|
||||
|
||||
/// Returns the next slice of prover data.
|
||||
///
|
||||
/// Because it is in static memory, the reference can be stored, passed around and will always be valid.
|
||||
///
|
||||
/// The start of the slice is guaranteed to be 4-bytes aligned.
|
||||
fn next(&mut self) -> Option<&'static [u8]> {
|
||||
if self.remaining_data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let (&len_bytes, remaining) = self.remaining_data.split_first().unwrap();
|
||||
let len_words = (len_bytes + 3) / 4;
|
||||
let (data, remaining) = remaining.split_at(len_words as usize);
|
||||
self.remaining_data = remaining;
|
||||
|
||||
// SAFETY: It is safe to cast an u32 slice to an u8 slice, which is the most general type.
|
||||
unsafe {
|
||||
let data_ptr = data.as_ptr() as *const u8;
|
||||
Some(slice::from_raw_parts(data_ptr, len_bytes as usize))
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
if self.remaining_data.is_empty() {
|
||||
(0, Some(0))
|
||||
} else {
|
||||
// At the minimum the size is 1 (the next slice can contain all remaining words);
|
||||
// At the maximum the size is len (every remaining word define a new slice with 0 elements).
|
||||
(1, Some(self.remaining_data.len()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for ProverDataReader {}
|
||||
|
||||
/// A single u32 from input channel 0.
|
||||
pub fn read_u32(idx: u32) -> u32 {
|
||||
let mut value: u32;
|
||||
@@ -51,8 +141,27 @@ pub fn write_slice(fd: u32, data: &[u8]) {
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
pub fn read<T: DeserializeOwned>() -> T {
|
||||
let mut reader = get_prover_data_reader();
|
||||
if let Some(ref mut reader) = *reader {
|
||||
let slice = reader.next().unwrap();
|
||||
serde_cbor::from_slice(slice).unwrap()
|
||||
} else {
|
||||
panic!("powdr ProverDataReader not available");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_bytes() -> &'static [u8] {
|
||||
let mut reader = get_prover_data_reader();
|
||||
if let Some(ref mut reader) = *reader {
|
||||
reader.next().unwrap()
|
||||
} else {
|
||||
panic!("powdr ProverDataReader not available");
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads and deserializes a serialized value of type T from the file descriptor fd.
|
||||
pub fn read<T: DeserializeOwned>(fd: u32) -> T {
|
||||
pub fn read_fd<T: DeserializeOwned>(fd: u32) -> T {
|
||||
let l = read_data_len(fd);
|
||||
let mut data = vec![0; l];
|
||||
read_slice(fd, &mut data);
|
||||
|
||||
@@ -34,9 +34,10 @@ lazy_static = "1.4.0"
|
||||
itertools = "0.13"
|
||||
log = "0.4.17"
|
||||
raki = "0.1.4"
|
||||
rand = "0.8"
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0"
|
||||
static_assertions = "1.1.0"
|
||||
thiserror = "1.0"
|
||||
|
||||
[build-dependencies]
|
||||
lalrpop = "^0.19"
|
||||
@@ -56,6 +57,7 @@ serde = { version = "1.0", default-features = false, features = [
|
||||
"derive",
|
||||
"rc",
|
||||
] }
|
||||
serde_cbor = "0.11.2"
|
||||
|
||||
[package.metadata.cargo-udeps.ignore]
|
||||
development = ["env_logger"]
|
||||
|
||||
@@ -100,6 +100,9 @@ pub trait RiscVProgram {
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = Statement<impl AsRef<str>, impl InstructionArgs>>;
|
||||
|
||||
/// Returns the addresses of the start and end of prover data.
|
||||
fn prover_data_bounds(&self) -> (u32, u32);
|
||||
|
||||
/// The name of the function that should be called to start the program.
|
||||
fn start_function(&self) -> impl AsRef<str>;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ use bootloader::{
|
||||
default_input, PAGE_SIZE_BYTES_LOG, PC_INDEX, REGISTER_MEMORY_NAMES, REGISTER_NAMES,
|
||||
};
|
||||
use memory_merkle_tree::MerkleTree;
|
||||
use rand::Rng;
|
||||
|
||||
use crate::continuations::bootloader::{
|
||||
default_register_values, shutdown_routine_upper_bound, BOOTLOADER_INPUTS_PER_PAGE, DEFAULT_PC,
|
||||
@@ -155,45 +156,127 @@ fn sanity_check(main_machine: &Machine, field: KnownField) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_initial_memory(program: &AnalysisASMFile) -> MemoryState {
|
||||
let machine = get_main_machine(program);
|
||||
let Some(expr) = machine.pil.iter().find_map(|v| match v {
|
||||
PilStatement::LetStatement(_, n, _, expr) if n == "initial_memory" => expr.as_ref(),
|
||||
fn extract_var_from_machine<'a>(
|
||||
machine: &'a Machine,
|
||||
variable_name: &str,
|
||||
) -> Option<&'a Expression> {
|
||||
machine.pil.iter().find_map(|v| match v {
|
||||
PilStatement::LetStatement(_, n, _, expr) if n == variable_name => expr.as_ref(),
|
||||
_ => None,
|
||||
}) else {
|
||||
})
|
||||
}
|
||||
|
||||
fn expr_to_u32(expr: &Expression) -> Option<u32> {
|
||||
if let Expression::Number(_, Number { value, type_: None }) = expr {
|
||||
value.try_into().ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_initial_memory(program: &AnalysisASMFile, prover_data: &[Vec<u8>]) -> MemoryState {
|
||||
const PAGE_SIZE_BYTES: u32 = bootloader::PAGE_SIZE_BYTES as u32;
|
||||
|
||||
let machine = get_main_machine(program);
|
||||
|
||||
// Extract the prover_data bounds from the machine.
|
||||
let prover_data_start = extract_var_from_machine(machine, "prover_data_start")
|
||||
.expect("prover_data_start variable not found in the machine");
|
||||
let prover_data_start =
|
||||
expr_to_u32(prover_data_start).expect("prover_data_start variable is not a u32 number");
|
||||
let prover_data_end = extract_var_from_machine(machine, "prover_data_end")
|
||||
.expect("prover_data_end variable not found in the machine");
|
||||
let prover_data_end =
|
||||
expr_to_u32(prover_data_end).expect("prover_data_end variable is not a u32 number");
|
||||
|
||||
// Sanity check the bounds of prover_data region.
|
||||
// It must be of a power of 2 size, greater than PAGE_SIZE_BYTES, aligned to its size.
|
||||
let prover_data_size = prover_data_end.checked_sub(prover_data_start).unwrap();
|
||||
assert!(prover_data_size.is_power_of_two());
|
||||
assert!(prover_data_size > PAGE_SIZE_BYTES);
|
||||
assert_eq!(prover_data_start % prover_data_size, 0);
|
||||
|
||||
// Extract the initial_memory variable from the machine.
|
||||
let mut initial_memory = if let Some(expr) = extract_var_from_machine(machine, "initial_memory")
|
||||
{
|
||||
let Expression::ArrayLiteral(_, array) = expr else {
|
||||
panic!("initial_memory is not an array literal");
|
||||
};
|
||||
|
||||
array
|
||||
.items
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let Expression::Tuple(_, tuple) = entry else {
|
||||
panic!("initial_memory entry is not a tuple");
|
||||
};
|
||||
assert_eq!(tuple.len(), 2);
|
||||
|
||||
let key = expr_to_u32(&tuple[0]).expect("initial_memory entry key is not a u32");
|
||||
let value =
|
||||
expr_to_u32(&tuple[1]).expect("initial_memory entry value is not a u32");
|
||||
|
||||
(key, value)
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
log::warn!("No initial_memory variable found in the machine. Assuming zeroed memory.");
|
||||
return MemoryState::default();
|
||||
MemoryState::default()
|
||||
};
|
||||
|
||||
let Expression::ArrayLiteral(_, array) = expr else {
|
||||
panic!("initial_memory is not an array literal");
|
||||
};
|
||||
// Fill the first page with random data to be the salt.
|
||||
// TODO: the random value should be the "hash" of the merkle tree leaf itself,
|
||||
// and the preimage of such hash should be unknown. But to implement that it would
|
||||
// require some inteligence on the MemoryState type.
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
for i in 0..(PAGE_SIZE_BYTES / 4) {
|
||||
initial_memory.insert(prover_data_start + i * 4, rng.gen::<u32>());
|
||||
}
|
||||
|
||||
array
|
||||
.items
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let Expression::Tuple(_, tuple) = entry else {
|
||||
panic!("initial_memory entry is not a tuple");
|
||||
};
|
||||
assert_eq!(tuple.len(), 2);
|
||||
let Expression::Number(
|
||||
_,
|
||||
Number {
|
||||
value: key,
|
||||
type_: None,
|
||||
},
|
||||
) = &tuple[0]
|
||||
else {
|
||||
panic!("initial_memory entry key is not a number");
|
||||
};
|
||||
let Expression::Number(_, Number { value, type_: None }) = &tuple[1] else {
|
||||
panic!("initial_memory entry value is not a number");
|
||||
};
|
||||
// Actually fill the prover data
|
||||
|
||||
(key.try_into().unwrap(), value.try_into().unwrap())
|
||||
})
|
||||
.collect()
|
||||
// Setup an iterator for the addresses to be filled. If the iterator runs out before
|
||||
// we are done, it means that the prover data is too large to fit in the reserved space.
|
||||
let mut word_addr_iter =
|
||||
((prover_data_start + PAGE_SIZE_BYTES) / 4..prover_data_end / 4).map(|i| i * 4);
|
||||
|
||||
// The first word is the total number of words that will follow in the user data.
|
||||
// We save tha address to write it later, when we know it.
|
||||
let total_word_count_addr = word_addr_iter.next().unwrap();
|
||||
|
||||
// Then we have a sequence of chunks.
|
||||
for chunk in prover_data {
|
||||
// The first word of the chunk is the length of the chunk, in bytes:
|
||||
initial_memory.insert(word_addr_iter.next().unwrap(), (chunk.len() as u32).to_le());
|
||||
|
||||
// followed by the chunk data:
|
||||
// TODO: this would be more elegant with the slice::as_chunks() method,
|
||||
// but as of this writing, it is still unstable.
|
||||
let mut remaining = &chunk[..];
|
||||
while let Some((word, rest)) = remaining.split_first_chunk::<4>() {
|
||||
initial_memory.insert(word_addr_iter.next().unwrap(), u32::from_le_bytes(*word));
|
||||
remaining = rest;
|
||||
}
|
||||
if !remaining.is_empty() {
|
||||
// last word is not full, pad with zeros
|
||||
let mut last_word = [0u8; 4];
|
||||
last_word[..remaining.len()].copy_from_slice(remaining);
|
||||
initial_memory.insert(
|
||||
word_addr_iter.next().unwrap(),
|
||||
u32::from_le_bytes(last_word),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate how many words have been written to the prover data
|
||||
// (don't count the first word, as it is weird to count itself).
|
||||
let word_past_end = word_addr_iter.next().unwrap_or(prover_data_end);
|
||||
let total_word_count = (word_past_end - prover_data_start) / 4 - 1;
|
||||
|
||||
// Write the total number of words in the prover data.
|
||||
initial_memory.insert(total_word_count_addr, total_word_count.to_le());
|
||||
|
||||
initial_memory
|
||||
}
|
||||
|
||||
pub struct DryRunResult<F: FieldElement> {
|
||||
@@ -229,7 +312,7 @@ pub fn rust_continuations_dry_run<F: FieldElement>(
|
||||
// In the first full run, we use it as the memory contents of the executor;
|
||||
// on the independent chunk runs, the executor uses zeroed initial memory,
|
||||
// and the pages are loaded via the bootloader.
|
||||
let initial_memory = load_initial_memory(&asm);
|
||||
let initial_memory = load_initial_memory(&asm, pipeline.initial_memory());
|
||||
|
||||
let mut merkle_tree = MerkleTree::<F>::new();
|
||||
merkle_tree.update(initial_memory.iter().map(|(k, v)| (*k, *v)));
|
||||
|
||||
@@ -42,6 +42,7 @@ struct ElfProgram {
|
||||
data_map: BTreeMap<u32, Data>,
|
||||
text_labels: BTreeSet<u32>,
|
||||
instructions: Vec<HighLevelInsn>,
|
||||
prover_data_bounds: (u32, u32),
|
||||
entry_point: u32,
|
||||
}
|
||||
|
||||
@@ -190,6 +191,8 @@ fn load_elf(file_name: &Path) -> ElfProgram {
|
||||
text_labels: referenced_text_addrs,
|
||||
instructions: lifted_text_sections,
|
||||
entry_point: elf.entry as u32,
|
||||
// TODO: properly load these from the ELF instead of hardcoding them.
|
||||
prover_data_bounds: (0x10000000, 0x20000000),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,6 +368,10 @@ impl RiscVProgram for ElfProgram {
|
||||
})
|
||||
}
|
||||
|
||||
fn prover_data_bounds(&self) -> (u32, u32) {
|
||||
self.prover_data_bounds
|
||||
}
|
||||
|
||||
fn start_function(&self) -> impl AsRef<str> {
|
||||
self.dbg.symbols.get_one(self.entry_point)
|
||||
}
|
||||
|
||||
@@ -20,11 +20,20 @@ use crate::large_field::runtime::Runtime;
|
||||
/// Will call each of the methods in the `RiscVProgram` just once.
|
||||
pub fn translate_program(program: impl RiscVProgram, options: CompilerOptions) -> String {
|
||||
let runtime = Runtime::new(options.libs);
|
||||
|
||||
let prover_data_bounds = program.prover_data_bounds();
|
||||
|
||||
// Do this in a separate function to avoid most of the code being generic on F.
|
||||
let (initial_mem, instructions) =
|
||||
translate_program_impl(program, options.field, &runtime, options.continuations);
|
||||
|
||||
riscv_machine(options, &runtime, initial_mem, instructions)
|
||||
riscv_machine(
|
||||
options,
|
||||
&runtime,
|
||||
initial_mem,
|
||||
prover_data_bounds,
|
||||
instructions,
|
||||
)
|
||||
}
|
||||
|
||||
fn translate_program_impl(
|
||||
@@ -182,6 +191,7 @@ fn riscv_machine(
|
||||
options: CompilerOptions,
|
||||
runtime: &Runtime,
|
||||
initial_memory: Vec<String>,
|
||||
prover_data_bounds: (u32, u32),
|
||||
program: Vec<String>,
|
||||
) -> String {
|
||||
for machine in [
|
||||
@@ -249,6 +259,11 @@ fn riscv_machine(
|
||||
&runtime.submachines_declare(),
|
||||
)
|
||||
.replace("{{INITIAL_MEMORY}}", &format!("{initial_memory}"))
|
||||
.replace(
|
||||
"{{PROVER_DATA_START}}",
|
||||
&format!("{}", prover_data_bounds.0),
|
||||
)
|
||||
.replace("{{PROVER_DATA_END}}", &format!("{}", prover_data_bounds.1))
|
||||
.replace("{{PROGRAM}}", &format!("{program}"))
|
||||
.replace("{{BOOTLOADER_INSTRUCTIONS}}", &bootloader_instructions)
|
||||
.replace("{{MUL_INSTRUCTION}}", mul_instruction)
|
||||
|
||||
@@ -27,6 +27,11 @@ machine Main with min_degree: MIN_DEGREE, max_degree: {{MAIN_MAX_DEGREE}} {
|
||||
{{INITIAL_MEMORY}}
|
||||
];
|
||||
|
||||
// Initial and final memory addresses of prover data.
|
||||
// The data is to be filled in by the prover in this range.
|
||||
let prover_data_start: fe = {{PROVER_DATA_START}};
|
||||
let prover_data_end: fe = {{PROVER_DATA_END}};
|
||||
|
||||
// ================= Extra columns we use to hold temporary values inside instructions.
|
||||
col witness tmp1_col;
|
||||
col witness tmp2_col;
|
||||
|
||||
@@ -18,7 +18,7 @@ use powdr_riscv::{
|
||||
|
||||
/// Compiles and runs a rust program with continuations, runs the full
|
||||
/// witness generation & verifies it using Pilcom.
|
||||
pub fn test_continuations(case: &str) {
|
||||
pub fn test_continuations(case: &str, prover_data: Vec<Vec<u8>>) {
|
||||
let temp_dir = Temp::new_dir().unwrap();
|
||||
|
||||
let executable = powdr_riscv::compile_rust_crate_to_riscv(
|
||||
@@ -30,10 +30,10 @@ pub fn test_continuations(case: &str) {
|
||||
// Test continuations from ELF file.
|
||||
let powdr_asm =
|
||||
powdr_riscv::elf::translate(&executable, CompilerOptions::new_gl().with_continuations());
|
||||
run_continuations_test(case, powdr_asm);
|
||||
run_continuations_test(case, powdr_asm, prover_data);
|
||||
}
|
||||
|
||||
fn run_continuations_test(case: &str, powdr_asm: String) {
|
||||
fn run_continuations_test(case: &str, powdr_asm: String, prover_data: Vec<Vec<u8>>) {
|
||||
// Manually create tmp dir, so that it is the same in all chunks.
|
||||
let tmp_dir = mktemp::Temp::new_dir().unwrap();
|
||||
|
||||
@@ -41,6 +41,11 @@ fn run_continuations_test(case: &str, powdr_asm: String) {
|
||||
.from_asm_string(powdr_asm.clone(), Some(PathBuf::from(&case)))
|
||||
.with_prover_inputs(Default::default())
|
||||
.with_output(tmp_dir.to_path_buf(), false);
|
||||
|
||||
for v in prover_data {
|
||||
pipeline = pipeline.add_to_initial_memory(v);
|
||||
}
|
||||
|
||||
let pipeline_callback = |pipeline: &mut Pipeline<GoldilocksField>| -> Result<(), ()> {
|
||||
run_pilcom_with_backend_variant(pipeline.clone(), BackendVariant::Composite).unwrap();
|
||||
|
||||
@@ -297,6 +302,25 @@ fn sum_serde() {
|
||||
verify_riscv_crate_gl_with_data(case, vec![answer.into()], vec![(42, data)], true);
|
||||
}
|
||||
|
||||
#[ignore = "Too slow"]
|
||||
#[test]
|
||||
fn sum_serde_in_mem() {
|
||||
let case = "sum_serde_in_mem";
|
||||
|
||||
let data: Vec<u32> = vec![1, 2, 8, 5];
|
||||
let answer = data.iter().sum::<u32>();
|
||||
|
||||
test_continuations(
|
||||
case,
|
||||
vec![
|
||||
serde_cbor::to_vec(&answer).unwrap(),
|
||||
serde_cbor::to_vec(&data).unwrap(),
|
||||
serde_cbor::to_vec(&answer).unwrap(),
|
||||
serde_cbor::to_vec(&data).unwrap(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Too slow"]
|
||||
fn read_slice() {
|
||||
@@ -603,13 +627,13 @@ fn output_syscall_with_options<T: FieldElement>(options: CompilerOptions) {
|
||||
#[test]
|
||||
#[ignore = "Too slow"]
|
||||
fn many_chunks() {
|
||||
test_continuations("many_chunks")
|
||||
test_continuations("many_chunks", Vec::new())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Too slow"]
|
||||
fn many_chunks_memory() {
|
||||
test_continuations("many_chunks_memory")
|
||||
test_continuations("many_chunks_memory", Vec::new())
|
||||
}
|
||||
|
||||
fn verify_riscv_crate(case: &str, inputs: &[u64], executor_witgen: bool) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use powdr_riscv_runtime::io::read;
|
||||
use powdr_riscv_runtime::io::read_fd;
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
let v: [u32; 6] = read(1);
|
||||
let v: [u32; 6] = read_fd(1);
|
||||
let a0 = v[0] as u64;
|
||||
let a1 = (v[1] as u64) << 32;
|
||||
let b0 = v[2] as u64;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use powdr_riscv_runtime::io::read;
|
||||
use powdr_riscv_runtime::io::read_fd;
|
||||
use powdr_riscv_runtime::print;
|
||||
|
||||
use revm::{
|
||||
@@ -22,7 +22,7 @@ fn main() {
|
||||
b256!("e3c84e69bac71c159b2ff0d62b9a5c231887a809a96cb4a262a4b96ed78a1db2");
|
||||
let mut db = CacheDB::new(EmptyDB::default());
|
||||
|
||||
let bytecode: Vec<u8> = read(666);
|
||||
let bytecode: Vec<u8> = read_fd(666);
|
||||
|
||||
// Fill database:
|
||||
let bytecode = Bytes::from(bytecode);
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use powdr_riscv_runtime::io::{read, read_u32};
|
||||
use powdr_riscv_runtime::io::{read_fd, read_u32};
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
let proposed_sum = read_u32(0);
|
||||
let data: Vec<u32> = read(42);
|
||||
let data: Vec<u32> = read_fd(42);
|
||||
let sum: u32 = data.iter().sum();
|
||||
assert_eq!(sum, proposed_sum);
|
||||
}
|
||||
|
||||
17
riscv/tests/riscv_data/sum_serde_in_mem/Cargo.toml
Normal file
17
riscv/tests/riscv_data/sum_serde_in_mem/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "sum_serde_in_mem"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
powdr-riscv-runtime = { path = "../../../../riscv-runtime" }
|
||||
serde = { version = "1.0", default-features = false, features = [
|
||||
"alloc",
|
||||
"derive",
|
||||
"rc",
|
||||
] }
|
||||
serde_cbor = { version = "0.11.2", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
|
||||
[workspace]
|
||||
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly-2024-08-01"
|
||||
targets = ["riscv32imac-unknown-none-elf"]
|
||||
profile = "minimal"
|
||||
21
riscv/tests/riscv_data/sum_serde_in_mem/src/main.rs
Normal file
21
riscv/tests/riscv_data/sum_serde_in_mem/src/main.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use powdr_riscv_runtime::io;
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
let proposed_sum: u32 = serde_cbor::from_slice(io::read_bytes()).unwrap();
|
||||
let data: Vec<u32> = serde_cbor::from_slice(io::read_bytes()).unwrap();
|
||||
let sum: u32 = data.iter().sum();
|
||||
assert_eq!(sum, proposed_sum);
|
||||
|
||||
let proposed_sum: u32 = io::read();
|
||||
let data: Vec<u32> = io::read();
|
||||
let sum: u32 = data.iter().sum();
|
||||
assert_eq!(sum, proposed_sum);
|
||||
|
||||
}
|
||||
@@ -4,12 +4,12 @@
|
||||
extern crate alloc;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use powdr_riscv_runtime::io::read;
|
||||
use powdr_riscv_runtime::io::read_fd;
|
||||
|
||||
#[no_mangle]
|
||||
pub fn main() {
|
||||
let data1: Vec<u32> = read(42);
|
||||
let data2: Vec<u32> = read(43);
|
||||
let data1: Vec<u32> = read_fd(42);
|
||||
let data2: Vec<u32> = read_fd(43);
|
||||
let sum1: u32 = data1.iter().sum();
|
||||
let sum2: u32 = data2.iter().sum();
|
||||
assert_eq!(sum1, sum2);
|
||||
|
||||
Reference in New Issue
Block a user