From 88dfdea0a50dde2d3074466c3815c547a8fb5c09 Mon Sep 17 00:00:00 2001 From: schaeff Date: Tue, 25 Jul 2023 23:55:34 +0200 Subject: [PATCH] create powdr book Co-authored-by: Leo --- .github/workflows/deploy-book.yml | 43 ++++++++++ README.md | 46 ---------- book/.gitignore | 1 + book/README.md | 14 +++ book/book.toml | 6 ++ book/src/README.md | 21 +++++ book/src/SUMMARY.md | 26 ++++++ book/src/asm/README.md | 3 + book/src/asm/expressions.md | 23 +++++ book/src/asm/functions.md | 49 +++++++++++ book/src/asm/instructions.md | 30 +++++++ book/src/asm/machines.md | 56 ++++++++++++ book/src/asm/registers.md | 40 +++++++++ book/src/backends/README.md | 3 + book/src/backends/estark.md | 3 + book/src/backends/halo2.md | 3 + book/src/cli/.gitignore | 1 + book/src/frontends/README.md | 3 + book/src/frontends/evm.md | 3 + book/src/frontends/riscv.md | 48 +++++++++++ book/src/frontends/valida.md | 3 + book/src/hello_world.md | 22 +++++ book/src/installation.md | 29 +++++++ book/src/pil/README.md | 3 + book/src/pil/fixed_columns.md | 34 ++++++++ book/src/pil/macros.md | 34 ++++++++ book/src/powdr_logo_pink.png | Bin 0 -> 19316 bytes book/theme/favicon.png | Bin 0 -> 19316 bytes book/theme/favicon.svg | 14 +++ compiler/tests/asm.rs | 21 +++++ compiler/tests/pil.rs | 5 ++ powdr_cli/Cargo.toml | 1 + powdr_cli/src/main.rs | 27 ++++-- .../asm/book/assert_assignment_register.asm | 23 +++++ test_data/asm/book/assert_write_register.asm | 22 +++++ test_data/asm/book/function.asm | 81 ++++++++++++++++++ test_data/asm/book/hello_world.asm | 41 +++++++++ test_data/asm/book/simple_static.asm | 22 +++++ test_data/asm/book/write_register.asm | 29 +++++++ test_data/pil/fib_macro.pil | 29 ++++--- test_data/pil/fixed_columns.pil | 23 +++++ 41 files changed, 820 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/deploy-book.yml create mode 100644 book/.gitignore create mode 100644 book/README.md create mode 100644 book/book.toml create mode 100644 book/src/README.md create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/asm/README.md create mode 100644 book/src/asm/expressions.md create mode 100644 book/src/asm/functions.md create mode 100644 book/src/asm/instructions.md create mode 100644 book/src/asm/machines.md create mode 100644 book/src/asm/registers.md create mode 100644 book/src/backends/README.md create mode 100644 book/src/backends/estark.md create mode 100644 book/src/backends/halo2.md create mode 100644 book/src/cli/.gitignore create mode 100644 book/src/frontends/README.md create mode 100644 book/src/frontends/evm.md create mode 100644 book/src/frontends/riscv.md create mode 100644 book/src/frontends/valida.md create mode 100644 book/src/hello_world.md create mode 100644 book/src/installation.md create mode 100644 book/src/pil/README.md create mode 100644 book/src/pil/fixed_columns.md create mode 100644 book/src/pil/macros.md create mode 100644 book/src/powdr_logo_pink.png create mode 100644 book/theme/favicon.png create mode 100644 book/theme/favicon.svg create mode 100644 test_data/asm/book/assert_assignment_register.asm create mode 100644 test_data/asm/book/assert_write_register.asm create mode 100644 test_data/asm/book/function.asm create mode 100644 test_data/asm/book/hello_world.asm create mode 100644 test_data/asm/book/simple_static.asm create mode 100644 test_data/asm/book/write_register.asm create mode 100644 test_data/pil/fixed_columns.pil diff --git a/.github/workflows/deploy-book.yml b/.github/workflows/deploy-book.yml new file mode 100644 index 000000000..454d28db6 --- /dev/null +++ b/.github/workflows/deploy-book.yml @@ -0,0 +1,43 @@ +# adapted from https://github.com/rust-lang/mdBook/wiki/Automated-Deployment%3A-GitHub-Actions#GitHub-Pages-Deploy + +name: Deploy book +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: write # To push a branch + pull-requests: write # To create a PR from that branch + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Install latest mdbook + run: | + tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name') + url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz" + mkdir mdbook + curl -sSL $url | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + - name: Deploy GitHub Pages + run: | + # generate cli docs and inject them into the book + cargo run --package powdr_cli -- --markdown-help > book/src/cli/README.md + # build the book + cd book + mdbook build + git worktree add gh-pages + git config user.name "Deploy from CI" + git config user.email "" + cd gh-pages + # Delete the ref to avoid keeping history. + git update-ref -d refs/heads/gh-pages + rm -rf * + mv ../book/* . + git add . + git commit -m "Deploy $GITHUB_SHA to gh-pages" + git push --force --set-upstream origin gh-pages \ No newline at end of file diff --git a/README.md b/README.md index 8f9f10b30..413d74ef5 100644 --- a/README.md +++ b/README.md @@ -43,52 +43,6 @@ The assembly language is designed to be extensible. This means that it does not native instruction. Instead, all instructions are user-defined and because of that, it is easy to adapt *powdr* assembly to any VM. -## How to run the Rust-RISCV example - -```sh -# Install the riscv target for the rust compiler -rustup target add riscv32imc-unknown-none-elf -# Run the compiler. It will generate files in /tmp/. -# -i specifies the prover witness input (see below) -cargo run --release rust riscv/tests/riscv_data/sum.rs -o /tmp -f -i 10,2,4,6 -``` - -The following example Rust file verifies that a supplied list of integers sums up to a specified value. -Note that this is the full and only input file you need for the whole process! - -```rust -#![no_std] - -extern crate alloc; -use alloc::vec::Vec; - -use runtime::get_prover_input; - -#[no_mangle] -pub fn main() { - // This is the sum claimed by the prover. - let proposed_sum = get_prover_input(0); - // The number of integers we want to sum. - let len = get_prover_input(1) as usize; - // Read the numbers from the prover and store them - // in a vector. - let data: Vec<_> = (2..(len + 2)) - .map(|idx| get_prover_input(idx as u32)) - .collect(); - // Compute the sum. - let sum: u32 = data.iter().sum(); - // Check that our sum matches the prover's. - assert_eq!(sum, proposed_sum); -} -``` - -The function `get_prover_input` reads a number from the list supplied with `-i`. - -This is just a first mechanism to provide access to the outside world. -The plan is to be able to call arbitrary user-defined `ffi` functions that will translate to prover queries, -and can then ask for e.g. the value of a storage slot at a certain address or the -root hash of a merkle tree. - ### Notes on Efficiency Currently, the code is extremely wasteful. It generates many unnecessary columns. diff --git a/book/.gitignore b/book/.gitignore new file mode 100644 index 000000000..7585238ef --- /dev/null +++ b/book/.gitignore @@ -0,0 +1 @@ +book diff --git a/book/README.md b/book/README.md new file mode 100644 index 000000000..6fb9f4927 --- /dev/null +++ b/book/README.md @@ -0,0 +1,14 @@ +# the powdr book + +This is the powdr book. + +## Contributing + +To serve the book: + +``` +cargo install mdbook +mdbook serve +``` + +Then put changes in the `src` folder. \ No newline at end of file diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 000000000..6803379f8 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["schaeff"] +language = "en" +multilingual = false +src = "src" +title = "powdr" diff --git a/book/src/README.md b/book/src/README.md new file mode 100644 index 000000000..cf4d53264 --- /dev/null +++ b/book/src/README.md @@ -0,0 +1,21 @@ +# Introduction + +**powdr** is a modular compiler stack to build zkVMs. +It is ideal for implementing existing VMs and experimenting with new designs with minimal boilerplate. + +* Domain specific languages are used to specify the VM and its underlying constraints, not low level Rust code +* Automated witness generation +* Support for multiple provers as well as aggregation schemes +* Support for hand-optimized co-processors when performance is critical +* Built in Rust 🦀 + +## Contributing + +powdr is free and open source. You can find the source code on +[GitHub](https://github.com/powdr-labs/powdr). Issues and feature requests can be posted on +the [GitHub issue tracker](https://github.com/powdr-labs/powdr/issues). + +## License + +The powdr source and documentation are released under +the [MIT License](https://opensource.org/license/mit/). \ No newline at end of file diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 000000000..fb8e02d2b --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,26 @@ +# Summary + +[Introduction](./README.md) + +# Getting Started +- [Installation](./installation.md) +- [Hello World](./hello_world.md) + +# Reference Guide +- [CLI](./cli/README.md) +- [asm](./asm/README.md) + - [Machines](./asm/machines.md) + - [Registers](./asm/registers.md) + - [Functions](./asm/functions.md) + - [Expressions](./asm/expressions.md) + - [Instructions](./asm/instructions.md) +- [PIL](./pil/README.md) + - [Fixed Columns](./pil/fixed_columns.md) + - [Macros](./pil/macros.md) +- [Frontends](./frontends/README.md) + - [RISCV](./frontends/riscv.md) + - [Valida](./frontends/valida.md) + - [EVM](./frontends/evm.md) +- [Backends](./backends/README.md) + - [Halo2](./backends/halo2.md) + - [eSTARK](./backends/estark.md) diff --git a/book/src/asm/README.md b/book/src/asm/README.md new file mode 100644 index 000000000..c319567af --- /dev/null +++ b/book/src/asm/README.md @@ -0,0 +1,3 @@ +# asm + +powdr asm is the higher level of abstraction in powdr. It allows defining Instruction Set Architectures (ISA) using dynamic and static machines. \ No newline at end of file diff --git a/book/src/asm/expressions.md b/book/src/asm/expressions.md new file mode 100644 index 000000000..932aa7222 --- /dev/null +++ b/book/src/asm/expressions.md @@ -0,0 +1,23 @@ +# Expressions + +## Field element literals + +Field element literals are signed elements of the prime field. + +``` +{{#include ../../../test_data/asm/book/function.asm:literals}} +``` + +## Registers and columns + +Registers can be used as expressions, with the exception of assignment registers. +``` +{{#include ../../../test_data/asm/book/function.asm:read_register}} +``` + +## Instructions + +Instructions which return outputs can be used as expressions. +``` +{{#include ../../../test_data/asm/book/function.asm:instruction}} +``` \ No newline at end of file diff --git a/book/src/asm/functions.md b/book/src/asm/functions.md new file mode 100644 index 000000000..59814f1dd --- /dev/null +++ b/book/src/asm/functions.md @@ -0,0 +1,49 @@ +# Functions + +Machine functions are the entry points to a machine. They can be called from another machine or from the outside. + +For static machines, functions simply indicate which columns should be exposed as inputs and outputs. They do not contain statements, since the business logic of all functions in a static machine is defined directly in constraints. We refer to the previous example [here](./machines.md#static-machines). + +In the rest of this section, we describe functions in dynamic machines with the example of this simple machine: + +``` +{{#include ../../../test_data/asm/book/function.asm:all}} +``` + +## Function inputs and outputs + +> For dynamic machines, function inputs and outputs are not supported yet + +## Statements + +### Labels + +Labels allow referring to a location in a function by name. + +``` +{{#include ../../../test_data/asm/book/function.asm:label}} +``` + +### Assignments + +Assignments allow setting the value of a write register to the value of an [expression](#expressions) using an assignment register. + +``` +{{#include ../../../test_data/asm/book/function.asm:instruction}} +``` + +One important requirement is for the assignment register of the assignment to be compatible with that of the expression. This is especially relevant for instructions: the assignment register of the instruction output must match that of the assignment. In this example, we use `Y` in the assignment as the output of `square` is `Y`: + +``` +{{#include ../../../test_data/asm/book/function.asm:square}} +``` + +> Specifying the assignment register of the assignment is currently required even in cases where it could be inferred. That restriction may be lifted in the future. + +### Instructions + +Instructions which do not return outputs can be used as statements. + +``` +{{#include ../../../test_data/asm/book/function.asm:instruction_statement}} +``` \ No newline at end of file diff --git a/book/src/asm/instructions.md b/book/src/asm/instructions.md new file mode 100644 index 000000000..029e2ce28 --- /dev/null +++ b/book/src/asm/instructions.md @@ -0,0 +1,30 @@ +# Instructions + +Instructions are declared as part of a powdr asm machine. Their inputs and outputs are [assignment registers](./registers.md) as well as labels. Once defined, they can be called by any function of this machine. + +# Local instructions + +A local instruction is the simplest type of instruction. It is called local because its behavior is defined using constraints over registers and columns of the machine it is defined in. + +``` +instr add X, Y -> Z { + X + Y = Z +} +``` + +Instructions feature: +- a name +- some inputs +- some outputs +- a set of PIL constraints to activate when the instruction is called + +# External instructions + +An external instruction delegates calls to a function inside a submachine of this machine. When it is called, a call is made to the submachine function. An example of an external instruction is the following: + +``` +instr assert_zero X = my_submachine.assert_zero // where `assert_zero` is a function defined in `my_submachine` +``` + +Note that external instructions cannot link to functions of the same machine: they delegate computation to a submachine. + diff --git a/book/src/asm/machines.md b/book/src/asm/machines.md new file mode 100644 index 000000000..3423c7077 --- /dev/null +++ b/book/src/asm/machines.md @@ -0,0 +1,56 @@ +# Machines + +Machines are the first main concept in powdr asm. They can currently be of two types: dynamic or static. + +## Dynamic machines + +Dynamic machines are defined by: +- a degree, indicating the number of execution steps +- a set of registers, including a program counter +- an instruction set +- constraints +- a set of functions +- a set of submachines + +> Dynamic machines are currently limited to having a single function called `main` + +An example of a simple dynamic machine is the following: + +``` +{{#include ../../../test_data/asm/book/hello_world.asm}} +``` + +## Static machines + +Static machines are a lower-level type of machine. They do not have registers, and instead rely on simple committed and fixed columns. They are used to implement hand-optimized computation. + +They are defined by: +- a degree, indicating the number of execution steps +- a latch, used to identify rows at which the machine can be accessed from the outside (where the inputs and outputs are passed) +- a set of functions + +> Currently, the latch must be a fixed column named `latch` + +An example of a simple static machine is the following: + +``` +{{#include ../../../test_data/asm/book/simple_static.asm}} +``` + +For more details on the constraints, check out the [pil](../pil) section of this book. Note that the parameters of the function are columns declared within the constraints block. + +## Submachines + +Machines can have submachines which they access by defining [external instructions](./instructions.md). They are declared as follows: + +``` +machine MySubmachine { + ... +} + +machine MyMachine { + MySubmachine my_submachine; +} +``` + +> Currently only dynamic machines can have submachines, and these must be static \ No newline at end of file diff --git a/book/src/asm/registers.md b/book/src/asm/registers.md new file mode 100644 index 000000000..a2d381d0c --- /dev/null +++ b/book/src/asm/registers.md @@ -0,0 +1,40 @@ +# Registers + +Registers are central to a machine. powdr supports a few types of registers: + +## Program counter + +Each machine can have at most one program counter. In the absence of a program counter, the machine is considered static, and no other register can be declared. The program counter is defined as follows: + +``` +reg pc[@pc] +``` + +At each step execution step, the program counter points to the [function](./functions.md) line to execute. +The program counter behaves like a [write register](#write-registers), with the exception that its value is incremented by default after each step. + +## Write registers + +Write registers are the default type for registers. They are declared as follows: + +``` +{{#include ../../../test_data/asm/book/write_register.asm:declaration}} +``` + +They hold a field element, are initialized as 0 at the beginning of a function and keep their value by default. They can be read from and written to. + +``` +{{#include ../../../test_data/asm/book/write_register.asm:component}} +``` + +## Assignment registers + +Assignment registers are transient to an execution step: their value is not persisted across steps. They are required in order to pass inputs and receive outputs from instructions, as well as in assignments. +For example, if we want to assert that write register `A` is `0`, we can use the following instruction: +``` +{{#include ../../../test_data/asm/book/assert_write_register.asm:component}} +``` +However, if we want the instruction to accept any write register as input, we use an assignment register. +``` +{{#include ../../../test_data/asm/book/assert_assignment_register.asm:component}} +``` \ No newline at end of file diff --git a/book/src/backends/README.md b/book/src/backends/README.md new file mode 100644 index 000000000..0d89303de --- /dev/null +++ b/book/src/backends/README.md @@ -0,0 +1,3 @@ +# Backends + +powdr aims to have full flexibility when it comes to generating proofs and comes with a few built-in backends to get started with zkVMs. \ No newline at end of file diff --git a/book/src/backends/estark.md b/book/src/backends/estark.md new file mode 100644 index 000000000..3b19c8d4b --- /dev/null +++ b/book/src/backends/estark.md @@ -0,0 +1,3 @@ +# eSTARK + +Integrated support for [eSTARK](https://eprint.iacr.org/2023/474) with the Goldilocks field is under development. \ No newline at end of file diff --git a/book/src/backends/halo2.md b/book/src/backends/halo2.md new file mode 100644 index 000000000..7de2b9fe4 --- /dev/null +++ b/book/src/backends/halo2.md @@ -0,0 +1,3 @@ +# Halo2 + +powdr supports the [PSE fork of halo2](https://github.com/privacy-scaling-explorations/halo2) with the bn254 field. \ No newline at end of file diff --git a/book/src/cli/.gitignore b/book/src/cli/.gitignore new file mode 100644 index 000000000..42061c01a --- /dev/null +++ b/book/src/cli/.gitignore @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/book/src/frontends/README.md b/book/src/frontends/README.md new file mode 100644 index 000000000..751e0c259 --- /dev/null +++ b/book/src/frontends/README.md @@ -0,0 +1,3 @@ +# Frontends + +While any frontend VM can be implemented in powdr asm, powdr comes with several frontends for popular instruction set architectures. diff --git a/book/src/frontends/evm.md b/book/src/frontends/evm.md new file mode 100644 index 000000000..ebe0a77a2 --- /dev/null +++ b/book/src/frontends/evm.md @@ -0,0 +1,3 @@ +# EVM + +An [EVM](https://ethereum.org/en/developers/docs/evm/) frontend for powdr is under development. If you are interested, feel free to [reach out](https://matrix.to/#/#powdr:matrix.org)! \ No newline at end of file diff --git a/book/src/frontends/riscv.md b/book/src/frontends/riscv.md new file mode 100644 index 000000000..62691d9a6 --- /dev/null +++ b/book/src/frontends/riscv.md @@ -0,0 +1,48 @@ +# RISCV + +A [RISCV](https://riscv.org/technical/specifications/) frontend for powdr is already available. + +## How to run the Rust-RISCV example + +```sh +# Install the riscv target for the rust compiler +rustup target add riscv32imc-unknown-none-elf +# Run the compiler. It will generate files in /tmp/. +# -i specifies the prover witness input (see below) +powdr rust riscv/tests/riscv_data/sum.rs -o /tmp -f -i 10,2,4,6 +``` + +The following example Rust file verifies that a supplied list of integers sums up to a specified value. +Note that this is the full and only input file you need for the whole process! + +```rust +#![no_std] + +extern crate alloc; +use alloc::vec::Vec; + +use runtime::get_prover_input; + +#[no_mangle] +pub fn main() { + // This is the sum claimed by the prover. + let proposed_sum = get_prover_input(0); + // The number of integers we want to sum. + let len = get_prover_input(1) as usize; + // Read the numbers from the prover and store them + // in a vector. + let data: Vec<_> = (2..(len + 2)) + .map(|idx| get_prover_input(idx as u32)) + .collect(); + // Compute the sum. + let sum: u32 = data.iter().sum(); + // Check that our sum matches the prover's. + assert_eq!(sum, proposed_sum); +} +``` + +The function `get_prover_input` reads a number from the list supplied with `-i`. + +This is just a first mechanism to provide access to the outside world. +The plan is to be able to call arbitrary user-defined `ffi` functions that will translate to prover queries, +and can then ask for e.g. the value of a storage slot at a certain address or the root hash of a Merkle tree. \ No newline at end of file diff --git a/book/src/frontends/valida.md b/book/src/frontends/valida.md new file mode 100644 index 000000000..8242391bb --- /dev/null +++ b/book/src/frontends/valida.md @@ -0,0 +1,3 @@ +# Valida + +A [Valida](https://github.com/valida-xyz/valida-compiler/issues/2) front end for powdr is under development. If you are interested, feel free to [reach out](https://matrix.to/#/#powdr:matrix.org)! \ No newline at end of file diff --git a/book/src/hello_world.md b/book/src/hello_world.md new file mode 100644 index 000000000..3c4a4562b --- /dev/null +++ b/book/src/hello_world.md @@ -0,0 +1,22 @@ +# Hello World + +Let's write [a minimal VM](https://github.com/powdr-labs/powdr/blob/main/test_data/asm/book/hello_world.asm) and generate a SNARK! + +``` +{{#include ../../test_data/asm/book/hello_world.asm}} +``` + +Then let's generate a proof of execution for the valid prover input `0` (since for `0 + 1 - 1 == 0`) + +```console +powdr pil hello_world.asm --field bn254 --force --inputs 0 --prove-with halo2 +``` + +We observe that a proof was created at `proof.bin`. +Now let's try for the invalid input `1` + +```console +powdr pil hello_world.asm --field bn254 --force --inputs 1 --prove-with halo2 +``` + +We observe that witness generation fails, and no proof is created. \ No newline at end of file diff --git a/book/src/installation.md b/book/src/installation.md new file mode 100644 index 000000000..cc52aca9b --- /dev/null +++ b/book/src/installation.md @@ -0,0 +1,29 @@ +# Installation + +The only way to install powdr currently is to build it from source. + +## Prerequisites + +You will need the [Rust](https://rust-lang.org) compiler and Cargo, the Rust package manager. +The easiest way to install both is with [`rustup.rs`](https://rustup.rs/). + +On Windows, you will also need a recent version of [Visual Studio](https://visualstudio.microsoft.com/downloads/), +installed with the "Desktop Development With C++" Workloads option. + +## Building + +Using a single Cargo command: + +```sh +cargo install --git https://github.com/powdr-labs/powdr powdr_cli +``` + +Or, by manually building from a local copy of the [powdr repository](https://github.com/powdr-labs/powdr): + +```sh +# clone the repository +git clone https://github.com/powdr-labs/powdr.git +cd powdr +# install powdr_cli +cargo install --path ./powdr_cli +``` diff --git a/book/src/pil/README.md b/book/src/pil/README.md new file mode 100644 index 000000000..61cf50b13 --- /dev/null +++ b/book/src/pil/README.md @@ -0,0 +1,3 @@ +# PIL + +powdr PIL is the lower level of abstraction in powdr. It is strongly inspired by and compatible with [Polygon zkEVM PIL](https://github.com/0xPolygonHermez/pilcom/). We refer to the [Polygon zkEVM PIL documentation](https://zkevm.polygon.technology/PIL/introduction) and document deviations from the original design here. \ No newline at end of file diff --git a/book/src/pil/fixed_columns.md b/book/src/pil/fixed_columns.md new file mode 100644 index 000000000..1ae6b3794 --- /dev/null +++ b/book/src/pil/fixed_columns.md @@ -0,0 +1,34 @@ +# Fixed columns + +powdr PIL requires the definition of fixed columns at the time of declaration. + +For example: + +``` +{{#include ../../../test_data/pil/fixed_columns.pil:declare_and_define}} +``` + +A number of mechanisms are supported to declare fixed columns. Let `N` be the total length of the column we're defining. + +## Values with repetitions + +powdr PIL supports a basic language to define the value of constant columns using: +- arrays, for example `[1, 2, 3]` +- repetition, for example `[1, 2]*` +- concatenation, for example `[1, 2] + [3, 4]` + +These mechanisms can be combined, as long as a single repetition is used per column definition. + +``` +{{#include ../../../test_data/pil/fixed_columns.pil:repetitions}} +``` + +## Mappings + +A column can be seen as a mapping from integers to field elements. In this context, different functions are supported: + +``` +{{#include ../../../test_data/pil/fixed_columns.pil:mapping}} +``` + +> Note that conversion from integer to field element is currently implicit, as seen in the first example above. \ No newline at end of file diff --git a/book/src/pil/macros.md b/book/src/pil/macros.md new file mode 100644 index 000000000..2ea986395 --- /dev/null +++ b/book/src/pil/macros.md @@ -0,0 +1,34 @@ +# Macros + +powdr PIL exposes a macro system which can generate arbitrary powdr PIL code. + +## Definition + +Let's define some macros which generate powdr PIL expressions: + +``` +{{#include ../../../test_data/pil/fib_macro.pil:expression_macro_definitions}} +``` + +In particular, we can generate constraints inside macros: + +``` +{{#include ../../../test_data/pil/fib_macro.pil:constraint_macro_definitions}} +``` + +## Usage + +> Macros currently have global scope + +Usage of the defined macros happens as expected in powdr PIL code: + +``` +{{#include ../../../test_data/pil/fib_macro.pil:expression_macro_usage}} +``` + +Generating constraints: + +``` +{{#include ../../../test_data/pil/fib_macro.pil:constraint_macro_usage}} +``` + diff --git a/book/src/powdr_logo_pink.png b/book/src/powdr_logo_pink.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd39d3f80d3821b5ebcdeb19b957eb92553c317 GIT binary patch literal 19316 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

KCh_Adv{j7NX9LJ4h<;S7Tm38Jjo4en|ctlwc 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 KMNUMnLSTY`!G@y% literal 0 HcmV?d00001 diff --git a/book/theme/favicon.png b/book/theme/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd39d3f80d3821b5ebcdeb19b957eb92553c317 GIT binary patch literal 19316 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

KCh_Adv{j7NX9LJ4h<;S7Tm38Jjo4en|ctlwc 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 KMNUMnLSTY`!G@y% literal 0 HcmV?d00001 diff --git a/book/theme/favicon.svg b/book/theme/favicon.svg new file mode 100644 index 000000000..aa7906a2b --- /dev/null +++ b/book/theme/favicon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/compiler/tests/asm.rs b/compiler/tests/asm.rs index 4068a8fc3..595fe5419 100644 --- a/compiler/tests/asm.rs +++ b/compiler/tests/asm.rs @@ -85,3 +85,24 @@ fn full_pil_constant() { verify_asm::(f, Default::default()); gen_proof(f, Default::default()); } + +#[test] +fn book() { + for f in fs::read_dir("../test_data/asm/book/").unwrap() { + let f = f.unwrap().path(); + let f = f.strip_prefix("../test_data/asm/").unwrap(); + // passing 0 to all tests currently works as they either take no prover input or 0 works + let i = [0]; + + verify_asm::(f.to_str().unwrap(), slice_to_vec(&i)); + gen_proof(f.to_str().unwrap(), slice_to_vec(&i)); + } +} + +#[test] +#[should_panic = "Witness generation failed."] +fn hello_world_asm_fail() { + let f = "book/hello_world.asm"; + let i = [1]; + verify_asm::(f, slice_to_vec(&i)); +} diff --git a/compiler/tests/pil.rs b/compiler/tests/pil.rs index 7748f22ae..ad4a97f8a 100644 --- a/compiler/tests/pil.rs +++ b/compiler/tests/pil.rs @@ -132,3 +132,8 @@ fn test_single_line_blocks() { fn test_two_block_machine_functions() { verify_pil("two_block_machine_functions.pil", None); } + +#[test] +fn test_fixed_columns() { + verify_pil("fixed_columns.pil", None); +} diff --git a/powdr_cli/Cargo.toml b/powdr_cli/Cargo.toml index 834847661..6b1075f6d 100644 --- a/powdr_cli/Cargo.toml +++ b/powdr_cli/Cargo.toml @@ -19,6 +19,7 @@ halo2 = { path = "../halo2", optional = true } backend = { path = "../backend" } pilopt = { path = "../pilopt" } strum = { version = "0.24.1", features = ["derive"] } +clap-markdown = "0.1.3" [dev-dependencies] tempfile = "3.6" diff --git a/powdr_cli/src/main.rs b/powdr_cli/src/main.rs index 37e767fab..21f39c2c1 100644 --- a/powdr_cli/src/main.rs +++ b/powdr_cli/src/main.rs @@ -2,13 +2,13 @@ mod util; -use clap::{Parser, Subcommand}; +use clap::{CommandFactory, Parser, Subcommand}; use compiler::{compile_pil_or_asm, Backend}; use env_logger::{Builder, Target}; use log::LevelFilter; use number::{Bn254Field, FieldElement, GoldilocksField}; use riscv::{compile_riscv_asm, compile_rust}; -use std::io::BufWriter; +use std::io::{self, BufWriter}; use std::{borrow::Cow, collections::HashSet, fs, io::Write, path::Path}; use strum::{Display, EnumString, EnumVariantNames}; @@ -34,10 +34,13 @@ pub enum CsvRenderMode { } #[derive(Parser)] -#[command(author, version, about, long_about = None)] +#[command(name = "powdr", author, version, about, long_about = None)] struct Cli { + #[arg(long, hide = true)] + markdown_help: bool, + #[command(subcommand)] - command: Commands, + command: Option, } #[derive(Subcommand)] @@ -233,7 +236,7 @@ fn split_inputs(inputs: &str) -> Vec { .collect() } -fn main() { +fn main() -> Result<(), io::Error> { let mut builder = Builder::new(); builder .filter_level(LevelFilter::Info) @@ -242,8 +245,17 @@ fn main() { .format(|buf, record| writeln!(buf, "{}", record.args())) .init(); - let command = Cli::parse().command; - run_command(command); + let args = Cli::parse(); + + if args.markdown_help { + clap_markdown::print_help_markdown::(); + Ok(()) + } else if let Some(command) = args.command { + run_command(command); + Ok(()) + } else { + Cli::command().print_help() + } } fn run_command(command: Commands) { @@ -524,7 +536,6 @@ fn optimize_and_output(file: &str) { mod test { use backend::Backend; - use tempfile; use crate::{run_command, Commands, CsvRenderMode, FieldArgument}; #[test] diff --git a/test_data/asm/book/assert_assignment_register.asm b/test_data/asm/book/assert_assignment_register.asm new file mode 100644 index 000000000..6887ef7bc --- /dev/null +++ b/test_data/asm/book/assert_assignment_register.asm @@ -0,0 +1,23 @@ +machine Machine { + + degree 8; + + // ANCHOR: component +reg pc[@pc]; +reg X[<=]; +reg A; + +instr assert_zero X { + X = 0 +} + +function main { + assert_zero A; + loop; +} + // ANCHOR_END: component + + instr loop { + pc' = pc + } +} \ No newline at end of file diff --git a/test_data/asm/book/assert_write_register.asm b/test_data/asm/book/assert_write_register.asm new file mode 100644 index 000000000..5a32d2295 --- /dev/null +++ b/test_data/asm/book/assert_write_register.asm @@ -0,0 +1,22 @@ +machine Machine { + + degree 8; + + // ANCHOR: component +reg pc[@pc]; +reg A; + +instr assert_A_is_zero { + A = 0 +} + +function main { + assert_A_is_zero; + loop; +} + // ANCHOR_END: component + + instr loop { + pc' = pc + } +} \ No newline at end of file diff --git a/test_data/asm/book/function.asm b/test_data/asm/book/function.asm new file mode 100644 index 000000000..64ed7c58e --- /dev/null +++ b/test_data/asm/book/function.asm @@ -0,0 +1,81 @@ +/* ANCHOR: all */ + +machine Machine { + + degree 256; + + reg pc[@pc]; + reg X[<=]; + reg Y[<=]; + reg CNT; + reg A; + + // an instruction to assert that a number is zero + instr assert_zero X { + X = 0 + } + + // an instruction to jump to a label + instr jmp l: label { + pc' = l + } + + // an instruction to jump to a label iff `X` is `0`, otherwise continue + instr jmpz X, l: label { + pc' = XIsZero * l + (1 - XIsZero) * (pc + 1) + } + + // an instruction to return the square of an input + // ANCHOR: square + instr square X -> Y { + Y = X * X + } + // ANCHOR_END: square + + function main { + // initialise `A` to 2 + A <=X= 2; + // initialise `CNT` to `3` + // ANCHOR: literals + CNT <=X= 3; + // ANCHOR_END: literals + // ANCHOR: label + start:: + // ANCHOR_END: label + // if `CNT` is `0`, jump to `end` + jmpz CNT, end; + // decrement `CNT` + // ANCHOR: read_register + CNT <=X= CNT - 1; + // ANCHOR_END: read_register + // square `A` + // ANCHOR: instruction + A <=Y= square(A); + // ANCHOR_END: instruction + // jump back to `start` + jmp start; + end:: + // check that `A == ((2**2)**2)**2` + // ANCHOR: instruction_statement + assert_zero A - ((2**2)**2)**2; + // ANCHOR_END: instruction_statement + // loop forever + loop; + } + + // an instruction to loop forever + instr loop { + pc' = pc + } + + // some superpowers on `X` to allow us to check if it's 0 + constraints { + col witness XInv; + col witness XIsZero; + XIsZero = 1 - X * XInv; + XIsZero * X = 0; + XIsZero * (1 - XIsZero) = 0; + } +} + +/* ANCHOR_END: all */ \ No newline at end of file diff --git a/test_data/asm/book/hello_world.asm b/test_data/asm/book/hello_world.asm new file mode 100644 index 000000000..9868140b5 --- /dev/null +++ b/test_data/asm/book/hello_world.asm @@ -0,0 +1,41 @@ +machine HelloWorld { + + degree 8; + + // this simple machine does not have submachines + + reg pc[@pc]; + reg X[<=]; + reg Y[<=]; + reg A; + + instr incr X -> Y { + Y = X + 1 + } + + instr decr X -> Y { + Y = X - 1 + } + + // an instruction to loop forever, as we must fill the whole execution trace + instr loop { + pc' = pc + } + + instr assert_zero X { + X = 0 + } + + constraints { + // in this machine, we do not add more constraints + } + + // the main function assigns the first prover input to A, increments it, decrements it, and loops forever + function main { + A <=X= ${ ("input", 0) }; + A <=Y= incr(A); + A <=Y= decr(A); + assert_zero A; + loop; + } +} \ No newline at end of file diff --git a/test_data/asm/book/simple_static.asm b/test_data/asm/book/simple_static.asm new file mode 100644 index 000000000..75feadb11 --- /dev/null +++ b/test_data/asm/book/simple_static.asm @@ -0,0 +1,22 @@ +machine SimpleStatic { + + degree 8; + + function power_4 x -> y { + } + + constraints { + col fixed latch = [0, 0, 0, 1]*; + col witness x; + col witness y; + + // initialise y to x at the beginning of each block + latch * (y' - x') = 0; + // x is unconstrained at the beginning of the block + + // x is constant within a block + (1 - latch) * (x' - x) = 0; + // y is multiplied by x at each row + (1 - latch) * (y' - x * y) = 0; + } +} \ No newline at end of file diff --git a/test_data/asm/book/write_register.asm b/test_data/asm/book/write_register.asm new file mode 100644 index 000000000..2815b4ec7 --- /dev/null +++ b/test_data/asm/book/write_register.asm @@ -0,0 +1,29 @@ +machine Machine { + + degree 8; + + reg pc[@pc]; + reg X[<=]; +// ANCHOR: declaration +reg A; +// ANCHOR_END: declaration + reg B; + + function main { +// ANCHOR: component +// write to A +A <=X= 1; +// A is 1 + +// read from A +B <=X= A; +// A is still 1 +// ANCHOR_END: component + + loop; + } + + instr loop { + pc' = pc + } +} \ No newline at end of file diff --git a/test_data/pil/fib_macro.pil b/test_data/pil/fib_macro.pil index cf19bbb6b..f17f40e4f 100644 --- a/test_data/pil/fib_macro.pil +++ b/test_data/pil/fib_macro.pil @@ -4,24 +4,31 @@ namespace Fibonacci(%N); constant %last_row = %N - 1; macro bool(X) { X * (1 - X) = 0; }; - macro is_nonzero(X) { match X { 0 => 0, _ => 1, } }; - macro is_zero(X) { 1 - is_nonzero(X) }; - macro is_equal(A, B) { is_zero(A - B) }; - macro is_one(X) { is_equal(X, 1) }; - macro ite(C, A, B) { is_nonzero(C) * A + is_zero(C) * B}; +// ANCHOR: expression_macro_definitions +macro is_nonzero(X) { match X { 0 => 0, _ => 1, } }; +macro is_zero(X) { 1 - is_nonzero(X) }; +macro is_equal(A, B) { is_zero(A - B) }; +macro is_one(X) { is_equal(X, 1) }; +macro ite(C, A, B) { is_nonzero(C) * A + is_zero(C) * B}; +macro one_hot(i, index) { ite(is_equal(i, index), 1, 0) }; +// ANCHOR_END: expression_macro_definitions - macro one_hot(i, index) { ite(is_equal(i, index), 1, 0) }; - - pol constant ISLAST(i) { one_hot(i, %last_row) }; +// ANCHOR: expression_macro_usage +pol constant ISLAST(i) { one_hot(i, %last_row) }; +// ANCHOR_END: expression_macro_usage pol commit x, y; - macro constrain_equal_expr(A, B) { A - B }; - macro force_equal_on_last_row(poly, value) { ISLAST * constrain_equal_expr(poly, value) = 0; }; +// ANCHOR: constraint_macro_definitions +macro constrain_equal_expr(A, B) { A - B }; +macro force_equal_on_last_row(poly, value) { ISLAST * constrain_equal_expr(poly, value) = 0; }; +// ANCHOR_END: constraint_macro_definitions // TODO would be easier if we could use "'" as an operator, // then we could write a "force_equal_on_first_row" macro, // and the macro would add a "'" to the parameter. - force_equal_on_last_row(x', 1); +// ANCHOR: constraint_macro_usage +force_equal_on_last_row(x', 1); +// ANCHOR_END: constraint_macro_usage force_equal_on_last_row(y', 1); macro on_regular_row(cond) { (1 - ISLAST) * cond = 0; }; diff --git a/test_data/pil/fixed_columns.pil b/test_data/pil/fixed_columns.pil new file mode 100644 index 000000000..1fac84ee5 --- /dev/null +++ b/test_data/pil/fixed_columns.pil @@ -0,0 +1,23 @@ +namespace Main(8); + +// ANCHOR: declare_and_define +col fixed ONES = [1]*; // this is valid +// col fixed ONES; // this is invalid +// ANCHOR_END: declare_and_define + +// ANCHOR: repetitions +// valid, as for a given total length, only one column fits this definition for a given `N` +col fixed A = [1, 2] + [3, 4]* + [5]; + +// invalid, as many columns fit this definition +// col fixed A = [1, 2]* + [3, 4]* +// ANCHOR_END: repetitions + +// ANCHOR: mapping +col fixed B(i) { i + 1 }; + +col fixed C(i) {match i { + 0 => 1, + _ => 0 +}}; +// ANCHOR_END: mapping \ No newline at end of file