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:
Leo
2024-12-30 20:56:46 +01:00
committed by GitHub
parent 816e8c742c
commit c8d2703d86
31 changed files with 550 additions and 72 deletions

View File

@@ -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

View File

@@ -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)))

View 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]

View 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]

View 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);
}

View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly-2024-09-21"

View File

@@ -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]
);
}

View 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]

View 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]

View 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");
}

View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly-2024-09-21"

View 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();
}

View File

@@ -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"

View File

@@ -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,

View File

@@ -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"]

View File

@@ -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:

View File

@@ -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);

View File

@@ -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"]

View File

@@ -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>;
}

View File

@@ -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)));

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);
}

View 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]

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "nightly-2024-08-01"
targets = ["riscv32imac-unknown-none-elf"]
profile = "minimal"

View 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);
}

View File

@@ -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);