diff --git a/.cargo/config b/.cargo/config index 761408102b..2cb7d14d05 100644 --- a/.cargo/config +++ b/.cargo/config @@ -6,5 +6,24 @@ xclippy = [ "clippy", "--workspace", "--all-targets", "--", "-Wclippy::all", + "-Wclippy::match_same_arms", + "-Wclippy::cast_lossless", + "-Wclippy::dbg_macro", "-Wclippy::disallowed_methods", + "-Wclippy::derive_partial_eq_without_eq", + "-Wclippy::enum_glob_use", + "-Wclippy::filter_map_next", + "-Wclippy::flat_map_option", + "-Wclippy::inefficient_to_string", + "-Wclippy::large_types_passed_by_value", + "-Wclippy::manual_assert", + "-Wclippy::manual_ok_or", + "-Wclippy::map_flatten", + "-Wclippy::map_unwrap_or", + "-Wclippy::needless_borrow", + "-Wclippy::checked_conversions", + "-Wclippy::trait_duplication_in_bounds", + "-Wrust_2018_idioms", + "-Wtrivial_numeric_casts", + "-Wunused_lifetimes", ] diff --git a/.circleci/config.yml b/.circleci/config.yml index 18f89ae5d2..b00aceae7a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,18 +1,13 @@ version: 2.1 setup-env: &setup-env - MSRV: 1.60.0 + MSRV: 1.70.0 RUSTFLAGS: "-Dwarnings" CARGO_INCREMENTAL: 0 RUST_BACKTRACE: 1 RUST_LOG: info executors: - default: - machine: - image: ubuntu-2004:202101-01 - working_directory: ~/lurk - resource_class: xlarge arm64: machine: image: ubuntu-2004:202101-01 @@ -99,191 +94,47 @@ commands: cargo --version rustc --version - save_rustup_cache: - steps: - - save_cache: - name: "Save rustup cache" - key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }} - paths: - - "~/.cargo" - - "~/.rustup" - - restore_rustup_cache: - steps: - - restore_cache: - keys: - - cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }} - - install_gpu_deps: - steps: - - run: sudo apt update && sudo apt install -y ocl-icd-opencl-dev curl build-essential - jobs: - cargo_fetch: - executor: default - environment: *setup-env + test: + parameters: + os: + type: executor + executor: << parameters.os >> steps: - checkout - run: name: Update submodules command: | git submodule update --init --recursive - - restore_rustup_cache - - set_env_path - - set_versions_n_runners - - run: cargo fetch - - run: rm -rf .git - - persist_to_workspace: - root: ~/ - paths: - - lurk - - save_rustup_cache - - linux_release: - executor: default - environment: *setup-env - steps: - - checkout - attach_workspace: at: "~/" - - restore_rustup_cache - - set_env_path - - setup-sccache - - install_gpu_deps - - restore-sccache-cache - - run: - name: Linux Tests - command: cargo nextest run --profile ci --workspace --cargo-profile dev-ci --run-ignored ignored-only -E 'all() - test(groth16::tests::outer_prove_recursion) - test(test_make_fcomm_examples) - test(test_functional_commitments_demo) - test(test_chained_functional_commitments_demo)' - - run: - name: Benches build successfully - command: cargo bench --no-run --profile dev-ci - - run: - name: Linux Doc Tests - command: cargo test --doc --workspace --profile dev-ci - - save-sccache-cache - - run: - name: Print sccache stats - command: sccache -s - - linux_exhaustive: - executor: default - environment: *setup-env - steps: - - checkout - - attach_workspace: - at: "~/" - - restore_rustup_cache - - set_env_path - - install_gpu_deps - - restore-sccache-cache - - run: - name: Linux Tests - command: cargo nextest run --profile ci --workspace --cargo-profile dev-ci --run-ignored all - - run: - name: Benches build successfully - command: cargo bench --no-run --profile dev-ci - - run: - name: Linux Doc Tests - command: cargo test --doc --workspace --profile dev-ci - - save-sccache-cache - - run: - name: Print sccache stats - command: sccache -s - - arm64: - executor: arm64 - environment: *setup-env - steps: - - checkout - - run: - name: Update submodules - command: | - git submodule update --init --recursive - - attach_workspace: - at: "~/" - - set_versions_n_runners - - set_env_path - - install_gpu_deps - - setup-sccache - - restore-sccache-cache - - run: - name: Arm64 Tests - command: cargo nextest run --profile ci --workspace --cargo-profile dev-ci - no_output_timeout: 120m - - run: - name: Benches build successfully - command: cargo bench --no-run --profile dev-ci - - run: - name: Arm64 Doc Tests - command: cargo test --doc --workspace --profile dev-ci - - save-sccache-cache - - run: - name: Print sccache stats - command: sccache -s - - mac: - executor: darwin - environment: *setup-env - steps: - - checkout - - run: - name: Update submodules - command: git submodule update --init --recursive - - attach_workspace: - at: "~/" - set_versions_n_runners - set_env_path - setup-sccache - restore-sccache-cache - run: - name: MacOS Tests + name: Run cargo tests command: cargo nextest run --profile ci --workspace --cargo-profile dev-ci no_output_timeout: 120m - run: name: Benches build successfully command: cargo bench --no-run --profile dev-ci - run: - name: MacOS Doc Tests + name: Doc Tests command: cargo test --doc --workspace --profile dev-ci - save-sccache-cache - run: name: Print sccache stats command: sccache -s - clippy: - executor: default - environment: *setup-env - steps: - - checkout - - set_env_path - - attach_workspace: - at: "~/" - - restore_rustup_cache - - run: cargo clippy --all -- -D warnings - - rustfmt: - executor: default - environment: *setup-env - steps: - - checkout - - set_env_path - - attach_workspace: - at: "~/" - - restore_rustup_cache - - run: cargo fmt --all -- --check workflows: test-all: jobs: - - cargo_fetch: - filters: - branches: - ignore: - - gh-pages - - staging.tmp - - trying.tmp - - staging-squash-merge.tmp - - arm64: + - test: + matrix: + parameters: + os: [arm64, darwin] filters: branches: ignore: @@ -292,58 +143,3 @@ workflows: - staging.tmp - trying.tmp - staging-squash-merge.tmp - - mac: - filters: - branches: - ignore: - - master - - gh-pages - - staging.tmp - - trying.tmp - - staging-squash-merge.tmp - - linux_release: - requires: - - cargo_fetch - filters: - branches: - ignore: - - master - - gh-pages - - staging.tmp - - trying.tmp - - staging-squash-merge.tmp - - rustfmt: - requires: - - cargo_fetch - filters: - branches: - ignore: - - master - - gh-pages - - staging.tmp - - trying.tmp - - staging-squash-merge.tmp - - clippy: - requires: - - cargo_fetch - filters: - branches: - ignore: - - master - - gh-pages - - staging.tmp - - trying.tmp - - staging-squash-merge.tmp - nightly: - triggers: - - schedule: - cron: "0 0 * * *" - filters: - branches: - only: - - master - jobs: - - cargo_fetch - - linux_exhaustive: - requires: - - cargo_fetch diff --git a/.clippy.toml b/.clippy.toml index d0112753cc..9c71707ab2 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,3 +1,4 @@ +type-complexity-threshold = 999 too-many-arguments-threshold = 20 disallowed-methods = [ # we use strict naming for pasta fields diff --git a/.config/nextest.toml b/.config/nextest.toml index d79bd47e56..547a2ff46e 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -6,5 +6,7 @@ failure-output = "immediate-final" status-level = "skip" # Do not cancel the test run on the first failure. fail-fast = false -# Mark tests as slow after 5mins, kill them after 50 -slow-timeout = { period = "300s", terminate-after = 10 } +# Mark tests as slow after 5mins, kill them after 20mins +slow-timeout = { period = "300s", terminate-after = 4 } +# Retry failed tests once, marked flaky if test then passes +retries = 1 diff --git a/.github/workflows/bench_pr_comment.yml b/.github/workflows/bench_pr_comment.yml new file mode 100644 index 0000000000..1c6e632b0b --- /dev/null +++ b/.github/workflows/bench_pr_comment.yml @@ -0,0 +1,65 @@ +# Creates a PR benchmark comment with a comparison to master +name: Benchmark pull requests +on: + issue_comment: + types: [created] + +env: + CARGO_TERM_COLOR: always + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + run-benchmark: + name: run end2end benchmark + runs-on: [self-hosted, bench] + if: + github.event.issue.pull_request + && github.event.issue.state == 'open' + && contains(github.event.comment.body, '!benchmark') + && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') + steps: + - uses: xt0rted/pull-request-comment-branch@v2 + id: comment-branch + + - uses: actions/checkout@v4 + if: success() + with: + ref: ${{ steps.comment-branch.outputs.head_ref }} + # Set the Rust env vars + - uses: actions-rs/toolchain@v1 + - uses: Swatinem/rust-cache@v2 + - uses: boa-dev/criterion-compare-action@v3 + with: + # Optional. Compare only this benchmark target + benchName: "end2end" + # Needed. The name of the branch to compare with + branchName: ${{ github.ref_name }} + + gpu-benchmark: + name: run fibonacci benchmark on GPU + runs-on: [self-hosted, gpu-bench] + if: + github.event.issue.pull_request + && github.event.issue.state == 'open' + && contains(github.event.comment.body, '!benchmark') + && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER') + steps: + - uses: xt0rted/pull-request-comment-branch@v2 + id: comment-branch + + - uses: actions/checkout@v4 + if: success() + with: + ref: ${{ steps.comment-branch.outputs.head_ref }} + # Set the Rust env vars + - uses: actions-rs/toolchain@v1 + - uses: Swatinem/rust-cache@v2 + - uses: boa-dev/criterion-compare-action@v3 + with: + # Optional. Compare only this benchmark target + benchName: "fibonacci" + # Needed. The name of the branch to compare with + branchName: ${{ github.ref_name }} \ No newline at end of file diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b8f84179c8..fdfdedccff 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -9,9 +9,9 @@ jobs: name: Continuous benchmarking runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get old benchmarks - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: gh-pages path: gh-pages diff --git a/.github/workflows/pr-test.yml b/.github/workflows/ci.yml similarity index 92% rename from .github/workflows/pr-test.yml rename to .github/workflows/ci.yml index 39429d0c8d..e170985bf5 100644 --- a/.github/workflows/pr-test.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,7 @@ -name: Lurk CI tests +name: CI tests on: merge_group: - push: - branches-ignore: - - "gh-readonly-queue/**" - - "master" pull_request: types: [opened, synchronize, reopened, ready_for_review] branches: [master] @@ -42,7 +38,9 @@ jobs: env: RUSTFLAGS: -D warnings steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + submodules: recursive - uses: actions-rs/toolchain@v1 - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -52,7 +50,7 @@ jobs: - name: Linux Gadget Tests w/o debug assertions run: | cargo nextest run --profile ci --workspace --cargo-profile dev-no-assertions -E 'test(circuit::gadgets)' - + misc: runs-on: ${{ matrix.os }} strategy: @@ -63,7 +61,7 @@ jobs: env: RUSTFLAGS: -D warnings steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 @@ -77,12 +75,12 @@ jobs: run: cargo build --benches - name: Doctests run: | - cargo test --doc - + cargo test --doc --workspace --profile dev-ci + clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions-rs/toolchain@v1 with: components: rustfmt, clippy @@ -99,7 +97,7 @@ jobs: msrv: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install rustup uses: actions-rs/toolchain@v1 with: diff --git a/.github/workflows/ci_ignored.yml b/.github/workflows/ci_ignored.yml new file mode 100644 index 0000000000..647c27dfdf --- /dev/null +++ b/.github/workflows/ci_ignored.yml @@ -0,0 +1,25 @@ +# Run ignored tests only when attempting to merge +name: Linux ignored tests + +on: + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + linux_ignored: + runs-on: [self-hosted, test] + env: + RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: actions-rs/toolchain@v1 + - uses: taiki-e/install-action@nextest + - uses: Swatinem/rust-cache@v2 + - name: Linux Tests + run: | + cargo nextest run --profile ci --workspace --cargo-profile dev-ci --run-ignored ignored-only -E 'all() - test(groth16::tests::outer_prove_recursion) - test(test_make_fcomm_examples) - test(test_functional_commitments_demo) - test(test_chained_functional_commitments_demo)' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2c5da74865..90b2e69739 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust toolchain uses: actions-rs/toolchain@v1 diff --git a/.github/workflows/rust.yml b/.github/workflows/gpu.yml similarity index 56% rename from .github/workflows/rust.yml rename to .github/workflows/gpu.yml index 52a42fe912..5a44670a91 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/gpu.yml @@ -1,9 +1,11 @@ -name: Rust +# Runs the test suite on a self-hosted GPU machine with CUDA and OpenCL enabled +name: GPU tests on: push: branches: - master + env: CARGO_TERM_COLOR: always # Disable incremental compilation. @@ -25,62 +27,54 @@ env: RUSTUP_MAX_RETRIES: 10 # Don't emit giant backtraces in the CI logs. RUST_BACKTRACE: short + RUSTFLAGS: -D warnings concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: - - ubuntu-latest - fail-fast: false + cuda: + name: Rust tests on CUDA + runs-on: [self-hosted, gpu] env: - RUSTFLAGS: -D warnings + NVIDIA_VISIBLE_DEVICES: all + NVIDIA_DRIVER_CAPABILITITES: compute,utility steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - uses: actions-rs/toolchain@v1 - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 - # make sure benches don't bit-rot - - name: build benches - run: cargo build --benches --release - - name: cargo test - run: | - cargo nextest run --profile ci --workspace --cargo-profile dev-ci --run-ignored all -E 'all() - test(groth16::tests::outer_prove_recursion) - test(test_make_fcomm_examples) - test(test_functional_commitments_demo) - test(test_chained_functional_commitments_demo)' - - name: Doctests + # Check we have access to the machine's Nvidia drivers + - run: nvidia-smi + # The `compute`/`sm` number corresponds to the Nvidia GPU architecture + # In this case, the self-hosted machine uses the Ampere architecture, but we want this to be configurable + # See https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/ + - name: Set env for CUDA compute + run: echo "CUDA_ARCH=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader | sed 's/\.//g')" >> $GITHUB_ENV + - name: set env for EC_GPU + run: echo 'EC_GPU_CUDA_NVCC_ARGS=--fatbin --gpu-architecture=sm_${{ env.CUDA_ARCH }} --generate-code=arch=compute_${{ env.CUDA_ARCH }},code=sm_${{ env.CUDA_ARCH }}' >> $GITHUB_ENV + - run: echo "${{ env.EC_GPU_CUDA_NVCC_ARGS}}" + # Check that CUDA is installed with a driver-compatible version + # This must also be compatible with the GPU architecture, see above link + - run: nvcc --version + - name: CUDA tests + env: + EC_GPU_FRAMEWORK: cuda + # Temporarily skipping CLI test due to cudaMallocAsync error: https://github.com/lurk-lab/lurk-rs/issues/596 run: | - cargo test --doc - - msrv: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install rustup - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - uses: Swatinem/rust-cache@v2 - - name: Install cargo-msrv - run: cargo install cargo-msrv - - name: Check Rust MSRV - run: cargo msrv verify + cargo nextest run --profile ci --cargo-profile dev-ci --features cuda -E 'all() - test(test_prove_and_verify)' - # Runs the test suite on a self-hosted GPU machine with CUDA enabled - test-gpu: - name: Rust tests on CUDA + opencl: + name: Rust tests on OpenCL runs-on: [self-hosted, gpu] env: NVIDIA_VISIBLE_DEVICES: all NVIDIA_DRIVER_CAPABILITITES: compute,utility steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - uses: actions-rs/toolchain@v1 @@ -99,37 +93,11 @@ jobs: # Check that CUDA is installed with a driver-compatible version # This must also be compatible with the GPU architecture, see above link - run: nvcc --version - # The use of `gcc-10`/`g++-10` is a hack to work around a pasta-msm error with CUDA 11.5 and gcc 11.3: - # https://github.com/NVlabs/instant-ngp/issues/119#issuecomment-1231890237 - # We could also update to CUDA 11.8+ but it breaks the self-hosted runner - - name: CUDA tests - env: - EC_GPU_FRAMEWORK: cuda - run: | - CC=gcc-10 CXX=g++-10 cargo nextest run --profile ci --cargo-profile dev-ci --features cuda # Check that we can access the OpenCL headers - run: clinfo - name: OpenCL tests env: EC_GPU_FRAMEWORK: opencl + # Temporarily skipping CLI test due to cudaMallocAsync error: https://github.com/lurk-lab/lurk-rs/issues/596 run: | - CC=gcc-10 CXX=g++-10 cargo nextest run --profile ci --cargo-profile dev-ci --features cuda,opencl - - rustfmt_n_clippy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: actions-rs/toolchain@v1 - with: - components: rustfmt, clippy - - uses: Swatinem/rust-cache@v2 - - name: rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all --check - # See '.cargo/config' for list of enabled/disabled clippy lints - - name: cargo clippy - run: cargo xclippy -D warnings + cargo nextest run --profile ci --cargo-profile dev-ci --features cuda,opencl -E 'all() - test(test_prove_and_verify)' diff --git a/.github/workflows/licenses-audits.yml b/.github/workflows/licenses-audits.yml index 022d8f0a7d..29336317a1 100644 --- a/.github/workflows/licenses-audits.yml +++ b/.github/workflows/licenses-audits.yml @@ -15,5 +15,5 @@ jobs: name: cargo-deny (advisories, licenses, bans, ...) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 \ No newline at end of file diff --git a/.github/workflows/links-check.yml b/.github/workflows/links-check.yml index bb01379a88..ec77c1cca1 100644 --- a/.github/workflows/links-check.yml +++ b/.github/workflows/links-check.yml @@ -15,7 +15,7 @@ jobs: linkChecker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Link Checker uses: lycheeverse/lychee-action@v1.8.0 with: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000000..c1cae9e384 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,47 @@ +name: Nightly tests on master + +on: + schedule: + - cron: '0 0 * * *' + +env: + CARGO_TERM_COLOR: always + # Disable incremental compilation. + # + # Incremental compilation is useful as part of an edit-build-test-edit cycle, + # as it lets the compiler avoid recompiling code that hasn't changed. However, + # on CI, we're not making small edits; we're almost always building the entire + # project from scratch. Thus, incremental compilation on CI actually + # introduces *additional* overhead to support making future builds + # faster...but no future builds will ever occur in any given CI environment. + # + # See https://matklad.github.io/2021/09/04/fast-rust-builds.html#ci-workflow + # for details. + CARGO_INCREMENTAL: 0 + # Allow more retries for network requests in cargo (downloading crates) and + # rustup (installing toolchains). This should help to reduce flaky CI failures + # from transient network timeouts or other issues. + CARGO_NET_RETRY: 10 + RUSTUP_MAX_RETRIES: 10 + # Don't emit giant backtraces in the CI logs. + RUST_BACKTRACE: short + +jobs: + linux_exhaustive: + runs-on: [self-hosted, test] + env: + RUSTFLAGS: -D warnings + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + - uses: taiki-e/install-action@nextest + - uses: Swatinem/rust-cache@v2 + - name: Linux Tests + run: | + cargo nextest run --profile ci --workspace --cargo-profile dev-ci --run-ignored all + - name: Benches build successfully + run: | + cargo bench --no-run --profile dev-ci + - name: Linux Doc Tests + run: | + cargo test --doc --workspace --profile dev-ci diff --git a/.github/workflows/pr-bench.yml b/.github/workflows/pr-bench.yml deleted file mode 100644 index 29b944669f..0000000000 --- a/.github/workflows/pr-bench.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Benchmark pull requests -on: - issue_comment: - types: [created] - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - runBenchmark: - name: run benchmark - runs-on: [self-hosted, bench] - if: - github.event.issue.pull_request - && github.event.issue.state == 'open' - && contains(github.event.comment.body, '!benchmark') - && (github.event.comment.author_association == 'MEMBER' - || github.event.comment.author_association == 'OWNER') - steps: - - uses: xt0rted/pull-request-comment-branch@v2 - id: comment-branch - - - uses: actions/checkout@v3 - if: success() - with: - ref: ${{ steps.comment-branch.outputs.head_ref }} - # Set the Rust env vars - - uses: actions-rs/toolchain@v1 - - uses: Swatinem/rust-cache@v2 - - uses: boa-dev/criterion-compare-action@v3 - with: - # Optional. Compare only this benchmark target - benchName: "end2end" - # Needed. The name of the branch to compare with - branchName: ${{ github.ref_name }} diff --git a/.github/workflows/upstream_update.yml b/.github/workflows/upstream_update.yml deleted file mode 100644 index 80cb362c90..0000000000 --- a/.github/workflows/upstream_update.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Check development branch accounts for upstream updates - -on: - schedule: - - cron: '5 * * * *' # every hour, 5 mins past the hour - workflow_dispatch: # manual trigger - -env: - ROOT: "master" - SCION: "dev" - -jobs: - check_branches: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # necessary to access all commits - ref: ${{ env.SCION }} - - run: git fetch origin - - name: Check if SCION is an extension of ROOT - id: check - run: | - ROOT_COMMIT=$(git rev-parse origin/${{ env.ROOT }}) - SCION_COMMIT=$(git rev-parse HEAD) - if git merge-base --is-ancestor $ROOT_COMMIT HEAD; then - echo "status=true" >> $GITHUB_ENV - echo "message=$SCION is rebased on $ROOT as of $SCION_COMMIT" >> $GITHUB_ENV - else - echo "status=false" >> $GITHUB_ENV - echo "message=$SCION at $SCION_COMMIT does not include $ROOT at $ROOT_COMMIT" >> $GITHUB_ENV - fi - shell: bash - - - name: Rebase SCION onto ROOT - id: rebase - run: | - git config user.name github-actions - git config user.email github-actions@github.com - git rebase origin/${{ env.ROOT }} - if: env.status == 'false' - continue-on-error: true - - - uses: dtolnay/rust-toolchain@stable - if: steps.rebase.outcome == 'success' - - name: Run a trivial test - id: test - run: cargo check --all --tests --benches - if: steps.rebase.outcome == 'success' - continue-on-error: true - - - name: Push the changes - run: | - git push origin HEAD:${{ env.SCION }} -f - SCION_COMMIT=$(git rev-parse HEAD) - echo "status=true" >> $GITHUB_ENV - echo "message=$SCION is rebased on $ROOT as of $SCION_COMMIT" >> $GITHUB_ENV - if: steps.rebase.outcome == 'success' && steps.test.outcome == 'success' - - - name: Find the last report issue open - id: last_issue - uses: micalevisk/last-issue-action@v2 - with: - state: open - labels: | - upstream - automated issue - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Debug - run: | - echo ${{ env.status }} - echo ${{ env.message }} - echo ${{ steps.last_issue.outputs.has-found }} - echo ${{ steps.last_issue.outputs.issue-number }} - echo ${{ steps.rebase.outcome }} - echo ${{ steps.test.outcome }} - - - name: Close last report open issue - if: env.status == 'true' && steps.last_issue.outputs.has-found == 'true' - uses: peter-evans/close-issue@v3 - with: - issue-number: ${{ steps.last_issue.outputs.issue-number }} - comment: ${{ env.message }} - - - name: Update last report open issue - if: env.status == 'false' && steps.last_issue.outputs.has-found == 'true' - uses: peter-evans/create-or-update-comment@v3 - with: - issue-number: ${{ steps.last_issue.outputs.issue-number }} - body: ${{ env.message }} - edit-mode: replace - - - name: Create file for issue - if: env.status == 'false' && steps.last_issue.outputs.has-found == 'false' - run: echo "${{ env.message }}" > ./_body.md - - - name: Create issue from report - if: env.status == 'false' && steps.last_issue.outputs.has-found == 'false' - uses: peter-evans/create-issue-from-file@v4 - with: - title: ${{ env.SCION }} needs to be rebased on ${{ env.ROOT }} - content-filepath: ./_body.md - assignees: huitseeker - labels: | - upstream - automated issue diff --git a/.gitignore b/.gitignore index 4d590a1f9b..59dd42e684 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ result* /scratch /.direnv *.commit +Cargo.lock # Configurations for VSCode .vscode/ diff --git a/.gitmodules b/.gitmodules index b9e14355d4..6db8534f7c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "lurk-lib"] path = lurk-lib - url = git@github.com:lurk-lab/lurk-lib.git + url = https://github.com/lurk-lab/lurk-lib.git diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index e2ff55d3e9..0000000000 --- a/Cargo.lock +++ /dev/null @@ -1,3209 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "addchain" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2e69442aa5628ea6951fa33e24efe8313f4321a91bd729fc2f75bdfc858570" -dependencies = [ - "num-bigint 0.3.3", - "num-integer", - "num-traits", -] - -[[package]] -name = "addr2line" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anstream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is-terminal", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" - -[[package]] -name = "anstyle-parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" -dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "anstyle-wincon" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" -dependencies = [ - "anstyle", - "windows-sys 0.48.0", -] - -[[package]] -name = "anyhow" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" - -[[package]] -name = "anymap" -version = "1.0.0-beta.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1f8f5a6f3d50d89e3797d7593a50f96bb2aaa20ca0cc7be1fb673232c91d72" - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "assert_cmd" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" -dependencies = [ - "anstyle", - "bstr", - "doc-comment", - "predicates 3.0.3", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - -[[package]] -name = "async-trait" -version = "0.1.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.68" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base-x" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" - -[[package]] -name = "base32ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396664016f30ad5ab761000391a5c0b436f7bfac738858263eb25897658b98c9" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "bellperson" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93eaee4b4753554139ae52ecf0e8b8c128cbc561b32e1bfaa32f70cba8518c1f" -dependencies = [ - "bincode", - "blake2s_simd 1.0.1", - "blstrs", - "byteorder", - "crossbeam-channel", - "digest", - "ec-gpu", - "ec-gpu-gen", - "ff", - "group", - "log", - "memmap2", - "pairing", - "rand", - "rand_core", - "rayon", - "rustversion", - "serde", - "sha2", - "thiserror", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2b_simd" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" -dependencies = [ - "arrayref", - "arrayvec 0.7.4", - "constant_time_eq 0.2.6", -] - -[[package]] -name = "blake2s_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq 0.1.5", -] - -[[package]] -name = "blake2s_simd" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" -dependencies = [ - "arrayref", - "arrayvec 0.7.4", - "constant_time_eq 0.2.6", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blst" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a30d0edd9dd1c60ddb42b80341c7852f6f985279a5c1a83659dcb65899dec99" -dependencies = [ - "cc", - "glob", - "threadpool", - "which", - "zeroize", -] - -[[package]] -name = "blstrs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33149fccb7f93271f0192614b884430cf274e880506bbd171cbc8918dcc95b14" -dependencies = [ - "blst", - "byte-slice-cast", - "ec-gpu", - "ff", - "group", - "pairing", - "rand_core", - "serde", - "subtle", -] - -[[package]] -name = "bstr" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" - -[[package]] -name = "byte-slice-cast" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" - -[[package]] -name = "bytecount" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "ciborium" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" - -[[package]] -name = "ciborium-ll" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "cl3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e014961508a981c7ef28a65d0c965af7061593b4db50cff83952c62931b39ec" -dependencies = [ - "libc", - "opencl-sys", -] - -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags 1.3.2", - "textwrap 0.11.0", - "unicode-width", -] - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "atty", - "bitflags 1.3.2", - "clap_derive 3.2.25", - "clap_lex 0.2.4", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap 0.16.0", -] - -[[package]] -name = "clap" -version = "4.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0827b011f6f8ab38590295339817b0d26f344aa4932c3ced71b45b0c54b4a9" -dependencies = [ - "clap_builder", - "clap_derive 4.3.12", - "once_cell", -] - -[[package]] -name = "clap-verbosity-flag" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0636f9c040082f8e161555a305f8cec1a1c2828b3d981c812b8c39f4ac00c42c" -dependencies = [ - "clap 3.2.25", - "log", -] - -[[package]] -name = "clap_builder" -version = "4.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9441b403be87be858db6a23edb493e7f694761acdc3343d5a0fcaafd304cbc9e" -dependencies = [ - "anstream", - "anstyle", - "clap_lex 0.5.0", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", -] - -[[package]] -name = "clap_derive" -version = "4.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" -dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "clap_lex" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" - -[[package]] -name = "clipboard-win" -version = "4.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362" -dependencies = [ - "error-code", - "str-buf", - "winapi", -] - -[[package]] -name = "clutch" -version = "0.1.0" -dependencies = [ - "anyhow", - "assert_cmd", - "blstrs", - "camino", - "clap 4.3.17", - "fcomm", - "ff", - "lurk", - "pasta_curves", - "pretty_env_logger", - "serde", -] - -[[package]] -name = "colorchoice" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" - -[[package]] -name = "config" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" -dependencies = [ - "async-trait", - "json5", - "lazy_static", - "nom", - "pathdiff", - "ron", - "rust-ini", - "serde", - "serde_json", - "toml", - "yaml-rust", -] - -[[package]] -name = "console" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "windows-sys 0.45.0", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" - -[[package]] -name = "cpp_demangle" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee34052ee3d93d6d8f3e6f81d85c47921f6653a19a7b70e939e3e602d893a674" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "cpufeatures" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "criterion" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" -dependencies = [ - "anes", - "atty", - "cast", - "ciborium", - "clap 3.2.25", - "criterion-plot", - "itertools 0.10.5", - "lazy_static", - "num-traits", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cuda-config" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee74643f7430213a1a78320f88649de309b20b80818325575e393f848f79f5d" -dependencies = [ - "glob", -] - -[[package]] -name = "cuda-driver-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d4c552cc0de854877d80bcd1f11db75d42be32962d72a6799b88dcca88fffbd" -dependencies = [ - "cuda-config", -] - -[[package]] -name = "dashmap" -version = "5.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6943ae99c34386c84a470c499d3414f66502a41340aa895406e0d2e4a207b91d" -dependencies = [ - "cfg-if", - "hashbrown 0.14.0", - "lock_api", - "once_cell", - "parking_lot_core", -] - -[[package]] -name = "debugid" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" -dependencies = [ - "uuid", -] - -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dissimilar" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e3bdc80eee6e16b2b6b0f87fbc98c04bee3455e35174c0de1a125d0688c632" - -[[package]] -name = "dlv-list" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" - -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - -[[package]] -name = "ec-gpu" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd63582de2b59ea1aa48d7c1941b5d87618d95484397521b3acdfa0e1e9f5e45" - -[[package]] -name = "ec-gpu-gen" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "892df2aa20abec5b816e15d5d6383892ca142077708efa3067dd3ac44b75c664" -dependencies = [ - "bitvec", - "crossbeam-channel", - "ec-gpu", - "execute", - "ff", - "group", - "hex", - "log", - "num_cpus", - "once_cell", - "rayon", - "rust-gpu-tools", - "sha2", - "thiserror", - "yastl", -] - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "error-code" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" -dependencies = [ - "libc", - "str-buf", -] - -[[package]] -name = "execute" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d9a9ea4c04632c16bc5c71a2fcc63d308481f7fc67eb1a1ce6315c44a426ae" -dependencies = [ - "execute-command-macro", - "execute-command-tokens", - "generic-array", -] - -[[package]] -name = "execute-command-macro" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5fbc65a0cf735106743f4c38c9a3671c1e734b5c2c20d21a3c93c696daa3157" -dependencies = [ - "execute-command-macro-impl", -] - -[[package]] -name = "execute-command-macro-impl" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a9a55d1dab3b07854648d48e366f684aefe2ac78ae28cec3bf65e3cd53d9a3" -dependencies = [ - "execute-command-tokens", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "execute-command-tokens" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba569491c70ec8471e34aa7e9c0b9e82bb5d2464c0398442d17d3c4af814e5a" - -[[package]] -name = "expect-test" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d9eafeadd538e68fb28016364c9732d78e420b9ff8853fa5e4058861e9f8d3" -dependencies = [ - "dissimilar", - "once_cell", -] - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fcomm" -version = "0.1.1" -dependencies = [ - "anyhow", - "assert_cmd", - "base64", - "bellperson", - "bincode", - "blstrs", - "camino", - "clap 3.2.25", - "clap-verbosity-flag", - "ff", - "hex", - "insta", - "log", - "lurk", - "lurk-macros", - "num_cpus", - "once_cell", - "pairing", - "pasta_curves", - "predicates 2.1.5", - "pretty_env_logger", - "proptest", - "proptest-derive", - "rand", - "serde", - "serde_json", - "tempfile", - "thiserror", -] - -[[package]] -name = "fd-lock" -version = "3.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5" -dependencies = [ - "cfg-if", - "rustix 0.38.4", - "windows-sys 0.48.0", -] - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "bitvec", - "byteorder", - "ff_derive", - "rand_core", - "subtle", -] - -[[package]] -name = "ff_derive" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f54704be45ed286151c5e11531316eaef5b8f5af7d597b806fdb8af108d84a" -dependencies = [ - "addchain", - "cfg-if", - "num-bigint 0.3.3", - "num-integer", - "num-traits", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", -] - -[[package]] -name = "fil-rustacuda" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40666d4072d5353fd2fd3aa26e4ddb225c38c6440e8c467cae9b17688ae6191c" -dependencies = [ - "bitflags 1.3.2", - "cuda-driver-sys", - "rustacuda_core", - "rustacuda_derive", -] - -[[package]] -name = "findshlibs" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" -dependencies = [ - "cc", - "lazy_static", - "libc", - "winapi", -] - -[[package]] -name = "flate2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" -dependencies = [ - "num-traits", -] - -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "spin 0.9.8", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand", - "rand_core", - "rand_xorshift", - "subtle", -] - -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "halo2curves" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b1142bd1059aacde1b477e0c80c142910f1ceae67fc619311d6a17428007ab" -dependencies = [ - "blake2b_simd", - "ff", - "group", - "lazy_static", - "num-bigint 0.4.3", - "num-traits", - "pasta_curves", - "paste", - "rand", - "rand_core", - "serde", - "serde_arrays", - "static_assertions", - "subtle", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.6", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "hdrhistogram" -version = "7.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" -dependencies = [ - "byteorder", - "num-traits", -] - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] - -[[package]] -name = "home" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" -dependencies = [ - "windows-sys 0.48.0", -] - -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "rayon", -] - -[[package]] -name = "inferno" -version = "0.11.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb7c1b80a1dfa604bb4a649a5c5aeef3d913f7c520cb42b40e534e8a61bcdfc" -dependencies = [ - "ahash 0.8.3", - "indexmap", - "is-terminal", - "itoa", - "log", - "num-format", - "once_cell", - "quick-xml", - "rgb", - "str_stack", -] - -[[package]] -name = "insta" -version = "1.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a" -dependencies = [ - "console", - "lazy_static", - "linked-hash-map", - "serde", - "similar", - "yaml-rust", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.2", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi 0.3.2", - "rustix 0.38.4", - "windows-sys 0.48.0", -] - -[[package]] -name = "itertools" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a" - -[[package]] -name = "js-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - -[[package]] -name = "keccak" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -dependencies = [ - "spin 0.5.2", -] - -[[package]] -name = "libc" -version = "0.2.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libm" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" - -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" - -[[package]] -name = "lurk" -version = "0.2.0" -dependencies = [ - "ahash 0.7.6", - "anyhow", - "anymap", - "assert_cmd", - "base-x", - "base32ct", - "base64", - "bellperson", - "bincode", - "blstrs", - "camino", - "cfg-if", - "clap 4.3.17", - "config", - "criterion", - "dashmap", - "ff", - "generic-array", - "getrandom", - "hex", - "home", - "indexmap", - "itertools 0.9.0", - "log", - "lurk-macros", - "lurk-metrics", - "memmap2", - "metrics", - "neptune", - "nom", - "nom_locate", - "nova-snark", - "num-bigint 0.4.3", - "num-integer", - "num-traits", - "once_cell", - "pairing", - "pasta-msm", - "pasta_curves", - "peekmore", - "pprof", - "pretty_env_logger", - "proptest", - "proptest-derive", - "rand", - "rand_core", - "rand_xorshift", - "rayon", - "rustyline", - "rustyline-derive", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2", - "stable_deref_trait", - "structopt", - "tap", - "tempfile", - "thiserror", -] - -[[package]] -name = "lurk-macros" -version = "0.1.0" -dependencies = [ - "bincode", - "lurk", - "pasta_curves", - "proc-macro2 1.0.66", - "proptest", - "proptest-derive", - "quote 1.0.31", - "serde", - "syn 1.0.109", -] - -[[package]] -name = "lurk-metrics" -version = "0.1.0" -dependencies = [ - "expect-test", - "hdrhistogram", - "log", - "metrics", - "once_cell", - "testing_logger", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "metrics" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" -dependencies = [ - "ahash 0.8.3", - "metrics-macros", - "portable-atomic", -] - -[[package]] -name = "metrics-macros" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddece26afd34c31585c74a4db0630c376df271c285d682d1e55012197830b6df" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "neptune" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb9a64337e6d214e2a48db5714ef18cf1e5a7bbff9043838fdf6e57ce5659335" -dependencies = [ - "bellperson", - "blake2s_simd 0.5.11", - "blstrs", - "byteorder", - "ec-gpu", - "ec-gpu-gen", - "ff", - "generic-array", - "itertools 0.8.2", - "log", - "pasta_curves", - "serde", - "trait-set", -] - -[[package]] -name = "nix" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "static_assertions", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nom_locate" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e299bf5ea7b212e811e71174c5d1a5d065c4c0ad0c8691ecb1f97e3e66025e" -dependencies = [ - "bytecount", - "memchr", - "nom", -] - -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - -[[package]] -name = "nova-snark" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e12911ac9672ad436acfc992f09e26a5960513bbe81d1572005cadd8c1be8f4" -dependencies = [ - "bellperson", - "bincode", - "bitvec", - "byteorder", - "digest", - "ff", - "flate2", - "generic-array", - "getrandom", - "halo2curves", - "itertools 0.11.0", - "neptune", - "num-bigint 0.4.3", - "num-integer", - "num-traits", - "pasta-msm", - "pasta_curves", - "rand_chacha", - "rand_core", - "rayon", - "serde", - "sha3", - "subtle", - "thiserror", -] - -[[package]] -name = "num-bigint" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", - "rand", - "serde", -] - -[[package]] -name = "num-format" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" -dependencies = [ - "arrayvec 0.7.4", - "itoa", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.2", - "libc", -] - -[[package]] -name = "object" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "opencl-sys" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75919008b8ed7ce9620e2b3580c648db40c7f564a368f271b2647145046d8ba" -dependencies = [ - "libc", -] - -[[package]] -name = "opencl3" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c247ee0c98af8a67ab9c836ed2ea663ac19a17d8bae71325b509835e536c18ad" -dependencies = [ - "cl3", - "libc", -] - -[[package]] -name = "ordered-multimap" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" -dependencies = [ - "dlv-list", - "hashbrown 0.12.3", -] - -[[package]] -name = "os_str_bytes" -version = "6.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" - -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.1", -] - -[[package]] -name = "pasta-msm" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e85d75eba3e7e9ee3bd11342b669185e194dadda3557934bc1000d9b87159d3" -dependencies = [ - "cc", - "pasta_curves", - "semolina", - "sppark", - "which", -] - -[[package]] -name = "pasta_curves" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" -dependencies = [ - "blake2b_simd", - "ec-gpu", - "ff", - "group", - "hex", - "lazy_static", - "rand", - "serde", - "static_assertions", - "subtle", -] - -[[package]] -name = "paste" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b27ab7be369122c218afc2079489cdcb4b517c0a3fc386ff11e1fedfcc2b35" - -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "peekmore" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9163e1259760e83d528d1b3171e5100c1767f10c52e1c4d6afad26e63d47d758" - -[[package]] -name = "pest" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "pest_meta" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - -[[package]] -name = "plotters" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" - -[[package]] -name = "plotters-svg" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "portable-atomic" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc55135a600d700580e406b4de0d59cb9ad25e344a3a091a97ded2622ec4ec6" - -[[package]] -name = "pprof" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196ded5d4be535690899a4631cc9f18cdc41b7ebf24a79400f46f48e49a11059" -dependencies = [ - "backtrace", - "cfg-if", - "criterion", - "findshlibs", - "inferno", - "libc", - "log", - "nix", - "once_cell", - "parking_lot", - "smallvec", - "symbolic-demangle", - "tempfile", - "thiserror", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "predicates" -version = "2.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" -dependencies = [ - "difflib", - "float-cmp", - "itertools 0.10.5", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" -dependencies = [ - "anstyle", - "difflib", - "itertools 0.10.5", - "predicates-core", -] - -[[package]] -name = "predicates-core" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" - -[[package]] -name = "predicates-tree" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "pretty_env_logger" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" -dependencies = [ - "env_logger", - "log", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "proc-macro2" -version = "1.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proptest" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" -dependencies = [ - "bit-set", - "bitflags 1.3.2", - "byteorder", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax 0.6.29", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "proptest-derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90b46295382dc76166cb7cf2bb4a97952464e4b7ed5a43e6cd34e1fec3349ddc" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quick-xml" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - -[[package]] -name = "quote" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" -dependencies = [ - "proc-macro2 1.0.66", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "regex" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax 0.7.4", -] - -[[package]] -name = "regex-automata" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.7.4", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - -[[package]] -name = "rgb" -version = "0.8.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "ron" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" -dependencies = [ - "base64", - "bitflags 1.3.2", - "serde", -] - -[[package]] -name = "rust-gpu-tools" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0ce78d5548a74fad25177825d0c20f4cfbc6eaf796bcee53afe792e39ede4e2" -dependencies = [ - "fil-rustacuda", - "hex", - "home", - "log", - "once_cell", - "opencl3", - "sha2", - "temp-env", - "thiserror", -] - -[[package]] -name = "rust-ini" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - -[[package]] -name = "rustacuda_core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3858b08976dc2f860c5efbbb48cdcb0d4fafca92a6ac0898465af16c0dbe848" - -[[package]] -name = "rustacuda_derive" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ce8670a1a1d0fc2514a3b846dacdb65646f9bd494b6674cfacbb4ce430bd7e" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustix" -version = "0.37.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" -dependencies = [ - "bitflags 2.3.3", - "errno", - "libc", - "linux-raw-sys 0.4.3", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustversion" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc31bd9b61a32c31f9650d18add92aa83a49ba979c143eefd27fe7177b05bd5f" - -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] - -[[package]] -name = "rustyline" -version = "11.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfc8644681285d1fb67a467fb3021bfea306b99b4146b166a1fe3ada965eece" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "clipboard-win", - "fd-lock", - "libc", - "log", - "memchr", - "nix", - "rustyline-derive", - "scopeguard", - "unicode-segmentation", - "unicode-width", - "utf8parse", - "winapi", -] - -[[package]] -name = "rustyline-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8218eaf5d960e3c478a1b0f129fa888dd3d8d22eb3de097e9af14c1ab4438024" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", -] - -[[package]] -name = "ryu" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semolina" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0111fd4fa831becb0606b9a2285ef3bee3c6a70d690209b8ae9514e9befe23" -dependencies = [ - "cc", - "glob", -] - -[[package]] -name = "serde" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_arrays" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_bytes" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "serde_json" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f1e14e89be7aa4c4b78bdbdc9eb5bf8517829a600ae8eaa39a6e1d960b5185c" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d89a8107374290037607734c0b73a85db7ed80cae314b3c5791f192a496e731" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "sha2" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "similar" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" - -[[package]] -name = "smallvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "sppark" -version = "0.1.3" -source = "git+https://github.com/supranational/sppark?rev=5fea26f43cc5d12a77776c70815e7c722fd1f8a7#5fea26f43cc5d12a77776c70815e7c722fd1f8a7" -dependencies = [ - "cc", - "which", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "str-buf" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" - -[[package]] -name = "str_stack" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap 2.34.0", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "symbolic-common" -version = "10.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b55cdc318ede251d0957f07afe5fed912119b8c1bc5a7804151826db999e737" -dependencies = [ - "debugid", - "memmap2", - "stable_deref_trait", - "uuid", -] - -[[package]] -name = "symbolic-demangle" -version = "10.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79be897be8a483a81fff6a3a4e195b4ac838ef73ca42d348b3f722da9902e489" -dependencies = [ - "cpp_demangle", - "rustc-demangle", - "symbolic-common", -] - -[[package]] -name = "syn" -version = "0.15.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "unicode-ident", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "temp-env" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9547444bfe52cbd79515c6c8087d8ae6ca8d64d2d31a27746320f5cb81d1a15c" -dependencies = [ - "parking_lot", -] - -[[package]] -name = "tempfile" -version = "3.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" -dependencies = [ - "autocfg", - "cfg-if", - "fastrand", - "redox_syscall", - "rustix 0.37.23", - "windows-sys 0.48.0", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "termtree" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" - -[[package]] -name = "testing_logger" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d92b727cb45d33ae956f7f46b966b25f1bc712092aeef9dba5ac798fc89f720" -dependencies = [ - "log", -] - -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "textwrap" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" - -[[package]] -name = "thiserror" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "trait-set" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 1.0.109", -] - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - -[[package]] -name = "unicode-ident" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - -[[package]] -name = "utf8parse" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" - -[[package]] -name = "uuid" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - -[[package]] -name = "walkdir" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" -dependencies = [ - "quote 1.0.31", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" - -[[package]] -name = "web-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - -[[package]] -name = "yastl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca6c5a4d66c1a9ea261811cf4773c27343de7e5033e1b75ea3f297dc7db3c1a" -dependencies = [ - "flume", - "scopeguard", -] - -[[package]] -name = "zeroize" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2 1.0.66", - "quote 1.0.31", - "syn 2.0.26", -] diff --git a/Cargo.toml b/Cargo.toml index a383c8f58c..a23c88b585 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" description = "Turing-Complete Zero Knowledge" edition = "2021" repository = "https://github.com/lurk-lab/lurk-rs" -rust-version = "1.68.2" +rust-version = "1.70.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -16,33 +16,35 @@ anymap = "1.0.0-beta.2" base32ct = { version = "0.2.0", features = ["std"] } base64 = { workspace = true } base-x = "0.2.11" +bellpepper = { workspace = true } +bellpepper-core = { workspace = true } bellperson = { workspace = true } bincode = { workspace = true } blstrs = { workspace = true } +camino = { workspace = true } clap = { workspace = true, features = ["derive"] } config = "0.13.3" dashmap = "5.5.0" ff = { workspace = true } generic-array = "0.14.7" hex = { version = "0.4.3", features = ["serde"] } -indexmap = { version = "1.9.3", features = ["rayon"] } +indexmap = { version = "1.9.3", features = ["rayon", "serde"] } itertools = "0.9" -log = { workspace = true } lurk-macros = { path = "lurk-macros" } lurk-metrics = { path = "lurk-metrics" } metrics = { workspace = true } neptune = { workspace = true, features = ["arity2","arity4","arity8","arity16","pasta","bls"] } nom = "7.1.3" nom_locate = "4.1.0" -nova = { workspace = true, default-features = false } +nova = { workspace = true } num-bigint = "0.4.3" +num_cpus = "1.10.1" num-integer = "0.1.45" num-traits = "0.2.15" once_cell = { workspace = true } pairing = { workspace = true } pasta_curves = { workspace = true, features = ["repr-c", "serde"] } peekmore = "1.3.0" -pretty_env_logger = { workspace = true } rand = { workspace = true } rand_core = { version = "0.6.4", default-features = false } rand_xorshift = "0.3.0" @@ -55,11 +57,21 @@ serde_repr = "0.1.14" tap = "1.0.1" stable_deref_trait = "1.2.0" thiserror = { workspace = true } -camino = { workspace = true } +abomonation = { workspace = true} +abomonation_derive = { git = "https://github.com/lurk-lab/abomonation_derive.git" } +crossbeam = "0.8.2" +byteorder = "1.4.3" +circom-scotia = { git = "https://github.com/lurk-lab/circom-scotia", branch = "dev" } +sha2 = { version = "0.10.2" } +reqwest = { version = "0.11.18", features = ["stream", "blocking"] } +ansi_term = "0.12.1" +tracing = { workspace = true } +tracing-texray = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] memmap = { version = "0.5.10", package = "memmap2" } -pasta-msm = "0.1.4" +pasta-msm = { workspace = true } proptest = { workspace = true } proptest-derive = { workspace = true } rand = "0.8.5" @@ -72,58 +84,66 @@ rustyline = { version = "11.0", features = ["derive"], default-features = false [features] default = [] -opencl = ["neptune/opencl"] -cuda = ["neptune/cuda"] +opencl = ["neptune/opencl", "bellperson/opencl", "nova/opencl"] +cuda = ["neptune/cuda", "bellperson/cuda", "nova/cuda"] # compile without ISA extensions -portable = ["blstrs/portable", "pasta-msm/portable"] +portable = ["blstrs/portable", "pasta-msm/portable", "nova/portable"] flamegraph = ["pprof/flamegraph", "pprof/criterion"] [dev-dependencies] assert_cmd = "2.0.12" cfg-if = "1.0.0" +ascii_table = "4.0.2" criterion = "0.4" hex = "0.4.3" pprof = { version = "0.11" } -sha2 = { version = "0.10.7" } structopt = { version = "0.3", default-features = false } tap = "1.0.1" tempfile = { workspace = true } +tracing-test = "0.1" + +[build-dependencies] +vergen = { version = "8", features = ["build", "git", "gitcl"] } [workspace] resolver = "2" members = [ "clutch", - "fcomm", + "fcomm", "lurk-macros", "lurk-metrics" ] # Dependencies that should be kept in sync through the whole workspace [workspace.dependencies] +abomonation = "0.7.3" anyhow = "1.0.72" base64 = "0.13.1" -bellperson = "0.25" +bellpepper = { git = "https://github.com/lurk-lab/bellpepper", branch = "dev" } +bellpepper-core = { git = "https://github.com/lurk-lab/bellpepper", branch = "dev" } +bellperson = { git = "https://github.com/lurk-lab/bellperson", branch = "dev" } bincode = "1.3.3" -blstrs = "0.7.0" +blstrs = { git = "https://github.com/lurk-lab/blstrs", branch = "dev" } clap = "4.3.17" ff = "0.13" -log = "0.4.19" metrics = "0.21.1" -neptune = { version = "10.0.0" } -nova = { version = "0.23", default-features = false, package = "nova-snark" } +neptune = { git = "https://github.com/lurk-lab/neptune", branch = "dev" } +nova = { git = "https://github.com/lurk-lab/arecibo", branch = "dev", package = "nova-snark" } once_cell = "1.18.0" pairing = { version = "0.23" } -pasta_curves = { version = "0.5.1" } -pasta-msm = "0.1.4" -pretty_env_logger = "0.4" +pasta_curves = { git = "https://github.com/lurk-lab/pasta_curves", branch = "dev" } +pasta-msm = { git = "https://github.com/lurk-lab/pasta-msm", branch = "dev" } proptest = "1.2.0" proptest-derive = "0.3.0" rand = "0.8" serde = "1.0" serde_json = { version = "1.0" } -thiserror = "1.0.43" tempfile = "3.6.0" camino = "1.1.6" +thiserror = "1.0.44" +tracing = "0.1.37" +tracing-texray = "0.2.0" +tracing-subscriber = "0.3.17" [[bin]] name = "lurk" @@ -155,9 +175,14 @@ harness = false name = "synthesis" harness = false +[[bench]] +name = "sha256_ivc" +harness = false + [[bench]] name = "public_params" harness = false [patch.crates-io] -sppark = { git = "https://github.com/supranational/sppark", rev="5fea26f43cc5d12a77776c70815e7c722fd1f8a7" } +# This is needed to ensure halo2curves, which imports pasta-curves, uses the *same* traits in bn256_grumpkin +pasta_curves = { git="https://github.com/lurk-lab/pasta_curves", branch="dev" } diff --git a/LICENSE-MIT b/LICENSE-MIT index b09c2b19b7..60e830f59c 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,25 +1,21 @@ +MIT License + Copyright (c) 2023 Lurk Lab -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 56ce882bbe..477551e627 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Lurk [![CircleCI](https://circleci.com/gh/lurk-lab/lurk-rs.svg?style=shield)](https://circleci.com/gh/lurk-lab/lurk-rs) -![minimum rustc 1.60][msrv-image] +![minimum rustc 1.70][msrv-image] ![crates.io][crates-image] -[msrv-image]: https://img.shields.io/badge/rustc-1.60+-blue.svg +[msrv-image]: https://img.shields.io/badge/rustc-1.70+-blue.svg [crates-image]: https://img.shields.io/crates/v/lurk.svg # Status (Alpha) @@ -197,3 +197,4 @@ $ cargo build ## License MIT or Apache 2.0 +---Temporary change--- diff --git a/benches/end2end.rs b/benches/end2end.rs index 60423c9a62..eb8572b06c 100644 --- a/benches/end2end.rs +++ b/benches/end2end.rs @@ -15,16 +15,22 @@ use lurk::{ proof::Prover, ptr::Ptr, public_parameters, + state::State, store::Store, }; use pasta_curves::pallas; -use std::sync::Arc; use std::time::Duration; +use std::{cell::RefCell, rc::Rc, sync::Arc}; const PUBLIC_PARAMS_PATH: &str = "/var/tmp/lurk_benches/public_params"; const DEFAULT_REDUCTION_COUNT: usize = 10; -fn go_base(store: &mut Store, a: u64, b: u64) -> Ptr { +fn go_base( + store: &mut Store, + state: Rc>, + a: u64, + b: u64, +) -> Ptr { let program = format!( r#" (let ((foo (lambda (a b) @@ -40,7 +46,7 @@ fn go_base(store: &mut Store, a: u64, b: u64) -> Ptr { "# ); - store.read(&program).unwrap() + store.read_with_state(state, &program).unwrap() } /// To run these benchmarks, do `cargo criterion end2end_benchmark`. @@ -66,6 +72,7 @@ fn end2end_benchmark(c: &mut Criterion) { // use cached public params let pp = public_parameters::public_params( reduction_count, + true, lang_pallas_rc.clone(), Utf8Path::new(PUBLIC_PARAMS_PATH), ) @@ -74,9 +81,11 @@ fn end2end_benchmark(c: &mut Criterion) { let size = (10, 0); let benchmark_id = BenchmarkId::new("end2end_go_base_nova", format!("_{}_{}", size.0, size.1)); + let state = State::init_lurk_state().rccell(); + group.bench_with_input(benchmark_id, &size, |b, &s| { b.iter(|| { - let ptr = go_base::(&mut store, s.0, s.1); + let ptr = go_base::(&mut store, state.clone(), s.0, s.1); let _result = prover .evaluate_and_prove(&pp, ptr, env, &mut store, limit, lang_pallas_rc.clone()) .unwrap(); @@ -98,6 +107,8 @@ fn store_benchmark(c: &mut Criterion) { let mut bls12_store = Store::::default(); let mut pallas_store = Store::::default(); + let state = State::init_lurk_state().rccell(); + // todo!() rfc out into more flexible test cases let sizes = vec![(10, 16), (10, 160)]; for size in sizes { @@ -106,7 +117,7 @@ fn store_benchmark(c: &mut Criterion) { let bls12_id = BenchmarkId::new("store_go_base_bls12", ¶meter_string); group.bench_with_input(bls12_id, &size, |b, &s| { b.iter(|| { - let result = go_base::(&mut bls12_store, s.0, s.1); + let result = go_base::(&mut bls12_store, state.clone(), s.0, s.1); black_box(result) }) }); @@ -114,7 +125,7 @@ fn store_benchmark(c: &mut Criterion) { let pasta_id = BenchmarkId::new("store_go_base_pallas", ¶meter_string); group.bench_with_input(pasta_id, &size, |b, &s| { b.iter(|| { - let result = go_base::(&mut pallas_store, s.0, s.1); + let result = go_base::(&mut pallas_store, state.clone(), s.0, s.1); black_box(result) }) }); @@ -135,6 +146,8 @@ fn hydration_benchmark(c: &mut Criterion) { let mut bls12_store = Store::::default(); let mut pallas_store = Store::::default(); + let state = State::init_lurk_state().rccell(); + // todo!() rfc out into more flexible test cases let sizes = vec![(10, 16), (10, 160)]; for size in sizes { @@ -143,7 +156,7 @@ fn hydration_benchmark(c: &mut Criterion) { { let benchmark_id = BenchmarkId::new("hydration_go_base_bls12", ¶meter_string); group.bench_with_input(benchmark_id, &size, |b, &s| { - let _ptr = go_base::(&mut bls12_store, s.0, s.1); + let _ptr = go_base::(&mut bls12_store, state.clone(), s.0, s.1); b.iter(|| bls12_store.hydrate_scalar_cache()) }); } @@ -151,7 +164,7 @@ fn hydration_benchmark(c: &mut Criterion) { { let benchmark_id = BenchmarkId::new("hydration_go_base_pallas", ¶meter_string); group.bench_with_input(benchmark_id, &size, |b, &s| { - let _ptr = go_base::(&mut pallas_store, s.0, s.1); + let _ptr = go_base::(&mut pallas_store, state.clone(), s.0, s.1); b.iter(|| pallas_store.hydrate_scalar_cache()) }); } @@ -175,6 +188,8 @@ fn eval_benchmark(c: &mut Criterion) { let mut bls12_store = Store::::default(); let mut pallas_store = Store::::default(); + let state = State::init_lurk_state().rccell(); + // todo!() rfc out into more flexible test cases let sizes = vec![(10, 16), (10, 160)]; for size in sizes { @@ -183,7 +198,7 @@ fn eval_benchmark(c: &mut Criterion) { { let benchmark_id = BenchmarkId::new("eval_go_base_bls12", ¶meter_string); group.bench_with_input(benchmark_id, &size, |b, &s| { - let ptr = go_base::(&mut bls12_store, s.0, s.1); + let ptr = go_base::(&mut bls12_store, state.clone(), s.0, s.1); b.iter(|| { Evaluator::new( ptr, @@ -200,7 +215,7 @@ fn eval_benchmark(c: &mut Criterion) { { let benchmark_id = BenchmarkId::new("eval_go_base_pallas", ¶meter_string); group.bench_with_input(benchmark_id, &size, |b, &s| { - let ptr = go_base::(&mut pallas_store, s.0, s.1); + let ptr = go_base::(&mut pallas_store, state.clone(), s.0, s.1); b.iter(|| { Evaluator::new( ptr, @@ -270,22 +285,31 @@ fn prove_benchmark(c: &mut Criterion) { let size = (10, 0); let benchmark_id = BenchmarkId::new("prove_go_base_nova", format!("_{}_{}", size.0, size.1)); + let state = State::init_lurk_state().rccell(); + group.bench_with_input(benchmark_id, &size, |b, &s| { - let ptr = go_base::(&mut store, s.0, s.1); + let ptr = go_base::(&mut store, state.clone(), s.0, s.1); let prover = NovaProver::new(reduction_count, lang_pallas.clone()); let pp = public_parameters::public_params( reduction_count, + true, lang_pallas_rc.clone(), Utf8Path::new(PUBLIC_PARAMS_PATH), ) .unwrap(); let frames = prover - .get_evaluation_frames(ptr, empty_sym_env(&store), &mut store, limit, &lang_pallas) + .get_evaluation_frames( + ptr, + empty_sym_env(&store), + &mut store, + limit, + lang_pallas_rc.clone(), + ) .unwrap(); b.iter(|| { let result = prover - .prove(&pp, &frames, &mut store, lang_pallas_rc.clone()) + .prove(&pp, &frames, &store, lang_pallas_rc.clone()) .unwrap(); black_box(result); }) @@ -314,22 +338,31 @@ fn prove_compressed_benchmark(c: &mut Criterion) { format!("_{}_{}", size.0, size.1), ); + let state = State::init_lurk_state().rccell(); + group.bench_with_input(benchmark_id, &size, |b, &s| { - let ptr = go_base::(&mut store, s.0, s.1); + let ptr = go_base::(&mut store, state.clone(), s.0, s.1); let prover = NovaProver::new(reduction_count, lang_pallas.clone()); let pp = public_parameters::public_params( reduction_count, + true, lang_pallas_rc.clone(), Utf8Path::new(PUBLIC_PARAMS_PATH), ) .unwrap(); let frames = prover - .get_evaluation_frames(ptr, empty_sym_env(&store), &mut store, limit, &lang_pallas) + .get_evaluation_frames( + ptr, + empty_sym_env(&store), + &mut store, + limit, + lang_pallas_rc.clone(), + ) .unwrap(); b.iter(|| { let (proof, _, _, _) = prover - .prove(&pp, &frames, &mut store, lang_pallas_rc.clone()) + .prove(&pp, &frames, &store, lang_pallas_rc.clone()) .unwrap(); let compressed_result = proof.compress(&pp).unwrap(); @@ -353,24 +386,33 @@ fn verify_benchmark(c: &mut Criterion) { let mut store = Store::default(); let reduction_count = DEFAULT_REDUCTION_COUNT; + let state = State::init_lurk_state().rccell(); + let sizes = vec![(10, 0)]; for size in sizes { let parameter_string = format!("_{}_{}", size.0, size.1); let benchmark_id = BenchmarkId::new("verify_go_base_nova", ¶meter_string); group.bench_with_input(benchmark_id, &size, |b, &s| { - let ptr = go_base(&mut store, s.0, s.1); + let ptr = go_base(&mut store, state.clone(), s.0, s.1); let prover = NovaProver::new(reduction_count, lang_pallas.clone()); let pp = public_parameters::public_params( reduction_count, + true, lang_pallas_rc.clone(), Utf8Path::new(PUBLIC_PARAMS_PATH), ) .unwrap(); let frames = prover - .get_evaluation_frames(ptr, empty_sym_env(&store), &mut store, limit, &lang_pallas) + .get_evaluation_frames( + ptr, + empty_sym_env(&store), + &mut store, + limit, + lang_pallas_rc.clone(), + ) .unwrap(); let (proof, z0, zi, num_steps) = prover - .prove(&pp, &frames, &mut store, lang_pallas_rc.clone()) + .prove(&pp, &frames, &store, lang_pallas_rc.clone()) .unwrap(); b.iter_batched( @@ -402,24 +444,33 @@ fn verify_compressed_benchmark(c: &mut Criterion) { let mut store = Store::default(); let reduction_count = DEFAULT_REDUCTION_COUNT; + let state = State::init_lurk_state().rccell(); + let sizes = vec![(10, 0)]; for size in sizes { let parameter_string = format!("_{}_{}", size.0, size.1); let benchmark_id = BenchmarkId::new("verify_compressed_go_base_nova", ¶meter_string); group.bench_with_input(benchmark_id, &size, |b, &s| { - let ptr = go_base(&mut store, s.0, s.1); + let ptr = go_base(&mut store, state.clone(), s.0, s.1); let prover = NovaProver::new(reduction_count, lang_pallas.clone()); let pp = public_parameters::public_params( reduction_count, + true, lang_pallas_rc.clone(), Utf8Path::new(PUBLIC_PARAMS_PATH), ) .unwrap(); let frames = prover - .get_evaluation_frames(ptr, empty_sym_env(&store), &mut store, limit, &lang_pallas) + .get_evaluation_frames( + ptr, + empty_sym_env(&store), + &mut store, + limit, + lang_pallas_rc.clone(), + ) .unwrap(); let (proof, z0, zi, num_steps) = prover - .prove(&pp, &frames, &mut store, lang_pallas_rc.clone()) + .prove(&pp, &frames, &store, lang_pallas_rc.clone()) .unwrap(); let compressed_proof = proof.compress(&pp).unwrap(); diff --git a/benches/fibonacci.rs b/benches/fibonacci.rs index dfb6808ad7..dcbd4b24cf 100644 --- a/benches/fibonacci.rs +++ b/benches/fibonacci.rs @@ -1,4 +1,4 @@ -use std::{sync::Arc, time::Duration}; +use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration}; use camino::Utf8Path; use criterion::{ @@ -12,125 +12,100 @@ use lurk::{ eval::{ empty_sym_env, lang::{Coproc, Lang}, - Evaluator, }, field::LurkField, proof::nova::NovaProver, proof::Prover, ptr::Ptr, public_parameters::public_params, + state::State, store::Store, }; const PUBLIC_PARAMS_PATH: &str = "/var/tmp/lurk_benches/public_params"; -const DEFAULT_REDUCTION_COUNT: usize = 100; -fn fib(store: &mut Store, a: u64) -> Ptr { - let program = format!( - r#" -(let ((fib (lambda (target) - (letrec ((next (lambda (a b target) - (if (= 0 target) - a - (next b - (+ a b) - (- target 1)))))) - (next 0 1 target))))) - (fib {a})) -"# - ); - store.read(&program).unwrap() -} +fn fib(store: &mut Store, state: Rc>, _a: u64) -> Ptr { + let program = r#" +(letrec ((next (lambda (a b) (next b (+ a b)))) + (fib (next 0 1))) + (fib)) +"#; -#[allow(dead_code)] -fn fibo_total(name: &str, iterations: u64, c: &mut BenchmarkGroup) { - let limit: usize = 10_000_000_000; - let lang_pallas = Lang::>::new(); - let lang_rc = Arc::new(lang_pallas.clone()); - let reduction_count = DEFAULT_REDUCTION_COUNT; - - // use cached public params - let pp = public_params( - reduction_count, - lang_rc.clone(), - Utf8Path::new(PUBLIC_PARAMS_PATH), - ) - .unwrap(); + store.read_with_state(state, program).unwrap() +} - c.bench_with_input( - BenchmarkId::new(name.to_string(), iterations), - &(iterations), - |b, iterations| { - let mut store = Store::default(); - let env = empty_sym_env(&store); - let ptr = fib::(&mut store, black_box(*iterations)); - let prover = NovaProver::new(reduction_count, lang_pallas.clone()); +// The env output in the `fib_frame`th frame of the above, infinite Fibonacci computation will contain a binding of the +// nth Fibonacci number to `a`. +// means of computing it.] +fn fib_frame(n: usize) -> usize { + 11 + 16 * n +} - b.iter_batched( - || lang_rc.clone(), - |lang_rc| { - let result = prover - .evaluate_and_prove(&pp, ptr, env, &mut store, limit, lang_rc) - .unwrap(); - black_box(result); - }, - BatchSize::SmallInput, - ) - }, - ); +// Set the limit so the last step will be filled exactly, since Lurk currently only pads terminal/error continuations. +fn fib_limit(n: usize, rc: usize) -> usize { + let frame = fib_frame(n); + rc * (frame / rc + usize::from(frame % rc != 0)) } -#[allow(dead_code)] -fn fibo_eval(name: &str, iterations: u64, c: &mut BenchmarkGroup) { - let limit = 10_000_000_000; - let lang_pallas = Lang::>::new(); +struct ProveParams { + fib_n: usize, + reduction_count: usize, +} - c.bench_with_input( - BenchmarkId::new(name.to_string(), iterations), - &(iterations), - |b, iterations| { - let mut store = Store::default(); - let ptr = fib::(&mut store, black_box(*iterations)); - b.iter(|| { - let result = - Evaluator::new(ptr, empty_sym_env(&store), &mut store, limit, &lang_pallas) - .eval(); - black_box(result) - }); - }, - ); +impl ProveParams { + fn name(&self) -> String { + let date = env!("VERGEN_GIT_COMMIT_DATE"); + let sha = env!("VERGEN_GIT_SHA"); + format!("{date}:{sha}:Fibonacci-rc={}", self.reduction_count) + } } -fn fibo_prove(name: &str, iterations: u64, c: &mut BenchmarkGroup) { - let limit = 10_000_000_000; +fn fibo_prove( + prove_params: ProveParams, + c: &mut BenchmarkGroup<'_, M>, + state: Rc>, +) { + let ProveParams { + fib_n, + reduction_count, + } = prove_params; + + let limit = fib_limit(fib_n, reduction_count); let lang_pallas = Lang::>::new(); let lang_rc = Arc::new(lang_pallas.clone()); - let reduction_count = DEFAULT_REDUCTION_COUNT; + + // use cached public params let pp = public_params( - reduction_count, + prove_params.reduction_count, + true, lang_rc.clone(), Utf8Path::new(PUBLIC_PARAMS_PATH), ) .unwrap(); c.bench_with_input( - BenchmarkId::new(name.to_string(), iterations), - &iterations, - |b, iterations| { + BenchmarkId::new(prove_params.name(), fib_n), + &prove_params, + |b, prove_params| { let mut store = Store::default(); - let env = empty_sym_env(&store); - let ptr = fib::(&mut store, black_box(*iterations)); - let prover = NovaProver::new(reduction_count, lang_pallas.clone()); - let frames = prover - .get_evaluation_frames(ptr, env, &mut store, limit, &lang_pallas) + let env = empty_sym_env(&store); + let ptr = fib::( + &mut store, + state.clone(), + black_box(prove_params.fib_n as u64), + ); + let prover = NovaProver::new(prove_params.reduction_count, lang_pallas.clone()); + + let frames = &prover + .get_evaluation_frames(ptr, env, &mut store, limit, lang_rc.clone()) .unwrap(); b.iter_batched( - || (frames.clone(), lang_rc.clone()), // avoid cloning the frames in the benchmark + || (frames, lang_rc.clone()), |(frames, lang_rc)| { - let result = prover.prove(&pp, &frames, &mut store, lang_rc).unwrap(); - black_box(result); + let result = prover.prove(&pp, frames, &store, lang_rc); + let _ = black_box(result); }, BatchSize::LargeInput, ) @@ -138,45 +113,28 @@ fn fibo_prove(name: &str, iterations: u64, c: &mut ); } -#[allow(dead_code)] -fn fibonacci_eval(c: &mut Criterion) { - static BATCH_SIZES: [u64; 2] = [100, 1000]; - let mut group: BenchmarkGroup<_> = c.benchmark_group("Evaluate"); - for size in BATCH_SIZES.iter() { - fibo_eval("Fibonacci", *size, &mut group); - } -} - fn fibonacci_prove(c: &mut Criterion) { - static BATCH_SIZES: [u64; 2] = [100, 1000]; - let mut group: BenchmarkGroup<_> = c.benchmark_group("Prove"); - group.sampling_mode(SamplingMode::Flat); // This can take a *while* - group.sample_size(10); - - for size in BATCH_SIZES.iter() { - fibo_prove("Fibonacci", *size, &mut group); - } -} - -#[allow(dead_code)] -fn fibonacci_total(c: &mut Criterion) { - static BATCH_SIZES: [u64; 2] = [100, 1000]; - let mut group: BenchmarkGroup<_> = c.benchmark_group("Total"); + tracing::debug!("{:?}", &*lurk::config::CONFIG); + let reduction_counts = vec![100, 600, 700, 800, 900]; + let batch_sizes = vec![100, 200]; + let mut group: BenchmarkGroup<'_, _> = c.benchmark_group("Prove"); group.sampling_mode(SamplingMode::Flat); // This can take a *while* group.sample_size(10); - - for size in BATCH_SIZES.iter() { - fibo_total("Fibonacci", *size, &mut group); + let state = State::init_lurk_state().rccell(); + + for fib_n in batch_sizes.iter() { + for reduction_count in reduction_counts.iter() { + let prove_params = ProveParams { + fib_n: *fib_n, + reduction_count: *reduction_count, + }; + fibo_prove(prove_params, &mut group, state.clone()); + } } } cfg_if::cfg_if! { if #[cfg(feature = "flamegraph")] { - // In order to collect a flamegraph, you need to indicate a profile time, see - // https://github.com/tikv/pprof-rs#integrate-with-criterion - // Example usage : - // cargo criterion --bench fibonacci --features flamegraph -- --profile-time 5 - // Warning: it is not recommended to run this on an M1 Mac, as making pprof work well there is hard. criterion_group! { name = benches; config = Criterion::default() @@ -184,8 +142,8 @@ cfg_if::cfg_if! { .sample_size(10) .with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None))); targets = - fibonacci_prove, - } + fibonacci_prove, + } } else { criterion_group! { name = benches; @@ -193,8 +151,8 @@ cfg_if::cfg_if! { .measurement_time(Duration::from_secs(120)) .sample_size(10); targets = - fibonacci_prove, - } + fibonacci_prove, + } } } diff --git a/benches/sha256_ivc.rs b/benches/sha256_ivc.rs new file mode 100644 index 0000000000..65968874d5 --- /dev/null +++ b/benches/sha256_ivc.rs @@ -0,0 +1,399 @@ +//! This benchmark measures the IVC performance of coprocessors, by adding a `sha256` +//! circuit alongside the lurk primary circuit. When supernova is integrated as a backend, +//! then NIVC performance can also be tested. This benchmark serves as a baseline for that +//! performance. +//! +//! Note: The example [example/sha256_ivc.rs] is this same benchmark but as an example +//! that's easier to play with and run. + +use lurk::circuit::gadgets::data::GlobalAllocations; +use lurk::state::user_sym; +use lurk::{circuit::gadgets::pointer::AllocatedContPtr, tag::Tag}; +use std::{cell::RefCell, marker::PhantomData, rc::Rc, sync::Arc, time::Duration}; + +use bellpepper::gadgets::{multipack::pack_bits, sha256::sha256}; +use bellpepper_core::{boolean::Boolean, ConstraintSystem, SynthesisError}; +use camino::Utf8Path; +use criterion::{ + black_box, criterion_group, criterion_main, measurement, BatchSize, BenchmarkGroup, + BenchmarkId, Criterion, SamplingMode, +}; + +use lurk_macros::Coproc; +use pasta_curves::pallas::Scalar as Fr; + +use lurk::{ + circuit::gadgets::pointer::AllocatedPtr, + coprocessor::{CoCircuit, Coprocessor}, + eval::{empty_sym_env, lang::Lang}, + field::LurkField, + proof::nova::NovaProver, + proof::Prover, + ptr::Ptr, + public_parameters::public_params, + state::State, + store::Store, + tag::ExprTag, + Num, +}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +const PUBLIC_PARAMS_PATH: &str = "/var/tmp/lurk_benches/public_params"; + +fn sha256_ivc( + store: &mut Store, + state: Rc>, + arity: usize, + n: usize, + input: Vec, +) -> Ptr { + assert_eq!(n, input.len()); + let input = input + .iter() + .map(|i| format!("(sha256 . {i})")) + .collect::>() + .join(" "); + let input = format!("'({input})"); + let program = format!( + r#" +(letrec ((encode-1 (lambda (term) + (let ((type (car term)) + (value (cdr term))) + (if (eq 'sha256 type) + (eval (cons 'sha256_ivc_{arity} value)) + (if (eq 'lurk type) + (commit value) + (if (eq 'id type) + value)))))) + (encode (lambda (input) + (if input + (cons + (encode-1 (car input)) + (encode (cdr input))))))) + (encode '((lurk . 5) (id . 15) {input}))) +"# + ); + + store.read_with_state(state, &program).unwrap() +} +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct Sha256Coprocessor { + arity: usize, + pub(crate) _p: PhantomData, +} + +impl CoCircuit for Sha256Coprocessor { + fn arity(&self) -> usize { + self.arity + } + + fn synthesize>( + &self, + cs: &mut CS, + _g: &GlobalAllocations, + _store: &Store, + input_exprs: &[AllocatedPtr], + input_env: &AllocatedPtr, + input_cont: &AllocatedContPtr, + ) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedContPtr), SynthesisError> { + let zero = Boolean::constant(false); + + let mut bits = vec![]; + + // println!("{:?}", input_exprs); + + for input_ptr in input_exprs { + let tag_bits = input_ptr + .tag() + .to_bits_le_strict(&mut cs.namespace(|| "preimage_tag_bits"))?; + let hash_bits = input_ptr + .hash() + .to_bits_le_strict(&mut cs.namespace(|| "preimage_hash_bits"))?; + + bits.extend(tag_bits); + bits.push(zero.clone()); // need 256 bits (or some multiple of 8). + bits.extend(hash_bits); + bits.push(zero.clone()); // need 256 bits (or some multiple of 8). + } + + bits.reverse(); + + let mut digest_bits = sha256(cs.namespace(|| "digest_bits"), &bits)?; + + digest_bits.reverse(); + + // Fine to lose the last <1 bit of precision. + let digest_scalar = pack_bits(cs.namespace(|| "digest_scalar"), &digest_bits)?; + let output_expr = AllocatedPtr::alloc_tag( + &mut cs.namespace(|| "output_expr"), + ExprTag::Num.to_field(), + digest_scalar, + )?; + Ok((output_expr, input_env.clone(), input_cont.clone())) + } +} + +impl Coprocessor for Sha256Coprocessor { + fn eval_arity(&self) -> usize { + self.arity + } + + fn simple_evaluate(&self, s: &mut Store, args: &[Ptr]) -> Ptr { + let mut hasher = ::new(); + + let mut input = vec![0u8; 64 * self.arity]; + + for (i, input_ptr) in args.iter().enumerate() { + let input_zptr = s.hash_expr(input_ptr).unwrap(); + let tag_zptr: F = input_zptr.tag().to_field(); + let hash_zptr = input_zptr.value(); + input[(64 * i)..(64 * i + 32)].copy_from_slice(&tag_zptr.to_bytes()); + input[(64 * i + 32)..(64 * (i + 1))].copy_from_slice(&hash_zptr.to_bytes()); + } + + input.reverse(); + + hasher.update(input); + let mut bytes = hasher.finalize(); + bytes.reverse(); + let l = bytes.len(); + // Discard the two most significant bits. + bytes[l - 1] &= 0b00111111; + + let scalar = F::from_bytes(&bytes).unwrap(); + let result = Num::from_scalar(scalar); + + s.intern_num(result) + } + + fn has_circuit(&self) -> bool { + true + } +} + +impl Sha256Coprocessor { + pub(crate) fn new(arity: usize) -> Self { + Self { + arity, + _p: Default::default(), + } + } +} + +#[derive(Clone, Debug, Coproc, Serialize, Deserialize)] +enum Sha256Coproc { + SC(Sha256Coprocessor), +} + +struct ProveParams { + arity: usize, + n: usize, + reduction_count: usize, +} + +impl ProveParams { + fn name(&self) -> String { + let date = env!("VERGEN_GIT_COMMIT_DATE"); + let sha = env!("VERGEN_GIT_SHA"); + format!( + "{date}:{sha}:rc={}:sha256_ivc_{}", + self.reduction_count, self.arity + ) + } +} + +fn sha256_ivc_prove( + prove_params: ProveParams, + c: &mut BenchmarkGroup<'_, M>, + state: Rc>, +) { + let ProveParams { + arity, + n: _, + reduction_count, + } = prove_params; + + let limit = 10000; + + let store = &mut Store::::new(); + let cproc_sym = user_sym(&format!("sha256_ivc_{arity}")); + + let lang = Lang::>::new_with_bindings( + store, + vec![(cproc_sym, Sha256Coprocessor::new(arity).into())], + ); + let lang_rc = Arc::new(lang.clone()); + + // use cached public params + let pp = public_params( + reduction_count, + true, + lang_rc.clone(), + Utf8Path::new(PUBLIC_PARAMS_PATH), + ) + .unwrap(); + + c.bench_with_input( + BenchmarkId::new(prove_params.name(), arity), + &prove_params, + |b, prove_params| { + let env = empty_sym_env(store); + let ptr = sha256_ivc( + store, + state.clone(), + black_box(prove_params.arity), + black_box(prove_params.n), + (0..prove_params.n).collect(), + ); + + let prover = NovaProver::new(prove_params.reduction_count, lang.clone()); + + let frames = &prover + .get_evaluation_frames(ptr, env, store, limit, lang_rc.clone()) + .unwrap(); + + b.iter_batched( + || (frames, lang_rc.clone()), + |(frames, lang_rc)| { + let result = prover.prove(&pp, frames, store, lang_rc); + let _ = black_box(result); + }, + BatchSize::LargeInput, + ) + }, + ); +} + +fn prove_benchmarks(c: &mut Criterion) { + tracing::debug!("{:?}", &*lurk::config::CONFIG); + let reduction_counts = vec![10, 100]; + let batch_sizes = vec![1, 2, 5, 10, 20]; + let mut group: BenchmarkGroup<'_, _> = c.benchmark_group("prove"); + group.sampling_mode(SamplingMode::Flat); // This can take a *while* + group.sample_size(10); + let state = State::init_lurk_state().rccell(); + + for &n in batch_sizes.iter() { + for &reduction_count in reduction_counts.iter() { + let prove_params = ProveParams { + arity: 1, + n, + reduction_count, + }; + sha256_ivc_prove(prove_params, &mut group, state.clone()); + } + } +} + +fn sha256_ivc_prove_compressed( + prove_params: ProveParams, + c: &mut BenchmarkGroup<'_, M>, + state: Rc>, +) { + let ProveParams { + arity, + n: _, + reduction_count, + } = prove_params; + + let limit = 10000; + + let store = &mut Store::::new(); + let cproc_sym = user_sym(&format!("sha256_ivc_{arity}")); + + let lang = Lang::>::new_with_bindings( + store, + vec![(cproc_sym, Sha256Coprocessor::new(arity).into())], + ); + let lang_rc = Arc::new(lang.clone()); + + // use cached public params + let pp = public_params( + reduction_count, + true, + lang_rc.clone(), + Utf8Path::new(PUBLIC_PARAMS_PATH), + ) + .unwrap(); + + c.bench_with_input( + BenchmarkId::new(prove_params.name(), arity), + &prove_params, + |b, prove_params| { + let env = empty_sym_env(store); + let ptr = sha256_ivc( + store, + state.clone(), + black_box(prove_params.arity), + black_box(prove_params.n), + (0..prove_params.n).collect(), + ); + + let prover = NovaProver::new(prove_params.reduction_count, lang.clone()); + + let frames = &prover + .get_evaluation_frames(ptr, env, store, limit, lang_rc.clone()) + .unwrap(); + + b.iter_batched( + || (frames, lang_rc.clone()), + |(frames, lang_rc)| { + let (proof, _, _, _) = prover.prove(&pp, frames, store, lang_rc).unwrap(); + let compressed_result = proof.compress(&pp).unwrap(); + + let _ = black_box(compressed_result); + }, + BatchSize::LargeInput, + ) + }, + ); +} + +fn prove_compressed_benchmarks(c: &mut Criterion) { + tracing::debug!("{:?}", &*lurk::config::CONFIG); + let reduction_counts = vec![10, 100]; + let batch_sizes = vec![1, 2, 5, 10, 20]; + let mut group: BenchmarkGroup<'_, _> = c.benchmark_group("prove_compressed"); + group.sampling_mode(SamplingMode::Flat); // This can take a *while* + group.sample_size(10); + let state = State::init_lurk_state().rccell(); + + for &n in batch_sizes.iter() { + for &reduction_count in reduction_counts.iter() { + let prove_params = ProveParams { + arity: 1, + n, + reduction_count, + }; + sha256_ivc_prove_compressed(prove_params, &mut group, state.clone()); + } + } +} + +cfg_if::cfg_if! { + if #[cfg(feature = "flamegraph")] { + criterion_group! { + name = benches; + config = Criterion::default() + .measurement_time(Duration::from_secs(120)) + .sample_size(10) + .with_profiler(pprof::criterion::PProfProfiler::new(100, pprof::criterion::Output::Flamegraph(None))); + targets = + prove_benchmarks, + prove_compressed_benchmarks + } + } else { + criterion_group! { + name = benches; + config = Criterion::default() + .measurement_time(Duration::from_secs(120)) + .sample_size(10); + targets = + prove_benchmarks, + prove_compressed_benchmarks + } + } +} + +criterion_main!(benches); diff --git a/benches/synthesis.rs b/benches/synthesis.rs index 0332d1eec5..0d203a8d27 100644 --- a/benches/synthesis.rs +++ b/benches/synthesis.rs @@ -1,6 +1,7 @@ -use std::{sync::Arc, time::Duration}; +use std::{cell::RefCell, rc::Rc, sync::Arc, time::Duration}; -use bellperson::{util_cs::test_cs::TestConstraintSystem, Circuit}; +use bellpepper::util_cs::witness_cs::WitnessCS; +use bellpepper_core::{Circuit, ConstraintSystem}; use criterion::{ black_box, criterion_group, criterion_main, measurement, BatchSize, BenchmarkGroup, BenchmarkId, Criterion, SamplingMode, @@ -14,12 +15,14 @@ use lurk::{ }, field::LurkField, proof::nova::NovaProver, + proof::supernova::FoldingConfig, proof::Prover, ptr::Ptr, + state::State, store::Store, }; -fn fib(store: &mut Store, a: u64) -> Ptr { +fn fib(store: &mut Store, state: Rc>, a: u64) -> Ptr { let program = format!( r#" (let ((fib (lambda (target) @@ -34,17 +37,18 @@ fn fib(store: &mut Store, a: u64) -> Ptr { "# ); - store.read(&program).unwrap() + store.read_with_state(state, &program).unwrap() } fn synthesize( name: &str, reduction_count: usize, - c: &mut BenchmarkGroup, + c: &mut BenchmarkGroup<'_, M>, ) { let limit = 1_000_000; let lang_pallas = Lang::>::new(); let lang_rc = Arc::new(lang_pallas.clone()); + let state = State::init_lurk_state().rccell(); c.bench_with_input( BenchmarkId::new(name.to_string(), reduction_count), @@ -53,20 +57,23 @@ fn synthesize( let mut store = Store::default(); let env = empty_sym_env(&store); let fib_n = (reduction_count / 3) as u64; // Heuristic, since one fib is 35 iterations. - let ptr = fib::(&mut store, black_box(fib_n)); + let ptr = fib::(&mut store, state.clone(), black_box(fib_n)); let prover = NovaProver::new(*reduction_count, lang_pallas.clone()); let frames = prover - .get_evaluation_frames(ptr, env, &mut store, limit, &lang_pallas) + .get_evaluation_frames(ptr, env, &mut store, limit, lang_rc.clone()) .unwrap(); + let folding_config = + Arc::new(FoldingConfig::new_ivc(lang_rc.clone(), *reduction_count)); let multiframe = - MultiFrame::from_frames(*reduction_count, &frames, &store, &lang_rc)[0].clone(); + MultiFrame::from_frames(*reduction_count, &frames, &store, folding_config)[0] + .clone(); b.iter_batched( || (multiframe.clone()), // avoid cloning the frames in the benchmark |multiframe| { - let mut cs = TestConstraintSystem::new(); + let mut cs = WitnessCS::new(); let result = multiframe.synthesize(&mut cs); let _ = black_box(result); }, @@ -78,7 +85,7 @@ fn synthesize( fn fibonacci_synthesize(c: &mut Criterion) { let batch_sizes = vec![5, 10, 100, 200]; - let mut group: BenchmarkGroup<_> = c.benchmark_group("synthesis"); + let mut group: BenchmarkGroup<'_, _> = c.benchmark_group("synthesis"); group.sampling_mode(SamplingMode::Flat); // This can take a *while* group.sample_size(10); diff --git a/build.rs b/build.rs new file mode 100644 index 0000000000..35aa44a6d8 --- /dev/null +++ b/build.rs @@ -0,0 +1,8 @@ +use std::error::Error; +use vergen::EmitBuilder; + +fn main() -> Result<(), Box> { + // Emit the instructions + EmitBuilder::builder().all_git().emit()?; + Ok(()) +} diff --git a/clutch/Cargo.toml b/clutch/Cargo.toml index 8997bc25c3..de1a557078 100644 --- a/clutch/Cargo.toml +++ b/clutch/Cargo.toml @@ -11,14 +11,17 @@ repository = "https://github.com/lurk-lab/lurk-rs" [dependencies] anyhow = { workspace = true } blstrs = { workspace = true } +camino = { workspace = true } clap = { workspace = true } fcomm = { path = "../fcomm" } ff = "0.13" lurk = { path = "../" } pasta_curves = { workspace = true, features = ["repr-c", "serde"] } -pretty_env_logger = "0.4" serde = { workspace = true, features = ["derive"] } -camino = { workspace = true } +tracing = { workspace = true } +tracing-texray = { workspace = true } +tracing-subscriber = { workspace = true } + [dev-dependencies] assert_cmd = "2.0.12" diff --git a/clutch/src/lib.rs b/clutch/src/lib.rs index 133eda5e01..37b5a31e9a 100644 --- a/clutch/src/lib.rs +++ b/clutch/src/lib.rs @@ -8,8 +8,10 @@ use fcomm::{ CommittedExpression, CommittedExpressionMap, LurkCont, LurkPtr, NovaProofCache, Opening, Proof, PtrEvaluation, }; +use lurk::lurk_sym_ptr; use lurk::public_parameters::public_params; +use lurk::state::State; use pasta_curves::pallas; use lurk::coprocessor::Coprocessor; @@ -27,9 +29,11 @@ use lurk::symbol::Symbol; use lurk::tag::ExprTag; use lurk::writer::Write; +use std::cell::RefCell; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; +use std::rc::Rc; use std::sync::Arc; use std::thread; @@ -143,7 +147,7 @@ impl ReplTrait> for ClutchState> { let lang_rc = Arc::new(lang.clone()); // Load params from disk cache, or generate them in the background. - thread::spawn(move || public_params(reduction_count, lang_rc, &public_param_dir())); + thread::spawn(move || public_params(reduction_count, true, lang_rc, &public_param_dir())); Self { repl_state: ReplState::new(s, limit, command, lang), @@ -199,6 +203,7 @@ impl ReplTrait> for ClutchState> { fn handle_meta + Copy>( &mut self, store: &mut Store, + state: Rc>, expr_ptr: Ptr, p: P, ) -> Result<()> { @@ -206,7 +211,7 @@ impl ReplTrait> for ClutchState> { macro_rules! delegate { () => { - self.repl_state.handle_meta(store, expr_ptr, p) + self.repl_state.handle_meta(store, state, expr_ptr, p) }; } @@ -216,13 +221,13 @@ impl ReplTrait> for ClutchState> { let s: Symbol = store .fetch_sym(&car) .ok_or(Error::msg("handle_meta fetch symbol"))?; - match format!("{}", s).as_str() { - "call" => self.call(store, rest)?, - "chain" => self.chain(store, rest)?, - "lurk.commit" => self.commit(store, rest)?, - "lurk.open" => self.open(store, rest)?, - "proof-in-expr" => self.proof_in_expr(store, rest)?, - "proof-out-expr" => self.proof_out_expr(store, rest)?, + match s.name()? { + "call" => self.call(store, &state.borrow(), rest)?, + "chain" => self.chain(store, &state.borrow(), rest)?, + "commit" => self.commit(store, rest)?, + "open" => self.open(store, rest)?, + "proof-in-expr" => self.proof_in_expr(store, &state.borrow(), rest)?, + "proof-out-expr" => self.proof_out_expr(store, &state.borrow(), rest)?, "proof-claim" => self.proof_claim(store, rest)?, "prove" => self.prove(store, rest)?, "verify" => self.verify(store, rest)?, @@ -231,11 +236,11 @@ impl ReplTrait> for ClutchState> { } Expression::Comm(_, c) => { // NOTE: this cannot happen from a text-based REPL, since there is not currrently a literal Comm syntax. - self.apply_comm(store, *c, rest)? + self.apply_comm(store, &state.borrow(), *c, rest)? } Expression::Num(c) => { let comm = store.intern_num(*c); - self.apply_comm(store, comm, rest)? + self.apply_comm(store, &state.borrow(), comm, rest)? } _ => return delegate!(), }, @@ -248,7 +253,7 @@ impl ReplTrait> for ClutchState> { if let Some(expr) = res { let mut handle = io::stdout().lock(); - expr.fmt(store, &mut handle)?; + expr.fmt(store, &state.borrow(), &mut handle)?; println!(); }; Ok(()) @@ -257,9 +262,11 @@ impl ReplTrait> for ClutchState> { fn handle_non_meta( &mut self, store: &mut Store, + state: &State, expr_ptr: Ptr, ) -> Result<(IO, IO, usize)> { - let (input, output, iterations) = self.repl_state.handle_non_meta(store, expr_ptr)?; + let (input, output, iterations) = + self.repl_state.handle_non_meta(store, state, expr_ptr)?; self.history.push(output); @@ -329,18 +336,29 @@ impl ClutchState> { fn apply_comm( &mut self, store: &mut Store, + state: &State, comm: Ptr, rest: Ptr, ) -> Result>> { let call_form = store.cons(comm, rest); - self.apply_comm_aux(store, call_form, false) + self.apply_comm_aux(store, state, call_form, false) } - fn call(&mut self, store: &mut Store, rest: Ptr) -> Result>> { - self.apply_comm_aux(store, rest, false) + fn call( + &mut self, + store: &mut Store, + state: &State, + rest: Ptr, + ) -> Result>> { + self.apply_comm_aux(store, state, rest, false) } - fn chain(&mut self, store: &mut Store, rest: Ptr) -> Result>> { + fn chain( + &mut self, + store: &mut Store, + state: &State, + rest: Ptr, + ) -> Result>> { let (result_expr, new_comm) = self - .apply_comm_aux(store, rest, true)? + .apply_comm_aux(store, state, rest, true)? .and_then(|cons| store.car_cdr(&cons).ok()) // unpack pair .ok_or_else(|| { anyhow!( @@ -376,7 +394,7 @@ impl ClutchState> { let interned_commitment = store.intern_maybe_opaque_comm(new_commitment.comm); let mut handle = io::stdout().lock(); - interned_commitment.fmt(store, &mut handle)?; + interned_commitment.fmt(store, state, &mut handle)?; println!(); Ok(Some(result_expr)) @@ -384,6 +402,7 @@ impl ClutchState> { fn apply_comm_aux( &mut self, store: &mut Store, + state: &State, rest: Ptr, chain: bool, ) -> Result>> { @@ -407,8 +426,8 @@ impl ClutchState> { }; let claim = Claim::Opening::(Opening { - input: arg.fmt_to_string(store), - output: output.fmt_to_string(store), + input: arg.fmt_to_string(store, state), + output: output.fmt_to_string(store, state), status: Status::from(cont), commitment, new_commitment, @@ -463,32 +482,42 @@ impl ClutchState> { Ok((commitment, commitment_ptr)) } - fn proof_in_expr(&self, store: &mut Store, rest: Ptr) -> Result>> { + fn proof_in_expr( + &self, + store: &mut Store, + state: &State, + rest: Ptr, + ) -> Result>> { let proof = self.get_proof(store, rest)?; let (input, _output) = proof.io(store, &self.lang())?; let mut handle = io::stdout().lock(); - input.expr.fmt(store, &mut handle)?; + input.expr.fmt(store, state, &mut handle)?; println!(); Ok(None) } - fn proof_out_expr(&self, store: &mut Store, rest: Ptr) -> Result>> { + fn proof_out_expr( + &self, + store: &mut Store, + state: &State, + rest: Ptr, + ) -> Result>> { let proof = self.get_proof(store, rest)?; let (_input, output) = proof.io(store, &self.lang())?; let mut handle = io::stdout().lock(); - output.expr.fmt(store, &mut handle)?; + output.expr.fmt(store, state, &mut handle)?; println!(); Ok(None) } - fn proof_claim(&self, store: &mut Store, rest: Ptr) -> Result>> { + fn proof_claim(&self, store: &Store, rest: Ptr) -> Result>> { let proof = self.get_proof(store, rest)?; println!("{0:#?}", proof.claim); Ok(None) } - fn get_proof(&self, store: &mut Store, rest: Ptr) -> Result> { + fn get_proof(&self, store: &Store, rest: Ptr) -> Result> { let (proof_cid, _rest1) = store.car_cdr(&rest)?; let zptr_string = store .fetch_string(&proof_cid) @@ -503,7 +532,7 @@ impl ClutchState> { let (proof_in_expr, _rest1) = store.car_cdr(&rest)?; let prover = NovaProver::>::new(self.reduction_count, (*self.lang()).clone()); - let pp = public_params(self.reduction_count, self.lang(), &public_param_dir())?; + let pp = public_params(self.reduction_count, true, self.lang(), &public_param_dir())?; let proof = if rest.is_nil() { self.last_claim @@ -537,20 +566,20 @@ impl ClutchState> { let zptr_string = proof .claim .proof_key() - .map_err(|_| Error::msg("Failed to generate proof key"))? + .map_err(|e| Error::msg(format!("Failed to generate proof key: {}", e)))? .to_base32(); match proof.claim { Claim::Evaluation(_) | Claim::Opening(_) => println!("{0:#?}", proof.claim), Claim::PtrEvaluation(_) => println!("Claim::PtrEvaluation elided."), } - let zid = store.str(zptr_string); + let zid = store.str(&zptr_string); Ok(Some(zid)) } else { bail!("verification of new proof failed"); } } - fn verify(&mut self, store: &mut Store, rest: Ptr) -> Result>> { + fn verify(&mut self, store: &Store, rest: Ptr) -> Result>> { let (proof_cid, _) = store.car_cdr(&rest)?; let zptr_string = store @@ -562,13 +591,13 @@ impl ClutchState> { .get(&zptr_string) .ok_or_else(|| anyhow!("proof not found: {zptr_string}"))?; - let pp = public_params(self.reduction_count, self.lang(), &public_param_dir())?; - let result = proof.verify(&pp, &self.lang()).unwrap(); + let pp = public_params(self.reduction_count, true, self.lang(), &public_param_dir())?; + let result = proof.verify(&pp, &self.lang())?; if result.verified { - Ok(Some(store.get_t())) + Ok(Some(lurk_sym_ptr!(store, t))) } else { - Ok(Some(store.get_nil())) + Ok(Some(lurk_sym_ptr!(store, nil))) } } } diff --git a/clutch/src/main.rs b/clutch/src/main.rs index fb575f2a63..d64330565e 100644 --- a/clutch/src/main.rs +++ b/clutch/src/main.rs @@ -6,20 +6,21 @@ use lurk::eval::lang::{Coproc, Lang}; use lurk::field::LanguageField; use lurk::repl::repl_cli; use pasta_curves::pallas; +use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; fn main() -> Result<()> { - pretty_env_logger::init(); + let subscriber = Registry::default() + // TODO: correctly filter log level with `clap_verbosity_flag` + .with(fmt::layer().pretty()) + .with(EnvFilter::from_default_env()); + tracing::subscriber::set_global_default(subscriber).unwrap(); let default_field = LanguageField::Pallas; - let field = if let Ok(lurk_field) = std::env::var("LURK_FIELD") { - match lurk_field.as_str() { - "BLS12-381" => LanguageField::BLS12_381, - "PALLAS" => LanguageField::Pallas, - "VESTA" => LanguageField::Vesta, - _ => default_field, - } - } else { - default_field + let field = match std::env::var("LURK_FIELD").as_deref() { + Ok("BLS12-381") => LanguageField::BLS12_381, + Ok("PALLAS") => LanguageField::Pallas, + Ok("VESTA") => LanguageField::Vesta, + _ => default_field, }; match field { diff --git a/deny.toml b/deny.toml index c9eadaf201..93b7800052 100644 --- a/deny.toml +++ b/deny.toml @@ -104,10 +104,12 @@ unlicensed = "deny" allow = [ "MIT", "BSL-1.0", + "0BSD", "BSD-2-Clause", "BSD-3-Clause", "CC0-1.0", "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", "Unicode-DFS-2016", "ISC", ] diff --git a/examples/circom.rs b/examples/circom.rs new file mode 100644 index 0000000000..74b8ab477e --- /dev/null +++ b/examples/circom.rs @@ -0,0 +1,158 @@ +//! # Circom Gadgets +//! +//! ## Setting up a Circom Gadget with Lurk +//! +//! First, in a separate directory from `lurk-rs`, clone the circomlib repo with the `Sha256_2` circuit. +//! ``` +//! git clone git@github.com:iden3/circomlib.git && cd circomlib +//! ``` +//! +//! Now return to the `lurk-rs` directory and run the following commands +//! ``` +//! cargo run --release -- circom --name sha256_2_test /test/circuits/ +//! cargo run --release --example circom +//! ``` +//! +//! This compiles the circom project and processes it for lurk to interface with. +//! The new `sha256_2_test` gadget is stored in `/sha256_2_test/*`. +//! To use the gadget, create a [CircomSha256] struct and implement the [CircomGadget] trait. +//! Refer to the example code below. Finally, declare the sha256 coprocessor: +//! +//! ```rust +//! #[derive(Clone, Debug, Coproc)] +//! enum Sha256Coproc { +//! SC(CircomCoprocessor>), +//! } +//! ``` +//! +//! Hooray! Now we can use a [CircomSha256] coprocessor just like a normal one. + +use std::fmt::Debug; +use std::marker::PhantomData; +use std::sync::Arc; +use std::time::Instant; + +use lurk::circuit::gadgets::circom::CircomGadget; +use lurk::circuit::gadgets::pointer::AllocatedPtr; + +#[cfg(not(target_arch = "wasm32"))] +use lurk::coprocessor::circom::non_wasm::CircomCoprocessor; + +use lurk::eval::{empty_sym_env, lang::Lang}; +use lurk::field::LurkField; +use lurk::proof::{nova::NovaProver, Prover}; +use lurk::ptr::Ptr; +use lurk::public_parameters::{public_params, public_params_default_dir}; +use lurk::store::Store; +use lurk::{Num, Symbol}; +use lurk_macros::Coproc; +use pasta_curves::pallas::Scalar as Fr; + +const REDUCTION_COUNT: usize = 1; + +#[derive(Debug, Clone)] +pub struct CircomSha256 { + _n: usize, + pub(crate) _p: PhantomData, +} + +impl CircomSha256 { + fn new(n: usize) -> Self { + CircomSha256 { + _n: n, + _p: PhantomData, + } + } +} + +impl CircomGadget for CircomSha256 { + fn name(&self) -> &str { + "sha256_2_test" + } + + fn into_circom_input(self, _input: &[AllocatedPtr]) -> Vec<(String, Vec)> { + // TODO: actually use the lurk inputs + let a = ("a".into(), vec![F::ZERO]); + let b = ("b".into(), vec![F::ZERO]); + vec![a, b] + } + + fn simple_evaluate(&self, s: &mut Store, _args: &[Ptr]) -> Ptr { + // TODO: actually use the lurk inputs + let expected = Num::Scalar( + F::from_str_vartime( + "55165702627807990590530466439275329993482327026534454077267643456", + ) + .unwrap(), + ); + s.intern_num(expected) + } +} + +#[derive(Clone, Debug, Coproc)] +enum Sha256Coproc { + SC(CircomCoprocessor>), +} + +/// Run the example in this file with +/// `cargo run --release -- circom --name sha256_2 examples/sha256/` +/// `cargo run --release --example circom` +fn main() { + let store = &mut Store::::new(); + let sym_str = Symbol::new(&[".circom_sha256_2"], false); // two inputs + let circom_sha256 = CircomSha256::new(0); + let lang = Lang::>::new_with_bindings( + store, + vec![( + sym_str.clone(), + CircomCoprocessor::new(circom_sha256).into(), + )], + ); + + let coproc_expr = format!("{sym_str}"); + + let expr = format!("({coproc_expr})"); + let ptr = store.read(&expr).unwrap(); + + let nova_prover = NovaProver::>::new(REDUCTION_COUNT, lang.clone()); + let lang_rc = Arc::new(lang); + + println!("Setting up public parameters..."); + + let pp_start = Instant::now(); + let pp = public_params::<_, Sha256Coproc>( + REDUCTION_COUNT, + true, + lang_rc.clone(), + &public_params_default_dir(), + ) + .unwrap(); + let pp_end = pp_start.elapsed(); + + println!("Public parameters took {pp_end:?}"); + + println!("Beginning proof step..."); + + let proof_start = Instant::now(); + let (proof, z0, zi, num_steps) = nova_prover + .evaluate_and_prove(&pp, ptr, empty_sym_env(store), store, 10000, lang_rc) + .unwrap(); + let proof_end = proof_start.elapsed(); + + println!("Proofs took {proof_end:?}"); + + println!("Verifying proof..."); + + let verify_start = Instant::now(); + let res = proof.verify(&pp, num_steps, &z0, &zi).unwrap(); + let verify_end = verify_start.elapsed(); + + println!("Verify took {verify_end:?}"); + + if res { + println!( + "Congratulations! You proved and verified a CIRCOM-SHA256 hash calculation in {:?} time!", + pp_end + proof_end + verify_end + ); + } +} diff --git a/examples/fibonacci.rs b/examples/fibonacci.rs new file mode 100644 index 0000000000..256274a186 --- /dev/null +++ b/examples/fibonacci.rs @@ -0,0 +1,76 @@ +use lurk::field::LurkField; +use lurk::ptr::Ptr; +use lurk::store::Store; +use lurk::writer::Write; +use lurk::{ + eval::{ + empty_sym_env, + lang::{Coproc, Lang}, + Evaluator, + }, + state::State, +}; +use pasta_curves::pallas::Scalar; + +fn fib_expr(store: &mut Store) -> Ptr { + let program = r#" +(letrec ((next (lambda (a b) (next b (+ a b)))) + (fib (next 0 1))) + (fib)) +"#; + + store.read(program).unwrap() +} + +// The env output in the `fib_frame`th frame of the above, infinite Fibonacci computation contains a binding of the +// nth Fibonacci number to `a`. +fn fib_frame(n: usize) -> usize { + 11 + 16 * n +} + +// Set the limit so the last step will be filled exactly, since Lurk currently only pads terminal/error continuations. +#[allow(dead_code)] +fn fib_limit(n: usize, rc: usize) -> usize { + let frame = fib_frame(n); + rc * (frame / rc + usize::from(frame % rc != 0)) +} + +fn lurk_fib(store: &mut Store, n: usize, _rc: usize) -> Ptr { + let lang = Lang::>::new(); + let frame_idx = fib_frame(n); + // let limit = fib_limit(n, rc); + let limit = frame_idx; + let fib_expr = fib_expr(store); + + let frames = Evaluator::new(fib_expr, empty_sym_env(store), store, limit, &lang) + .get_frames() + .unwrap(); + + let target_frame = frames.last().unwrap(); + + let target_env = target_frame.output.env; + + // The result is the value of the second binding (of `A`), in the target env. + // See relevant excerpt of execution trace below: + // + // INFO lurk::eval > Frame: 11 + // Expr: (NEXT B (+ A B)) + // Env: ((B . 1) (A . 0) ((NEXT . ))) + // Cont: Tail{ saved_env: (((NEXT . ))), continuation: LetRec{var: FIB, + // saved_env: (((NEXT . ))), body: (FIB), continuation: Tail{ saved_env: + // NIL, continuation: Outermost } } } + + let rest_bindings = store.cdr(&target_env).unwrap(); + let second_binding = store.car(&rest_bindings).unwrap(); + store.cdr(&second_binding).unwrap() +} + +fn main() { + let store = &mut Store::::new(); + let n: usize = std::env::args().collect::>()[1].parse().unwrap(); + let state = State::init_lurk_state(); + + let fib = lurk_fib(store, n, 100); + + println!("Fib({n}) = {}", fib.fmt_to_string(store, &state)); +} diff --git a/examples/itcalc.rs b/examples/itcalc.rs new file mode 100644 index 0000000000..c20060a74b --- /dev/null +++ b/examples/itcalc.rs @@ -0,0 +1,86 @@ +use ascii_table::AsciiTable; + +#[derive(Debug, Clone, Copy)] +struct Prog { + setup_iterations: usize, + loop_iterations: usize, +} + +fn real_iterations(prog: Prog, n: usize) -> usize { + prog.setup_iterations + prog.loop_iterations * n +} + +fn ceiling(n: usize, m: usize) -> usize { + n / m + usize::from(n % m != 0) +} + +enum Opt { + Some(T), + None, + Empty, +} + +impl core::fmt::Display for Opt { + fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Opt::None => "-".fmt(fmt), + Opt::Some(x) => x.fmt(fmt), + Opt::Empty => "".fmt(fmt), + } + } +} + +fn total_iterations(real_iterations: usize, rc: usize) -> Opt { + let steps = ceiling(real_iterations, rc); + let total_iterations = steps * rc; + + if real_iterations < rc { + Opt::None + } else { + Opt::Some(total_iterations) + } +} +fn rc_total_iterations(prog: Prog, n: usize, rc: usize) -> Opt { + let real_iterations = real_iterations(prog, n); + total_iterations(real_iterations, rc) +} + +fn analyze_rcs(prog: Prog, n: usize, rcs: &[usize]) -> Vec> { + let mut analysis = Vec::with_capacity(rcs.len() + 2); + analysis.push(Opt::Some(n)); + analysis.push(Opt::Empty); + analysis.extend(rcs.iter().map(|rc| rc_total_iterations(prog, n, *rc))); + analysis +} + +fn analyze_ncs_rcs(prog: Prog, ns: &[usize], rcs: &[usize]) -> Vec>> { + ns.iter().map(|n| analyze_rcs(prog, *n, rcs)).collect() +} + +/// Produces a table of 'real Lurk iterations' proved per loop-iteration/rc combination. +/// If the program has fewer real iterations than rc, no value is produced. +/// Otherwise, the number of total iterations (including padding) is used. +fn main() { + let args = std::env::args().collect::>(); + + let setup_iterations: usize = args[1].parse().unwrap(); + let loop_iterations: usize = args[2].parse().unwrap(); + let ns = [10, 20, 30, 40, 50, 60, 100, 200]; + let rcs = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]; + + let prog = Prog { + setup_iterations, + loop_iterations, + }; + let analysis = analyze_ncs_rcs(prog, &ns, &rcs); + let mut table = AsciiTable::default(); + + table.column(0).set_header("n"); + table.column(1).set_header("rc"); + for (i, rc) in rcs.into_iter().enumerate() { + table.column(i + 2).set_header(rc.to_string()); + } + + println!("\nSetup iterations: {setup_iterations}; Iterations per loop: {loop_iterations}."); + table.print(analysis); +} diff --git a/examples/sha256.rs b/examples/sha256.rs index d45a5de663..52bf2d1667 100644 --- a/examples/sha256.rs +++ b/examples/sha256.rs @@ -12,8 +12,8 @@ use lurk::field::LurkField; use lurk::proof::{nova::NovaProver, Prover}; use lurk::ptr::Ptr; use lurk::public_parameters::{public_params, public_params_default_dir}; +use lurk::state::user_sym; use lurk::store::Store; -use lurk::sym; use lurk_macros::Coproc; use bellperson::gadgets::boolean::{AllocatedBit, Boolean}; @@ -25,8 +25,10 @@ use bellperson::{ConstraintSystem, SynthesisError}; use pasta_curves::pallas::Scalar as Fr; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; +use tracing_texray::TeXRayLayer; -const REDUCTION_COUNT: usize = 10; +const REDUCTION_COUNT: usize = 2; #[derive(Clone, Debug, Serialize, Deserialize)] pub(crate) struct Sha256Coprocessor { @@ -82,8 +84,7 @@ impl CoCircuit for Sha256Coprocessor { let num = allocate_constant( &mut cs.namespace(|| format!("allocate result {i}")), F::from_u128(self.expected[i]), - ) - .unwrap(); + ); let eq = alloc_equal( cs.namespace(|| format!("equate numbers {i}")), @@ -152,8 +153,17 @@ enum Sha256Coproc { /// Run the example in this file with /// `cargo run --release --example sha256 1 f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b false` +/// +/// To see the `texray` outputs, run with +/// ` +/// `RUST_LOG=info cargo run --release --example sha256 1 f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b false` fn main() { - pretty_env_logger::init(); + let subscriber = Registry::default() + .with(fmt::layer().pretty()) + .with(EnvFilter::from_default_env()) + .with(TeXRayLayer::new()); + tracing::subscriber::set_global_default(subscriber).unwrap(); + let args: Vec = env::args().collect(); let num_of_64_bytes = args[1].parse::().unwrap(); @@ -170,18 +180,15 @@ fn main() { u.reverse(); let store = &mut Store::::new(); - let sym_str = sym!("sha256", format!("{}-zero-bytes", input_size)); + let cproc_sym = user_sym(&format!("sha256-{input_size}-zero-bytes")); + let cproc_sym_ptr = store.intern_symbol(&cproc_sym); let lang = Lang::>::new_with_bindings( store, - vec![( - sym_str.clone(), - Sha256Coprocessor::new(input_size, u).into(), - )], + vec![(cproc_sym, Sha256Coprocessor::new(input_size, u).into())], ); - let coproc_expr = format!("({})", sym_str); - let ptr = store.read(&coproc_expr).unwrap(); + let cproc_call = store.list(&[cproc_sym_ptr]); let nova_prover = NovaProver::>::new(REDUCTION_COUNT, lang.clone()); let lang_rc = Arc::new(lang); @@ -191,13 +198,14 @@ fn main() { let pp_start = Instant::now(); let pp = public_params::<_, Sha256Coproc>( REDUCTION_COUNT, + true, lang_rc.clone(), &public_params_default_dir(), ) .unwrap(); let pp_end = pp_start.elapsed(); - println!("Public parameters took {:?}", pp_end); + println!("Public parameters took {pp_end:?}"); if setup_only { return; @@ -206,12 +214,15 @@ fn main() { println!("Beginning proof step..."); let proof_start = Instant::now(); - let (proof, z0, zi, num_steps) = nova_prover - .evaluate_and_prove(&pp, ptr, empty_sym_env(store), store, 10000, lang_rc) - .unwrap(); + let (proof, z0, zi, num_steps) = tracing_texray::examine(tracing::info_span!("prog_start")) + .in_scope(|| { + nova_prover + .evaluate_and_prove(&pp, cproc_call, empty_sym_env(store), store, 10000, lang_rc) + .unwrap() + }); let proof_end = proof_start.elapsed(); - println!("Proofs took {:?}", proof_end); + println!("Proofs took {proof_end:?}"); println!("Verifying proof..."); @@ -219,7 +230,7 @@ fn main() { let res = proof.verify(&pp, num_steps, &z0, &zi).unwrap(); let verify_end = verify_start.elapsed(); - println!("Verify took {:?}", verify_end); + println!("Verify took {verify_end:?}"); if res { println!( diff --git a/examples/sha256_ivc.rs b/examples/sha256_ivc.rs new file mode 100644 index 0000000000..1edf391512 --- /dev/null +++ b/examples/sha256_ivc.rs @@ -0,0 +1,232 @@ +use std::marker::PhantomData; +use std::sync::Arc; +use std::time::Instant; + +use lurk::circuit::gadgets::data::GlobalAllocations; +use lurk::circuit::gadgets::pointer::{AllocatedContPtr, AllocatedPtr}; +use lurk::coprocessor::{CoCircuit, Coprocessor}; +use lurk::eval::{empty_sym_env, lang::Lang}; +use lurk::field::LurkField; +use lurk::proof::{nova::NovaProver, Prover}; +use lurk::ptr::Ptr; +use lurk::public_parameters::with_public_params; +use lurk::state::user_sym; +use lurk::store::Store; +use lurk::tag::{ExprTag, Tag}; +use lurk::Num; +use lurk_macros::Coproc; + +use bellpepper::gadgets::multipack::pack_bits; +use bellpepper::gadgets::sha256::sha256; +use bellpepper_core::boolean::Boolean; +use bellpepper_core::{ConstraintSystem, SynthesisError}; + +use pasta_curves::pallas::Scalar as Fr; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; +use tracing_texray::TeXRayLayer; + +const REDUCTION_COUNT: usize = 10; + +fn sha256_ivc(store: &mut Store, n: usize, input: Vec) -> Ptr { + assert_eq!(n, input.len()); + let input = input + .iter() + .map(|i| i.to_string()) + .collect::>() + .join(" "); + let input = format!("'({input})"); + let program = format!( + r#" +(letrec ((encode-1 (lambda (term) + (let ((type (car term)) + (value (cdr term))) + (if (eq 'sha256 type) + (eval (cons 'sha256_ivc_{n} value)) + (if (eq 'lurk type) + (commit value) + (if (eq 'id type) + value)))))) + (encode (lambda (input) + (if input + (cons + (encode-1 (car input)) + (encode (cdr input))))))) + (encode '((sha256 . {input}) (lurk . 5) (id . 15)))) +"# + ); + + store.read(&program).unwrap() +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct Sha256Coprocessor { + n: usize, + pub(crate) _p: PhantomData, +} + +impl CoCircuit for Sha256Coprocessor { + fn arity(&self) -> usize { + self.n + } + + fn synthesize>( + &self, + cs: &mut CS, + _g: &GlobalAllocations, + _store: &Store, + input_exprs: &[AllocatedPtr], + input_env: &AllocatedPtr, + input_cont: &AllocatedContPtr, + ) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedContPtr), SynthesisError> { + let zero = Boolean::constant(false); + + let mut bits = vec![]; + + // println!("{:?}", input_exprs); + + for input_ptr in input_exprs { + let tag_bits = input_ptr + .tag() + .to_bits_le_strict(&mut cs.namespace(|| "preimage_tag_bits"))?; + let hash_bits = input_ptr + .hash() + .to_bits_le_strict(&mut cs.namespace(|| "preimage_hash_bits"))?; + + bits.extend(tag_bits); + bits.push(zero.clone()); // need 256 bits (or some multiple of 8). + bits.extend(hash_bits); + bits.push(zero.clone()); // need 256 bits (or some multiple of 8). + } + + bits.reverse(); + + let mut digest_bits = sha256(cs.namespace(|| "digest_bits"), &bits)?; + + digest_bits.reverse(); + + // Fine to lose the last <1 bit of precision. + let digest_scalar = pack_bits(cs.namespace(|| "digest_scalar"), &digest_bits)?; + let output_expr = AllocatedPtr::alloc_tag( + &mut cs.namespace(|| "output_expr"), + ExprTag::Num.to_field(), + digest_scalar, + )?; + Ok((output_expr, input_env.clone(), input_cont.clone())) + } +} + +impl Coprocessor for Sha256Coprocessor { + fn eval_arity(&self) -> usize { + self.n + } + + fn simple_evaluate(&self, s: &mut Store, args: &[Ptr]) -> Ptr { + let mut hasher = Sha256::new(); + + let mut input = vec![0u8; 64 * self.n]; + + for (i, input_ptr) in args.iter().enumerate() { + let input_zptr = s.hash_expr(input_ptr).unwrap(); + let tag_zptr: F = input_zptr.tag().to_field(); + let hash_zptr = input_zptr.value(); + input[(64 * i)..(64 * i + 32)].copy_from_slice(&tag_zptr.to_bytes()); + input[(64 * i + 32)..(64 * (i + 1))].copy_from_slice(&hash_zptr.to_bytes()); + } + + input.reverse(); + + hasher.update(input); + let mut bytes = hasher.finalize(); + bytes.reverse(); + let l = bytes.len(); + // Discard the two most significant bits. + bytes[l - 1] &= 0b00111111; + + let scalar = F::from_bytes(&bytes).unwrap(); + let result = Num::from_scalar(scalar); + + s.intern_num(result) + } + + fn has_circuit(&self) -> bool { + true + } +} + +impl Sha256Coprocessor { + pub(crate) fn new(n: usize) -> Self { + Self { + n, + _p: Default::default(), + } + } +} + +#[derive(Clone, Debug, Coproc, Serialize, Deserialize)] +enum Sha256Coproc { + SC(Sha256Coprocessor), +} + +/// Run the example in this file with +/// `cargo run --release --example sha256_ivc ` +/// where `n` is the needed arity +fn main() { + let subscriber = Registry::default() + .with(fmt::layer().pretty()) + .with(EnvFilter::from_default_env()) + .with(TeXRayLayer::new()); + tracing::subscriber::set_global_default(subscriber).unwrap(); + + let args = std::env::args().collect::>(); + let n = args[1].parse().unwrap(); + + let store = &mut Store::::new(); + let cproc_sym = user_sym(&format!("sha256_ivc_{n}")); + + let call = sha256_ivc(store, n, (0..n).collect()); + + let lang = Lang::>::new_with_bindings( + store, + vec![(cproc_sym, Sha256Coprocessor::new(n).into())], + ); + let lang_rc = Arc::new(lang.clone()); + + let nova_prover = NovaProver::>::new(REDUCTION_COUNT, lang); + + println!("Setting up public parameters (rc = {REDUCTION_COUNT})..."); + + let pp_start = Instant::now(); + + // see the documentation on `with_public_params` + with_public_params(REDUCTION_COUNT, lang_rc.clone(), |pp| { + let pp_end = pp_start.elapsed(); + println!("Public parameters took {:?}", pp_end); + + println!("Beginning proof step..."); + let proof_start = Instant::now(); + let (proof, z0, zi, num_steps) = nova_prover + .evaluate_and_prove(pp, call, empty_sym_env(store), store, 10000, lang_rc) + .unwrap(); + let proof_end = proof_start.elapsed(); + + println!("Proofs took {:?}", proof_end); + + println!("Verifying proof..."); + + let verify_start = Instant::now(); + let res = proof.verify(pp, num_steps, &z0, &zi).unwrap(); + let verify_end = verify_start.elapsed(); + + println!("Verify took {:?}", verify_end); + + if res { + println!( + "Congratulations! You proved and verified a SHA256 hash calculation in {:?} time!", + pp_end + proof_end + verify_end + ); + } + }) + .unwrap(); +} diff --git a/examples/sha256_nivc.rs b/examples/sha256_nivc.rs new file mode 100644 index 0000000000..2ed65c01ea --- /dev/null +++ b/examples/sha256_nivc.rs @@ -0,0 +1,222 @@ +use std::marker::PhantomData; +use std::sync::Arc; +use std::time::Instant; + +use lurk::circuit::gadgets::data::GlobalAllocations; +use lurk::circuit::gadgets::pointer::{AllocatedContPtr, AllocatedPtr}; +use lurk::coprocessor::{CoCircuit, Coprocessor}; +use lurk::eval::{empty_sym_env, lang::Lang}; +use lurk::field::LurkField; +use lurk::proof::{supernova::SuperNovaProver, Prover}; +use lurk::ptr::Ptr; + +use lurk::state::user_sym; +use lurk::store::Store; +use lurk::tag::{ExprTag, Tag}; +use lurk::Num; +use lurk_macros::Coproc; + +use bellpepper::gadgets::multipack::pack_bits; +use bellpepper::gadgets::sha256::sha256; +use bellpepper_core::boolean::Boolean; +use bellpepper_core::{ConstraintSystem, SynthesisError}; + +use pasta_curves::pallas::Scalar as Fr; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; +use tracing_texray::TeXRayLayer; + +const REDUCTION_COUNT: usize = 10; + +fn sha256_encode(store: &mut Store) -> Ptr { + let program = r#" +(letrec ((encode-1 (lambda (term) + (let ((type (car term)) + (value (cdr term))) + (if (eq 'sha256 type) + (eval (cons 'sha256 (cons value nil))) + (if (eq 'lurk type) + (commit value) + (if (eq 'id type) + value)))))) + (encode (lambda (input) + (if input + (cons + (encode-1 (car input)) + (encode (cdr input))))))) + (encode '((sha256 . 123) (lurk . 5) (id . 15)))) +"# + .to_string(); + + store.read(&program).unwrap() +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct Sha256Coprocessor { + pub(crate) _p: PhantomData, +} + +impl CoCircuit for Sha256Coprocessor { + fn arity(&self) -> usize { + 1 + } + + fn synthesize>( + &self, + cs: &mut CS, + _g: &GlobalAllocations, + _store: &Store, + input_exprs: &[AllocatedPtr], + input_env: &AllocatedPtr, + input_cont: &AllocatedContPtr, + ) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedContPtr), SynthesisError> { + let zero = Boolean::constant(false); + + let mut bits = vec![]; + + // println!("{:?}", input_exprs); + + for input_ptr in input_exprs { + let tag_bits = input_ptr + .tag() + .to_bits_le_strict(&mut cs.namespace(|| "preimage_tag_bits"))?; + let hash_bits = input_ptr + .hash() + .to_bits_le_strict(&mut cs.namespace(|| "preimage_hash_bits"))?; + + bits.extend(tag_bits); + bits.push(zero.clone()); // need 256 bits (or some multiple of 8). + bits.extend(hash_bits); + bits.push(zero.clone()); // need 256 bits (or some multiple of 8). + } + + bits.reverse(); + + let mut digest_bits = sha256(cs.namespace(|| "digest_bits"), &bits)?; + + digest_bits.reverse(); + + // Fine to lose the last <1 bit of precision. + let digest_scalar = pack_bits(cs.namespace(|| "digest_scalar"), &digest_bits)?; + let output_expr = AllocatedPtr::alloc_tag( + &mut cs.namespace(|| "output_expr"), + ExprTag::Num.to_field(), + digest_scalar, + )?; + Ok((output_expr, input_env.clone(), input_cont.clone())) + } +} + +impl Coprocessor for Sha256Coprocessor { + fn eval_arity(&self) -> usize { + 1 + } + + fn simple_evaluate(&self, s: &mut Store, args: &[Ptr]) -> Ptr { + let mut hasher = Sha256::new(); + + let mut input = vec![0u8; 64]; + + for (i, input_ptr) in args.iter().enumerate() { + let input_zptr = s.hash_expr(input_ptr).unwrap(); + let tag_zptr: F = input_zptr.tag().to_field(); + let hash_zptr = input_zptr.value(); + input[(64 * i)..(64 * i + 32)].copy_from_slice(&tag_zptr.to_bytes()); + input[(64 * i + 32)..(64 * (i + 1))].copy_from_slice(&hash_zptr.to_bytes()); + } + + input.reverse(); + + hasher.update(input); + let mut bytes = hasher.finalize(); + bytes.reverse(); + let l = bytes.len(); + // Discard the two most significant bits. + bytes[l - 1] &= 0b00111111; + + let scalar = F::from_bytes(&bytes).unwrap(); + let result = Num::from_scalar(scalar); + + s.intern_num(result) + } + + fn has_circuit(&self) -> bool { + true + } +} + +impl Sha256Coprocessor { + pub(crate) fn new() -> Self { + Self { + _p: Default::default(), + } + } +} + +#[derive(Clone, Debug, Coproc, Serialize, Deserialize)] +enum Sha256Coproc { + SC(Sha256Coprocessor), +} + +/// Run the example in this file with +/// `cargo run --release --example sha256_nivc` +/// where `n` is the needed arity +fn main() { + let subscriber = Registry::default() + .with(fmt::layer().pretty()) + .with(EnvFilter::from_default_env()) + .with(TeXRayLayer::new()); + tracing::subscriber::set_global_default(subscriber).unwrap(); + + let store = &mut Store::::new(); + let cproc_sym = user_sym("sha256"); + + let call = sha256_encode(store); + + let lang = Lang::>::new_with_bindings( + store, + vec![(cproc_sym, Sha256Coprocessor::new().into())], + ); + let lang_rc = Arc::new(lang.clone()); + + let supernova_prover = SuperNovaProver::>::new(REDUCTION_COUNT, lang); + + // println!("Setting up public parameters (rc = {REDUCTION_COUNT})..."); + + let pp_start = Instant::now(); + + // // see the documentation on `with_public_params` + // with_public_params(REDUCTION_COUNT, lang_rc.clone(), |pp| { + let pp_end = pp_start.elapsed(); + // println!("Public parameters took {:?}", pp_end); + + println!("Beginning proof+public-parameters step..."); + let proof_start = Instant::now(); + let (_proof, _z0, _zi, _num_steps) = supernova_prover + .evaluate_and_prove(None, call, empty_sym_env(store), store, 10000, lang_rc) + .unwrap(); + let proof_end = proof_start.elapsed(); + + println!("Proofs took {:?}", proof_end); + + // TODO: plumb verification. + // println!("Verifying proof..."); + + // let verify_start = Instant::now(); + // let res = proof.verify(claim, &z0, &zi).unwrap(); + // let verify_end = verify_start.elapsed(); + + // println!("Verify took {:?}", verify_end); + + // if res { + println!( + // "Congratulations! You proved and verified a SHA256 hash calculation in {:?} time!", + // pp_end + proof_end + verify_end + "Congratulations! You proved a dynamic SHA256 encoding in {:?} time!", + pp_end + proof_end + ); + // } + // }) + // .unwrap(); +} diff --git a/fcomm/Cargo.toml b/fcomm/Cargo.toml index d91ef8984c..14bfbdc66a 100644 --- a/fcomm/Cargo.toml +++ b/fcomm/Cargo.toml @@ -12,29 +12,32 @@ name = "fcomm" path = "src/bin/fcomm.rs" [dependencies] +abomonation = { workspace = true } anyhow = { workspace = true } base64 = { workspace = true } -bellperson = { workspace = true } +bellpepper-core = { workspace = true } bincode = { workspace = true } blstrs = { workspace = true } -clap = { version = "3.2", features = ["derive"] } -clap-verbosity-flag = "1.0" +camino = { workspace = true } +clap = { workspace = true, features = ["derive"] } +clap-verbosity-flag = "2.0" ff = { workspace = true } hex = { version = "0.4.3", features = ["serde"] } -log = { workspace = true } lurk = { path = "../", package = "lurk" } lurk-macros = { path = "../lurk-macros" } +nova = { workspace = true } once_cell = { workspace = true } pairing = { workspace = true } pasta_curves = { workspace = true, features = ["repr-c", "serde"] } -pretty_env_logger = { workspace = true } proptest = { workspace = true } proptest-derive = { workspace = true } rand = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } -camino = { workspace = true } +tracing = { workspace = true } +tracing-texray = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [dev-dependencies] assert_cmd = "2.0.12" diff --git a/fcomm/src/bin/fcomm.rs b/fcomm/src/bin/fcomm.rs index 7e0dae2eb0..04d89ed510 100644 --- a/fcomm/src/bin/fcomm.rs +++ b/fcomm/src/bin/fcomm.rs @@ -1,11 +1,15 @@ -use log::info; -use lurk::proof::nova::CurveCycleEquipped; +use abomonation::Abomonation; +use lurk::lurk_sym_ptr; +use lurk::proof::nova::{CurveCycleEquipped, G1, G2}; +use nova::traits::Group; use std::convert::TryFrom; use std::env; use std::fs::read_to_string; use std::io; use std::path::{Path, PathBuf}; use std::sync::Arc; +use tracing::info; +use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; use hex::FromHex; use serde::de::DeserializeOwned; @@ -21,7 +25,7 @@ use lurk::ptr::{Ptr, TypePredicates}; use lurk::public_parameters::error; use lurk::store::Store; -use clap::{AppSettings, Args, Parser, Subcommand}; +use clap::{Args, Parser, Subcommand}; use clap_verbosity_flag::{Verbosity, WarnLevel}; use fcomm::{ @@ -35,7 +39,6 @@ use lurk::public_parameters::public_params; /// Functional commitments #[derive(Parser, Debug)] #[clap(version, about, long_about = None)] -#[clap(global_setting(AppSettings::DeriveDisplayOrder))] struct Cli { /// Evaluate inputs before passing to function (outside the proof) when opening. Otherwise inputs are unevaluated. #[clap(long, value_parser)] @@ -230,10 +233,11 @@ impl Open { let rc = ReductionCount::try_from(self.reduction_count).expect("reduction count"); let prover = NovaProver::>::new(rc.count(), lang.clone()); let lang_rc = Arc::new(lang.clone()); - let pp = public_params(rc.count(), lang_rc, &public_param_dir()).expect("public params"); + let pp = + public_params(rc.count(), true, lang_rc, &public_param_dir()).expect("public params"); let function_map = committed_expression_store(); - let handle_proof = |out_path, proof: Proof| { + let handle_proof = |out_path, proof: Proof<'_, S1>| { proof.write_to_json_path(out_path); proof .verify(&pp, lang) @@ -334,7 +338,7 @@ impl Prove { let rc = ReductionCount::try_from(self.reduction_count).unwrap(); let prover = NovaProver::>::new(rc.count(), lang.clone()); let lang_rc = Arc::new(lang.clone()); - let pp = public_params(rc.count(), lang_rc.clone(), &public_param_dir()).unwrap(); + let pp = public_params(rc.count(), true, lang_rc.clone(), &public_param_dir()).unwrap(); let proof = match &self.claim { Some(claim) => { @@ -380,8 +384,13 @@ impl Verify { fn verify(&self, cli_error: bool, lang: &Lang>) { let proof = proof(Some(&self.proof)).unwrap(); let lang_rc = Arc::new(lang.clone()); - let pp = - public_params(proof.reduction_count.count(), lang_rc, &public_param_dir()).unwrap(); + let pp = public_params( + proof.reduction_count.count(), + true, + lang_rc, + &public_param_dir(), + ) + .unwrap(); let result = proof.verify(&pp, lang).unwrap(); serde_json::to_writer(io::stdout(), &result).unwrap(); @@ -431,7 +440,7 @@ fn read_no_eval_from_path, F: LurkField + Serialize>( ) -> Result<(Ptr, Ptr), Error> { let src = read_from_path(store, path)?; - let quote = store.lurk_sym("quote"); + let quote = lurk_sym_ptr!(store, quote); let quoted = store.list(&[quote, src]); Ok((quoted, src)) } @@ -500,6 +509,8 @@ fn proof<'a, P: AsRef, F: CurveCycleEquipped>( ) -> Result, error::Error> where F: Serialize + for<'de> Deserialize<'de>, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, { match proof_path { Some(path) => Proof::read_from_json_path(path), @@ -510,9 +521,11 @@ where fn main() { let cli = Cli::parse(); - pretty_env_logger::formatted_builder() - .filter_level(cli.verbose.log_level_filter()) - .init(); + let subscriber = Registry::default() + // TODO: correctly filter log level with `clap_verbosity_flag` + .with(fmt::layer().pretty()) + .with(EnvFilter::from_default_env()); + tracing::subscriber::set_global_default(subscriber).unwrap(); // TODO: make this properly configurable, e.g. allowing coprocessors let lang = Lang::new(); diff --git a/fcomm/src/error.rs b/fcomm/src/error.rs index b41e2f3f13..e9c47d31d5 100644 --- a/fcomm/src/error.rs +++ b/fcomm/src/error.rs @@ -1,5 +1,5 @@ use anyhow; -use bellperson::SynthesisError; +use bellpepper_core::SynthesisError; use lurk::error::ReductionError; use lurk::public_parameters::error; use lurk::store; diff --git a/fcomm/src/file_map.rs b/fcomm/src/file_map.rs index f071ab197c..21768f0c3e 100644 --- a/fcomm/src/file_map.rs +++ b/fcomm/src/file_map.rs @@ -62,6 +62,7 @@ where } } +#[derive(Debug)] pub struct FileMap { dir: Utf8PathBuf, _t: PhantomData<(K, V)>, diff --git a/fcomm/src/lib.rs b/fcomm/src/lib.rs index 621bc25194..f301445c29 100644 --- a/fcomm/src/lib.rs +++ b/fcomm/src/lib.rs @@ -1,6 +1,8 @@ -use log::info; +use ::nova::traits::Group; +use abomonation::Abomonation; use std::convert::TryFrom; use std::sync::Arc; +use tracing::info; #[cfg(not(target_arch = "wasm32"))] use proptest::prelude::*; @@ -20,9 +22,11 @@ use lurk::{ }, field::LurkField, hash::PoseidonCache, - proof::nova::{self, NovaProver, PublicParams}, + lurk_sym_ptr, + proof::nova::{self, NovaProver, PublicParams, G1, G2}, proof::Prover, ptr::{ContPtr, Ptr}, + state::initial_lurk_state, store::Store, tag::ExprTag, writer::Write, @@ -74,7 +78,7 @@ mod base64 { pub type NovaProofCache = FileMap>; pub fn nova_proof_cache(reduction_count: usize) -> NovaProofCache { - FileMap::>::new(format!("nova_proofs.{}", reduction_count)).unwrap() + FileMap::>::new(format!("nova_proofs.{reduction_count}")).unwrap() } pub type CommittedExpressionMap = FileMap, CommittedExpression>; @@ -87,7 +91,7 @@ pub fn public_param_dir() -> Utf8PathBuf { } // Number of circuit reductions per step, equivalent to `chunk_frame_count` -#[derive(Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum ReductionCount { One, Five, @@ -133,7 +137,7 @@ pub struct Commitment { pub comm: F, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct OpeningRequest { pub commitment: Commitment, pub input: Expression, @@ -194,7 +198,7 @@ impl FromHex for Commitment { } } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct Expression { pub expr: LurkPtr, } @@ -224,7 +228,7 @@ pub struct ZBytes { #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[cfg_attr(not(target_arch = "wasm32"), proptest(no_bound))] #[cfg_attr(not(target_arch = "wasm32"), serde_test(types(S1), zdata(true)))] -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct ZStorePtr { z_store: ZStore, z_ptr: ZExprPtr, @@ -259,8 +263,8 @@ impl Eq for LurkPtr {} #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[cfg_attr(not(target_arch = "wasm32"), proptest(no_bound))] #[cfg_attr(not(target_arch = "wasm32"), serde_test(types(S1), zdata(true)))] -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -pub struct CommittedExpression { +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub struct CommittedExpression { pub expr: LurkPtr, #[cfg_attr( not(target_arch = "wasm32"), @@ -276,7 +280,11 @@ pub struct VerificationResult { } #[derive(Serialize, Deserialize)] -pub struct Proof<'a, F: CurveCycleEquipped> { +pub struct Proof<'a, F: CurveCycleEquipped> +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ pub claim: Claim, pub proof: nova::Proof<'a, F, Coproc>, pub num_steps: usize, @@ -334,7 +342,7 @@ impl Deserialize<'de>> Claim { // this. Even if not entirely realistic, something with this general *shape* is likely to play a role in a recursive // system where the ability to aggregate proof verification more soundly is possible. //#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct Cert { pub claim_cid: ZExprPtr, pub proof_cid: ZExprPtr, @@ -397,7 +405,7 @@ impl ReductionCount { impl Evaluation { fn new( - s: &mut Store, + s: &Store, input: IO, output: IO, iterations: Option, // This might be padded, so is not quite 'iterations' in the sense of number of actual reduction steps required @@ -419,13 +427,13 @@ impl Evaluation { }; } - let expr = input.expr.fmt_to_string(s); - let env = input.env.fmt_to_string(s); - let cont = input.cont.fmt_to_string(s); + let expr = input.expr.fmt_to_string(s, initial_lurk_state()); + let env = input.env.fmt_to_string(s, initial_lurk_state()); + let cont = input.cont.fmt_to_string(s, initial_lurk_state()); - let expr_out = maybe_hide!(output.expr.fmt_to_string(s)); - let env_out = maybe_hide!(output.env.fmt_to_string(s)); - let cont_out = maybe_hide!(output.cont.fmt_to_string(s)); + let expr_out = maybe_hide!(output.expr.fmt_to_string(s, initial_lurk_state())); + let env_out = maybe_hide!(output.env.fmt_to_string(s, initial_lurk_state())); + let cont_out = maybe_hide!(output.cont.fmt_to_string(s, initial_lurk_state())); Self { expr, @@ -483,7 +491,7 @@ impl PtrEvaluation { } impl Commitment { - pub fn from_comm(s: &mut Store, ptr: &Ptr) -> Result { + pub fn from_comm(s: &Store, ptr: &Ptr) -> Result { assert_eq!(ExprTag::Comm, ptr.tag); let digest = *s @@ -525,7 +533,7 @@ impl Commitment { let commitment = Self::from_ptr_and_secret(s, &fun_ptr, secret)?; - let open = s.lurk_sym("open"); + let open = lurk_sym_ptr!(s, open); let comm_ptr = s.hide(secret, fun_ptr); // (open ) @@ -538,7 +546,7 @@ impl Commitment { } fn fun_application(&self, s: &mut Store, input: Ptr) -> Ptr { - let open = s.lurk_sym("open"); + let open = lurk_sym_ptr!(s, open); let comm_ptr = self.ptr(s); // (open ) @@ -581,7 +589,7 @@ impl LurkPtr { } } - pub fn from_ptr(s: &mut Store, ptr: &Ptr) -> Self { + pub fn from_ptr(s: &Store, ptr: &Ptr) -> Self { let (z_store, z_ptr) = ZStore::new_with_expr(s, ptr); let z_ptr = z_ptr.unwrap(); Self::ZStorePtr(ZStorePtr { z_store, z_ptr }) @@ -591,7 +599,7 @@ impl LurkPtr { impl LurkCont { pub fn cont_ptr( &self, - s: &mut Store, + s: &Store, ) -> ContPtr { match self { Self::Outermost => s.get_cont_outermost(), @@ -630,7 +638,6 @@ impl Expression { } impl<'a> Opening { - #[allow(clippy::too_many_arguments)] pub fn apply_and_prove( s: &'a mut Store, input: Ptr, @@ -760,12 +767,12 @@ impl<'a> Opening { (None, public_output.expr) }; - let input_string = input.fmt_to_string(s); + let input_string = input.fmt_to_string(s, initial_lurk_state()); let status = as Evaluable, Coproc>>::status(&public_output); let output_string = if status.is_terminal() { // Only actual output if result is terminal. - output_expr.fmt_to_string(s) + output_expr.fmt_to_string(s, initial_lurk_state()) } else { // We don't want to leak any internal information in the case of incomplete computations. // Provers might want to expose results in the case of explicit errors. @@ -786,7 +793,6 @@ impl<'a> Opening { } impl<'a> Proof<'a, S1> { - #[allow(clippy::too_many_arguments)] pub fn eval_and_prove( s: &'a mut Store, expr: Ptr, @@ -846,10 +852,8 @@ impl<'a> Proof<'a, S1> { return Ok(proof); } - if only_use_cached_proofs { - // FIXME: Error handling. - panic!("no cached proof"); - } + // FIXME: Error handling. + assert!(!only_use_cached_proofs, "no cached proof"); info!("Starting Proving"); @@ -941,7 +945,7 @@ impl<'a> Proof<'a, S1> { let chunk_frame_count = self.reduction_count.count(); let expected_steps = - (iterations / chunk_frame_count) + (iterations % chunk_frame_count != 0) as usize; + (iterations / chunk_frame_count) + usize::from(iterations % chunk_frame_count != 0); expected_steps == num_steps } else { @@ -965,11 +969,11 @@ impl<'a> Proof<'a, S1> { let input_io = { let expr = s .read(&evaluation.expr) - .map_err(|_| Error::VerificationError("failed to read expr".into()))?; + .map_err(|e| Error::VerificationError(format!("failed to read expr: {}", e)))?; let env = s .read(&evaluation.env) - .map_err(|_| Error::VerificationError("failed to read env".into()))?; + .map_err(|e| Error::VerificationError(format!("failed to read env: {}", e)))?; // FIXME: We ignore cont and assume Outermost, since we can't read a Cont. let cont = s.intern_cont_outermost(); @@ -980,11 +984,11 @@ impl<'a> Proof<'a, S1> { let output_io = { let expr = s .read(&evaluation.expr_out) - .map_err(|_| Error::VerificationError("failed to read expr out".into()))?; + .map_err(|e| Error::VerificationError(format!("failed to read expr out: {}", e)))?; let env = s .read(&evaluation.env_out) - .map_err(|_| Error::VerificationError("failed to read env out".into()))?; + .map_err(|e| Error::VerificationError(format!("failed to read env out: {}", e)))?; let cont = evaluation .status .to_cont(s) @@ -1209,6 +1213,7 @@ mod test { let rc = ReductionCount::One; let pp = public_params( rc.count(), + true, lang_rc.clone(), &fcomm_path_val.join("public_params"), ) @@ -1256,7 +1261,7 @@ mod test { Some(c) => commitment = c, _ => panic!("new commitment missing"), } - println!("Commitment: {:?}", commitment); + println!("Commitment: {commitment:?}"); } } diff --git a/fcomm/tests/makefile_tests.rs b/fcomm/tests/makefile_tests.rs index 6035b54803..5a46309f92 100644 --- a/fcomm/tests/makefile_tests.rs +++ b/fcomm/tests/makefile_tests.rs @@ -21,7 +21,7 @@ fn test_make_fcomm_examples() { let make_output = Command::new("make") .current_dir(examples_dir) - .arg(format!("-j{}", cpus)) + .arg(format!("-j{cpus}")) .output() .expect("Failed to run the make command, is make installed?"); diff --git a/fcomm/tests/proof_tests.rs b/fcomm/tests/proof_tests.rs index 59204a757f..cc892794ec 100644 --- a/fcomm/tests/proof_tests.rs +++ b/fcomm/tests/proof_tests.rs @@ -1,4 +1,5 @@ use assert_cmd::prelude::*; +use lurk::state::initial_lurk_state; use predicates::prelude::*; use std::fs::File; use std::io::Write; @@ -23,9 +24,9 @@ fn test_bad_command() { let mut cmd = fcomm_cmd(); cmd.arg("uiop"); - cmd.assert().failure().stderr(predicate::str::contains( - "error: Found argument 'uiop' which wasn't expected, or isn't valid in this context", - )); + cmd.assert() + .failure() + .stderr(predicate::str::contains("unrecognized subcommand \'uiop\'")); } #[test] @@ -47,7 +48,7 @@ fn test_eval_expression() { cmd.assert() .success() - .stdout("{\"expr\":\"((lambda (a b) (+ (* a 3) b)) 9 7)\",\"env\":\"nil\",\"cont\":\"Outermost\",\"expr_out\":\"34\",\"env_out\":\"nil\",\"cont_out\":\"Terminal\",\"status\":\"Terminal\",\"iterations\":17}"); + .stdout("{\"expr\":\"((lambda (.lurk.user.a .lurk.user.b) (+ (* .lurk.user.a 3) .lurk.user.b)) 9 7)\",\"env\":\"nil\",\"cont\":\"Outermost\",\"expr_out\":\"34\",\"env_out\":\"nil\",\"cont_out\":\"Terminal\",\"status\":\"Terminal\",\"iterations\":17}"); } fn test_prove_expression( @@ -236,12 +237,12 @@ fn test_function_aux( let mut store = Store::::default(); let input = store.read(function_input).expect("store read"); - let canonical_input = input.fmt_to_string(&store); + let canonical_input = input.fmt_to_string(&store, initial_lurk_state()); let canonical_output = store .read(expected_output) .expect("store read") - .fmt_to_string(&store); + .fmt_to_string(&store, initial_lurk_state()); assert_eq!(canonical_input, opening.input); assert_eq!(*expected_output, canonical_output); @@ -325,7 +326,7 @@ fn test_create_open_and_verify_complicated_higher_order_functional_commitment2() (sum (sum-aux 0))) (lambda (nums) (= (sum nums) 15)))"; - let expected_output = "BILL"; + let expected_output = ".lurk.user.BILL"; test_create_open_and_verify_functional_commitment_aux( function_source, diff --git a/lurk-macros/Cargo.toml b/lurk-macros/Cargo.toml index 7296f73027..21a8fcfc64 100644 --- a/lurk-macros/Cargo.toml +++ b/lurk-macros/Cargo.toml @@ -19,6 +19,7 @@ proptest-derive = { workspace = true } serde = { workspace = true, features = ["derive"] } [dev-dependencies] +anyhow.workspace = true bincode = { workspace = true } lurk_crate = { path = "../", package = "lurk" } pasta_curves = { workspace = true, features = ["repr-c", "serde"] } diff --git a/lurk-macros/src/lib.rs b/lurk-macros/src/lib.rs index 768d7bd29e..15fe0d52b8 100644 --- a/lurk-macros/src/lib.rs +++ b/lurk-macros/src/lib.rs @@ -12,14 +12,12 @@ //! Although severely limited in the expressions it can represent, and still lacking quasiquoting, //! the `lurk` macro allows embedding Lurk code in Rust source. See tests for examples. -extern crate proc_macro; - use proc_macro::TokenStream; use proc_macro2::Span; use quote::{quote, ToTokens}; use syn::{ parse_macro_input, AttributeArgs, Data, DataEnum, DeriveInput, Ident, Item, Lit, Meta, - MetaList, NestedMeta, Type, + MetaList, NestedMeta, Path, Type, }; #[proc_macro_derive(Coproc)] @@ -78,7 +76,7 @@ fn impl_enum_coproc(name: &Ident, variants: &DataEnum) -> TokenStream { } } - fn synthesize>( + fn synthesize>( &self, cs: &mut CS, g: &lurk::circuit::gadgets::data::GlobalAllocations, @@ -86,7 +84,7 @@ fn impl_enum_coproc(name: &Ident, variants: &DataEnum) -> TokenStream { input_exprs: &[lurk::circuit::gadgets::pointer::AllocatedPtr], input_env: &lurk::circuit::gadgets::pointer::AllocatedPtr, input_cont: &lurk::circuit::gadgets::pointer::AllocatedContPtr, - ) -> Result<(lurk::circuit::gadgets::pointer::AllocatedPtr, lurk::circuit::gadgets::pointer::AllocatedPtr, lurk::circuit::gadgets::pointer::AllocatedContPtr), bellperson::SynthesisError> { + ) -> Result<(lurk::circuit::gadgets::pointer::AllocatedPtr, lurk::circuit::gadgets::pointer::AllocatedPtr, lurk::circuit::gadgets::pointer::AllocatedContPtr), bellpepper_core::SynthesisError> { match self { #synthesize_arms } @@ -200,14 +198,14 @@ enum Lurk { } impl Lurk { - fn parse_raw(input: proc_macro2::TokenStream) -> Result { + fn parse_raw(input: proc_macro2::TokenStream) -> Self { // We just immediately turn the `TokenStream` into a string then delegate // to the Lurk parser. Although this is a little silly, it is simple. let string = input.to_string(); let mut input_it = input.into_iter().peekable(); while input_it.next().is_some() {} - Ok(Lurk::Src(string)) + Lurk::Src(string) } fn emit(&self) -> TokenStream { @@ -232,7 +230,7 @@ pub fn let_store(_tokens: TokenStream) -> TokenStream { #[proc_macro] pub fn lurk(tokens: TokenStream) -> TokenStream { - Lurk::parse_raw(tokens.into()).unwrap().emit() + Lurk::parse_raw(tokens.into()).emit() } /// This macro is used to generate round-trip serialization tests. @@ -291,9 +289,7 @@ pub fn serde_test(args: TokenStream, input: TokenStream) -> TokenStream { } Some(id) if *id == "zdata" => { - if nested.len() != 1 { - panic!("zdata attribute takes 1 argument"); - } + assert!(nested.len() == 1, "zdata attribute takes 1 argument"); match &nested[0] { NestedMeta::Lit(Lit::Bool(b)) => { test_zdata = b.value; @@ -302,10 +298,10 @@ pub fn serde_test(args: TokenStream, input: TokenStream) -> TokenStream { } } - _ => panic!("invalid attribute {:?}", path), + _ => panic!("invalid attribute {path:?}"), }, - _ => panic!("invalid argument {:?}", arg), + _ => panic!("invalid argument {arg:?}"), } } @@ -322,7 +318,7 @@ pub fn serde_test(args: TokenStream, input: TokenStream) -> TokenStream { for (i, ty) in types.into_iter().enumerate() { let serde_test = { let test_name = Ident::new( - &format!("test_serde_roundtrip_{}_{}", name, i), + &format!("test_serde_roundtrip_{name}_{i}"), Span::mixed_site(), ); quote! { @@ -339,7 +335,7 @@ pub fn serde_test(args: TokenStream, input: TokenStream) -> TokenStream { let zdata_test = if test_zdata { let test_name = Ident::new( - &format!("test_zdata_roundtrip_{}_{}", name, i), + &format!("test_zdata_roundtrip_{name}_{i}"), Span::mixed_site(), ); quote! { @@ -375,3 +371,99 @@ fn parse_type(m: &NestedMeta) -> Type { } } } + +fn try_from_match_arms( + name: &Ident, + variant_names: &[&Ident], + ty: syn::Path, +) -> proc_macro2::TokenStream { + let mut match_arms = quote! {}; + for variant in variant_names { + match_arms.extend(quote! { + x if x == #name::#variant as #ty => Ok(#name::#variant), + }); + } + match_arms +} + +fn get_type_from_attrs(attrs: &[syn::Attribute], attr_name: &str) -> syn::Result { + let Some(nested_arg) = attrs.iter().find_map(|arg| { + let Ok(Meta::List(MetaList { path, nested, .. })) = arg.parse_meta() else { + return None; + }; + if !path.is_ident(attr_name) { + return None; + } + nested.first().cloned() + }) else { + return Err(syn::Error::new( + proc_macro2::Span::call_site(), + format!("Could not find attribute {attr_name}"), + )); + }; + + match nested_arg { + NestedMeta::Meta(Meta::Path(path)) => Ok(path), + bad => Err(syn::Error::new_spanned( + bad, + &format!("Could not parse {attr_name} attribute")[..], + )), + } +} + +/// This macro derives an impl of TryFrom for an enum type T with `#[repr(foo)]`. +/// +/// # Example +/// ``` +/// use lurk_macros::TryFromRepr; +/// +/// #[derive(TryFromRepr)] +/// #[repr(u8)] +/// enum Foo { +/// Bar = 0, +/// Baz +/// } +/// ``` +/// +/// This will derive the natural impl that compares the input representation type to +/// the automatic conversions of each variant into that representation type. +#[proc_macro_derive(TryFromRepr)] +pub fn derive_try_from_repr(input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as DeriveInput); + let res_ty = get_type_from_attrs(&ast.attrs, "repr"); + + let name = &ast.ident; + let variants = match ast.data { + Data::Enum(ref variants) => variants + .variants + .iter() + .map(|v| &v.ident) + .collect::>(), + Data::Struct(_) | Data::Union(_) => { + panic!("#[derive(TryFromRepr)] is only defined for enums") + } + }; + + match res_ty { + Err(e) => { + // If no explicit repr were given for us, we can't pursue + panic!("TryFromRepr macro requires a repr parameter, which couldn't be parsed: {e:?}"); + } + Ok(ty) => { + let match_arms = try_from_match_arms(name, &variants, ty.clone()); + let name_str = name.to_string(); + quote! { + impl std::convert::TryFrom<#ty> for #name { + type Error = anyhow::Error; + fn try_from(v: #ty) -> Result>::Error> { + match v { + #match_arms + _ => Err(anyhow::anyhow!("invalid variant for enum {}", #name_str)), + } + } + } + } + } + } + .into() +} diff --git a/lurk-metrics/Cargo.toml b/lurk-metrics/Cargo.toml index 9918a5a7b9..4b26dcc79b 100644 --- a/lurk-metrics/Cargo.toml +++ b/lurk-metrics/Cargo.toml @@ -10,10 +10,9 @@ repository = "https://github.com/lurk-lab/lurk-rs" [dependencies] metrics = { workspace = true } once_cell = { workspace = true } -log = { workspace = true } hdrhistogram = { version = "7.5.2", default-features = false } - +tracing = { workspace = true } [dev-dependencies] -expect-test = "1" -testing_logger = "0.1.1" \ No newline at end of file +regex = { version = "1.9.4", features = ["unicode-case"] } +tracing-test = { version = "0.2", features = ["no-env-filter"] } \ No newline at end of file diff --git a/lurk-metrics/src/data.rs b/lurk-metrics/src/data.rs index 50acd982e4..792386e8f8 100644 --- a/lurk-metrics/src/data.rs +++ b/lurk-metrics/src/data.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use std::fmt::{self, Display, Formatter}; -use log::info; use metrics::Key; +use tracing::info; pub const METRICS_TARGET_NAME: &str = "lurk::metrics"; diff --git a/lurk-metrics/src/lib.rs b/lurk-metrics/src/lib.rs index 35c9c2d7a4..623f068ffa 100644 --- a/lurk-metrics/src/lib.rs +++ b/lurk-metrics/src/lib.rs @@ -170,12 +170,11 @@ impl ThreadMetricsSinkHandle { #[cfg(test)] mod tests { - use log::Level; - use metrics::Label; - use super::*; + use metrics::Label; + use tracing_test::traced_test; - // TODO: this uses, but does not clean up the global sink, clobbering the state for any further test + #[traced_test] #[test] fn test_basic_metrics() { let sink = MetricsSink::new(); @@ -229,25 +228,11 @@ mod tests { } } - testing_logger::setup(); MetricsSink::publish(metrics); - - testing_logger::validate(|captured_logs| { - assert_eq!(captured_logs.len(), 4); - let snapshot = expect_test::expect![[r#" - test_counter[type=bar]: 7 (n=3) - test_counter[type=foo]: 6 (n=3) - test_gauge[type=bar]: 3 (n=1) - test_gauge[type=foo]: 2 (n=1)"#]]; - - snapshot.assert_eq( - &captured_logs - .iter() - .map(|line| line.body.clone()) - .collect::>() - .join("\n"), - ); - assert_eq!(captured_logs[0].level, Level::Info); - }); + assert!(logs_contain("test_counter")); + assert!(logs_contain("test_counter[type=bar]: 7 (n=3)")); + assert!(logs_contain("test_counter[type=foo]: 6 (n=3)")); + assert!(logs_contain("test_gauge[type=bar]: 3 (n=1)")); + assert!(logs_contain("test_gauge[type=foo]: 2 (n=1)")); } } diff --git a/proptest-regressions/parser/syntax.txt b/proptest-regressions/parser/syntax.txt new file mode 100644 index 0000000000..def093d905 --- /dev/null +++ b/proptest-regressions/parser/syntax.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 72dda53aa591d94f5466c82b7f57c179c5b7e39a1fa3d88058bbe1fc5b05a501 # shrinks to x = Quote(No, List(No, [Path(No, ["."], false)])) diff --git a/rustfmt.toml b/rustfmt.toml index 186b3ffea0..aa4b564604 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,73 +1,5 @@ -max_width = 100 -hard_tabs = false -tab_spaces = 4 -newline_style = "Auto" -indent_style = "Block" -use_small_heuristics = "Default" -fn_call_width = 60 -attr_fn_like_width = 70 -struct_lit_width = 18 -struct_variant_width = 35 -array_width = 60 -chain_width = 60 -single_line_if_else_max_width = 50 -wrap_comments = false -format_code_in_doc_comments = false -comment_width = 80 -normalize_comments = false -normalize_doc_attributes = false -format_strings = false -format_macro_matchers = false -format_macro_bodies = true -hex_literal_case = "Preserve" -empty_item_single_line = true -struct_lit_single_line = true -fn_single_line = false -where_single_line = false -imports_indent = "Block" -imports_layout = "Mixed" -imports_granularity = "Preserve" -group_imports = "Preserve" -reorder_imports = true -reorder_modules = true -reorder_impl_items = false -type_punctuation_density = "Wide" -space_before_colon = false -space_after_colon = true -spaces_around_ranges = false -binop_separator = "Front" -remove_nested_parens = true -combine_control_expr = true -overflow_delimited_expr = false -struct_field_align_threshold = 0 -enum_discrim_align_threshold = 0 -match_arm_blocks = true -match_arm_leading_pipes = "Never" -force_multiline_blocks = false -fn_args_layout = "Tall" -brace_style = "SameLineWhere" -control_brace_style = "AlwaysSameLine" -trailing_semicolon = true -trailing_comma = "Vertical" -match_block_trailing_comma = false -blank_lines_upper_bound = 1 -blank_lines_lower_bound = 0 -edition = "2015" -version = "One" -inline_attribute_width = 0 -format_generated_files = true -merge_derives = true -use_try_shorthand = false -use_field_init_shorthand = false -force_explicit_abi = true -condense_wildcard_suffixes = false -color = "Auto" -unstable_features = false -disable_all_formatting = false -skip_children = false -hide_parse_errors = false -error_on_line_overflow = false -error_on_unformatted = false -ignore = [] -emit_mode = "Files" -make_backup = false +edition = "2021" # Rust edition +newline_style = "Unix" # never allow Windows' \n\r newlines + +use_field_init_shorthand = true # replace Foo { x: x } with Foo { x } +use_try_shorthand = true # replace try! with ? diff --git a/src/circuit/circuit_frame.rs b/src/circuit/circuit_frame.rs index f7754b1474..0911861678 100644 --- a/src/circuit/circuit_frame.rs +++ b/src/circuit/circuit_frame.rs @@ -1,11 +1,13 @@ +use std::collections::HashMap; use std::fmt::Debug; use std::marker::PhantomData; -use bellperson::{ - gadgets::{boolean::Boolean, num::AllocatedNum}, - util_cs::Comparable, - Circuit, ConstraintSystem, SynthesisError, +use bellpepper::util_cs::{witness_cs::WitnessCS, Comparable}; +use bellpepper_core::{ + boolean::Boolean, num::AllocatedNum, Circuit, ConstraintSystem, SynthesisError, }; +use rayon::prelude::*; +use tracing::{debug, info}; use crate::{ circuit::gadgets::{ @@ -13,8 +15,12 @@ use crate::{ data::GlobalAllocations, pointer::{AllocatedContPtr, AllocatedPtr, AsAllocatedHashComponents}, }, + config::CONFIG, field::LurkField, - hash_witness::{ConsName, ContName}, + hash::HashConst, + hash_witness::{ + ConsCircuitWitness, ConsName, ContCircuitWitness, ContName, HashCircuitWitnessCache, + }, store::NamedConstants, tag::Tag, }; @@ -24,15 +30,16 @@ use super::gadgets::constraints::{ pick, pick_const, sub, }; use crate::circuit::circuit_frame::constraints::{ - add, allocate_is_negative, boolean_to_num, enforce_pack, linear, mul, + add, allocate_is_negative, boolean_to_num, enforce_pack, enforce_product_and_sum, mul, }; use crate::circuit::gadgets::hashes::{AllocatedConsWitness, AllocatedContWitness}; use crate::circuit::ToInputs; use crate::coprocessor::Coprocessor; -use crate::eval::{lang::Lang, Frame, Witness, IO}; +use crate::eval::{Frame, Meta, Witness, IO}; use crate::expr::Thunk; use crate::hash_witness::HashWitness; -use crate::proof::Provable; +use crate::lurk_sym_ptr; +use crate::proof::{supernova::FoldingConfig, Provable}; use crate::ptr::Ptr; use crate::store::Store; use crate::tag::{ContTag, ExprTag, Op1, Op2}; @@ -42,25 +49,27 @@ use num_traits::FromPrimitive; use std::sync::Arc; #[derive(Clone, Copy, Debug)] -pub struct CircuitFrame<'a, F: LurkField, T, W, C: Coprocessor> { +pub struct CircuitFrame<'a, F: LurkField, C: Coprocessor> { pub store: Option<&'a Store>, - pub input: Option, - pub output: Option, - pub witness: Option, + pub input: Option>, + pub output: Option>, + pub witness: Option>, _p: PhantomData, } -#[derive(Clone)] -pub struct MultiFrame<'a, F: LurkField, T: Copy, W, C: Coprocessor> { +#[derive(Debug, Clone)] +pub struct MultiFrame<'a, F: LurkField, C: Coprocessor> { pub store: Option<&'a Store>, - pub lang: Option>>, - pub input: Option, - pub output: Option, - pub frames: Option>>, + pub input: Option>, + pub output: Option>, + pub frames: Option>>, + pub cached_witness: Option>, pub count: usize, + pub folding_config: Arc>, + pub meta: Meta, } -impl<'a, F: LurkField, T: Clone + Copy, W: Copy, C: Coprocessor> CircuitFrame<'a, F, T, W, C> { +impl<'a, F: LurkField, C: Coprocessor> CircuitFrame<'a, F, C> { pub fn blank() -> Self { Self { store: None, @@ -71,7 +80,7 @@ impl<'a, F: LurkField, T: Clone + Copy, W: Copy, C: Coprocessor> CircuitFrame } } - pub fn from_frame(frame: &Frame, store: &'a Store) -> Self { + pub fn from_frame(frame: &Frame, Witness, F, C>, store: &'a Store) -> Self { CircuitFrame { store: Some(store), input: Some(frame.input), @@ -82,17 +91,21 @@ impl<'a, F: LurkField, T: Clone + Copy, W: Copy, C: Coprocessor> CircuitFrame } } -impl<'a, F: LurkField, T: Clone + Copy + std::cmp::PartialEq, W: Copy, C: Coprocessor> - MultiFrame<'a, F, T, W, C> -{ - pub fn blank(count: usize, lang: Arc>) -> Self { +impl<'a, F: LurkField, C: Coprocessor> MultiFrame<'a, F, C> { + pub fn blank(folding_config: Arc>, meta: Meta) -> Self { + let count = match meta { + Meta::Lurk => folding_config.reduction_count(), + Meta::Coprocessor(_zptr) => 1, + }; Self { store: None, - lang: Some(lang), input: None, output: None, frames: None, + cached_witness: None, count, + folding_config, + meta, } } @@ -101,21 +114,26 @@ impl<'a, F: LurkField, T: Clone + Copy + std::cmp::PartialEq, W: Copy, C: Coproc } pub fn from_frames( + // we need this count, even though folding_config contains reduction_count + // because it might be overridden in the case of a coprocoessor, which should have rc=1. + // This is not ideal and might not *actually* be needed. count: usize, - frames: &[Frame], + frames: &[Frame, Witness, F, C>], store: &'a Store, - lang: &Arc>, + folding_config: Arc>, ) -> Vec { // `count` is the number of `Frames` to include per `MultiFrame`. let total_frames = frames.len(); - let n = total_frames / count + (total_frames % count != 0) as usize; + let n = (total_frames + count - 1) / count; let mut multi_frames = Vec::with_capacity(n); + let mut meta = None; for chunk in frames.chunks(count) { let mut inner_frames = Vec::with_capacity(count); for x in chunk { let circuit_frame = CircuitFrame::from_frame(x, store); + meta.get_or_insert(x.meta); inner_frames.push(circuit_frame); } @@ -126,20 +144,22 @@ impl<'a, F: LurkField, T: Clone + Copy + std::cmp::PartialEq, W: Copy, C: Coproc .clone(); // Fill out the MultiFrame, if needed, and capture output of the final actual frame. - for _ in chunk.len()..count { - inner_frames.push(last_circuit_frame.clone()); - } + inner_frames.resize(count, last_circuit_frame.clone()); let output = last_frame.output; debug_assert!(!inner_frames.is_empty()); + let meta = meta.unwrap_or(Meta::Lurk); + let mf = MultiFrame { store: Some(store), - lang: Some(lang.clone()), input: Some(chunk[0].input), output: Some(output), frames: Some(inner_frames), + cached_witness: None, count, + folding_config: folding_config.clone(), + meta, }; multi_frames.push(mf); @@ -151,9 +171,10 @@ impl<'a, F: LurkField, T: Clone + Copy + std::cmp::PartialEq, W: Copy, C: Coproc /// Make a dummy `MultiFrame`, duplicating `self`'s final `CircuitFrame`. pub(crate) fn make_dummy( count: usize, - circuit_frame: Option>, + circuit_frame: Option>, store: &'a Store, - lang: Arc>, + folding_config: Arc>, + meta: Meta, ) -> Self { let (frames, input, output) = if let Some(circuit_frame) = circuit_frame { ( @@ -166,11 +187,13 @@ impl<'a, F: LurkField, T: Clone + Copy + std::cmp::PartialEq, W: Copy, C: Coproc }; Self { store: Some(store), - lang: Some(lang), input, output, frames, + cached_witness: None, count, + folding_config, + meta, } } @@ -181,77 +204,233 @@ impl<'a, F: LurkField, T: Clone + Copy + std::cmp::PartialEq, W: Copy, C: Coproc input_expr: AllocatedPtr, input_env: AllocatedPtr, input_cont: AllocatedContPtr, - frames: &[CircuitFrame<'_, F, IO, Witness, C>], + frames: &[CircuitFrame<'_, F, C>], + g: &GlobalAllocations, + ) -> (AllocatedPtr, AllocatedPtr, AllocatedContPtr) { + if cs.is_witness_generator() && CONFIG.parallelism.synthesis.is_parallel() { + self.synthesize_frames_parallel(cs, store, input_expr, input_env, input_cont, frames, g) + } else { + self.synthesize_frames_sequential( + cs, store, input_expr, input_env, input_cont, frames, None, g, + ) + } + } + + pub fn synthesize_frames_sequential>( + &self, + cs: &mut CS, + store: &Store, + input_expr: AllocatedPtr, + input_env: AllocatedPtr, + input_cont: AllocatedContPtr, + frames: &[CircuitFrame<'_, F, C>], + cons_and_cont_witnesses: Option, ContCircuitWitness)>>, g: &GlobalAllocations, ) -> (AllocatedPtr, AllocatedPtr, AllocatedContPtr) { + let mut hash_circuit_witness_cache = HashMap::new(); + let acc = (input_expr, input_env, input_cont); let (_, (new_expr, new_env, new_cont)) = frames.iter().fold((0, acc), |(i, allocated_io), frame| { + info!("synthesizing frame {i}"); if let Some(next_input) = frame.input { - // Ensure all intermediate allocated I/O values match the provided executation trace. + // Ensure all intermediate allocated I/O values match the provided execution trace. + + let next_expr_hash = store.hash_expr(&next_input.expr); + let next_env_hash = store.hash_expr(&next_input.env); + let next_cont_hash = store.hash_cont(&next_input.cont); assert_eq!( allocated_io.0.tag().get_value(), - store.hash_expr(&next_input.expr).map(|x| x.tag_field()), + next_expr_hash.map(|x| x.tag_field()), "expr tag mismatch" ); assert_eq!( allocated_io.0.hash().get_value(), - store.hash_expr(&next_input.expr).map(|x| *x.value()), + next_expr_hash.map(|x| *x.value()), "expr mismatch" ); assert_eq!( allocated_io.1.tag().get_value(), - store.hash_expr(&next_input.env).map(|x| x.tag_field()), + next_env_hash.map(|x| x.tag_field()), "env tag mismatch" ); assert_eq!( allocated_io.1.hash().get_value(), - store.hash_expr(&next_input.env).map(|x| *x.value()), + next_env_hash.map(|x| *x.value()), "env mismatch" ); assert_eq!( allocated_io.2.tag().get_value(), - store.hash_cont(&next_input.cont).map(|x| x.tag_field()), + next_cont_hash.map(|x| x.tag_field()), "cont tag mismatch" ); assert_eq!( allocated_io.2.hash().get_value(), - store.hash_cont(&next_input.cont).map(|x| *x.value()), + next_cont_hash.map(|x| *x.value()), "cont mismatch" ); }; - ( - i + 1, - frame - .synthesize( - cs, - i, - allocated_io, - self.lang.as_ref().expect("Lang missing"), - g, + let (cons_witnesses, cont_witnesses) = + if let Some(cons_and_cont_witnesses) = &cons_and_cont_witnesses { + ( + Some(cons_and_cont_witnesses[i].0.clone()), + Some(cons_and_cont_witnesses[i].1.clone()), ) - .unwrap(), - ) + } else { + (None, None) + }; + + let new_allocated_io = frame + .synthesize( + cs, + i, + allocated_io, + self.folding_config.clone(), + g, + &mut hash_circuit_witness_cache, + cons_witnesses, + cont_witnesses, + ) + .unwrap(); + + (i + 1, new_allocated_io) }); (new_expr, new_env, new_cont) } + + pub fn synthesize_frames_parallel>( + &self, + cs: &mut CS, + store: &Store, + input_expr: AllocatedPtr, + input_env: AllocatedPtr, + input_cont: AllocatedContPtr, + frames: &[CircuitFrame<'_, F, C>], + g: &GlobalAllocations, + ) -> (AllocatedPtr, AllocatedPtr, AllocatedContPtr) { + assert!(cs.is_witness_generator()); + assert!(CONFIG.parallelism.synthesis.is_parallel()); + + // TODO: this probably belongs in config, perhaps per-Flow. + const MIN_CHUNK_SIZE: usize = 10; + + let num_frames = frames.len(); + + let chunk_size = CONFIG + .parallelism + .synthesis + .chunk_size(num_frames, MIN_CHUNK_SIZE); + + let css = frames + .par_chunks(chunk_size) + .enumerate() + .map(|(i, chunk)| { + let (input_expr, input_env, input_cont) = if i == 0 { + (input_expr.clone(), input_env.clone(), input_cont.clone()) + } else { + let previous_frame = &frames[i * chunk_size]; + let mut bogus_cs = WitnessCS::new(); + let x = previous_frame.input.unwrap().expr; + let input_expr = + AllocatedPtr::alloc_ptr(&mut bogus_cs, store, || Ok(&x)).unwrap(); + let y = previous_frame.input.unwrap().env; + let input_env = + AllocatedPtr::alloc_ptr(&mut bogus_cs, store, || Ok(&y)).unwrap(); + let z = previous_frame.input.unwrap().cont; + let input_cont = + AllocatedContPtr::alloc_cont_ptr(&mut bogus_cs, store, || Ok(&z)).unwrap(); + (input_expr, input_env, input_cont) + }; + + let cons_and_cont_witnesses = { + macro_rules! f { + () => { + |frame| { + let cons_circuit_witness: ConsCircuitWitness = frame + .witness + .map(|x| x.conses) + .unwrap_or_else(|| HashWitness::new_blank()) + .into(); + + let cons_constants: HashConst<'_, F> = + store.poseidon_constants().constants(4.into()); + + // Force generating the witness. This is the important part! + cons_circuit_witness.circuit_witness_blocks(store, cons_constants); + + let cont_circuit_witness: ContCircuitWitness = frame + .witness + .map(|x| x.conts) + .unwrap_or_else(|| HashWitness::new_blank()) + .into(); + + let cont_constants: HashConst<'_, F> = + store.poseidon_constants().constants(8.into()); + + // Force generating the witness. This is the important part! + cont_circuit_witness.circuit_witness_blocks(store, cont_constants); + + (cons_circuit_witness, cont_circuit_witness) + } + }; + } + + if CONFIG.parallelism.poseidon_witnesses.is_parallel() { + chunk.par_iter().map(f!()).collect::>() + } else { + chunk.iter().map(f!()).collect::>() + } + }; + + let mut cs = WitnessCS::new(); + + let output = self.synthesize_frames_sequential( + &mut cs, + store, + input_expr, + input_env, + input_cont, + chunk, + Some(cons_and_cont_witnesses), + g, + ); + + (cs, output) + }) + .collect::>(); + + let mut final_output = None; + + for (frames_cs, output) in css { + final_output = Some(output); + + let aux = frames_cs.aux_slice(); + cs.extend_aux(aux); + } + + final_output.unwrap() + } } -impl> CircuitFrame<'_, F, T, W, C> { +impl> CircuitFrame<'_, F, C> { pub fn precedes(&self, maybe_next: &Self) -> bool { self.output == maybe_next.input } } -impl> MultiFrame<'_, F, T, W, C> { +impl> MultiFrame<'_, F, C> { pub fn precedes(&self, maybe_next: &Self) -> bool { self.output == maybe_next.input } } -impl> Provable for MultiFrame<'_, F, IO, W, C> { +impl< + F: LurkField, // W: Copy + Sync, + C: Coprocessor, + > Provable for MultiFrame<'_, F, C> +{ fn public_inputs(&self) -> Vec { let mut inputs: Vec<_> = Vec::with_capacity(Self::public_input_size()); @@ -281,37 +460,50 @@ impl> Provable for MultiFrame<'_, F, type AllocatedIO = (AllocatedPtr, AllocatedPtr, AllocatedContPtr); -impl> CircuitFrame<'_, F, IO, Witness, C> { +impl> CircuitFrame<'_, F, C> { + #[tracing::instrument(skip_all, name = "CircuitFrame::synthesize", level = "debug")] pub(crate) fn synthesize>( &self, cs: &mut CS, i: usize, inputs: AllocatedIO, - lang: &Lang, + folding_config: Arc>, g: &GlobalAllocations, + _hash_circuit_witness_cache: &mut HashCircuitWitnessCache, // Currently unused. + cons_circuit_witness: Option>, + cont_circuit_witness: Option>, ) -> Result, SynthesisError> { let (input_expr, input_env, input_cont) = inputs; + let reduce = |store| { + let cons_circuit_witness = if let Some(ccw) = cons_circuit_witness { + ccw + } else { + let cons_witness = self + .witness + .map_or_else(|| HashWitness::new_blank(), |x| x.conses); + + (cons_witness).into() + }; - let mut reduce = |store| { - let cons_witness = self - .witness - .map(|x| x.conses) - .unwrap_or_else(|| HashWitness::new_blank()); let mut allocated_cons_witness = AllocatedConsWitness::from_cons_witness( &mut cs.namespace(|| format!("allocated_cons_witness {i}")), store, - &cons_witness, + &cons_circuit_witness, )?; - let cont_witness = self - .witness - .map(|x| x.conts) - .unwrap_or_else(|| HashWitness::new_blank()); + let cont_circuit_witness = if let Some(ccw) = cont_circuit_witness { + ccw + } else { + let cont_witness = self + .witness + .map_or_else(|| HashWitness::new_blank(), |x| x.conts); + (cont_witness).into() + }; let mut allocated_cont_witness = AllocatedContWitness::from_cont_witness( &mut cs.namespace(|| format!("allocated_cont_witness {i}")), store, - &cont_witness, + &cont_circuit_witness, )?; reduce_expression( @@ -323,7 +515,7 @@ impl> CircuitFrame<'_, F, IO, Witness, C> &mut allocated_cons_witness, &mut allocated_cont_witness, store, - lang, + folding_config, g, ) }; @@ -338,14 +530,15 @@ impl> CircuitFrame<'_, F, IO, Witness, C> } } -impl> Circuit for MultiFrame<'_, F, IO, Witness, C> { +impl> Circuit for MultiFrame<'_, F, C> { + #[tracing::instrument(skip_all, name = "::synthesize")] fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { //////////////////////////////////////////////////////////////////////////////// // Bind public inputs. // // Initial input: let mut synth = |store, - frames: &[CircuitFrame<'_, F, IO, Witness, C>], + frames: &[CircuitFrame<'_, F, C>], input: Option>, output: Option>| { let input_expr = AllocatedPtr::bind_input( @@ -673,14 +866,14 @@ fn reduce_expression, C: Coprocessor>( allocated_cons_witness: &mut AllocatedConsWitness<'_, F>, allocated_cont_witness: &mut AllocatedContWitness<'_, F>, store: &Store, - lang: &Lang, + folding_config: Arc>, g: &GlobalAllocations, ) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedContPtr), SynthesisError> { - // dbg!("reduce_expression"); - // dbg!(&expr.fetch_and_write_str(store)); - // dbg!(&expr); - // dbg!(&env.fetch_and_write_str(store)); - // dbg!(&cont.fetch_and_write_cont_str(store), &cont); + debug!("reduce_expression"); + debug!("{}", &expr.fetch_and_write_str(store)); + debug!("{:?}", &expr); + debug!("{}", &env.fetch_and_write_str(store)); + debug!("{} {:?}", &cont.fetch_and_write_cont_str(store), &cont); let mut results = Results::default(); { // Self-evaluating expressions @@ -770,7 +963,7 @@ fn reduce_expression, C: Coprocessor>( allocated_cons_witness, allocated_cont_witness, store, - lang, + folding_config, g, )?; @@ -939,13 +1132,12 @@ fn reduce_expression, C: Coprocessor>( )?; allocated_cons_witness.assert_final_invariants(); - allocated_cont_witness.witness.all_names(); allocated_cont_witness.assert_final_invariants(); - // dbg!(&result_expr.fetch_and_write_str(store)); - // dbg!(&result_env.fetch_and_write_str(store)); - // dbg!(&result_cont.fetch_and_write_cont_str(store)); - // dbg!(expr, env, cont); + // tracing::debug!("result_expr: {}", &result_expr.fetch_and_write_str(store)); + // tracing::debug!("result_env: {}", &result_env.fetch_and_write_str(store)); + // tracing::debug!("result_cont: {}", &result_cont.fetch_and_write_cont_str(store)); + // tracing::debug!("expr: {:?}; env: {:?}; cont: {:?}", expr, env, cont); Ok((result_expr, result_env, result_cont)) } @@ -1276,7 +1468,7 @@ fn reduce_cons, C: Coprocessor>( allocated_cons_witness: &mut AllocatedConsWitness<'_, F>, allocated_cont_witness: &mut AllocatedContWitness<'_, F>, store: &Store, - lang: &Lang, + folding_config: Arc>, g: &GlobalAllocations, ) -> Result< ( @@ -1305,7 +1497,7 @@ fn reduce_cons, C: Coprocessor>( head.alloc_hash_equal(&mut cs.namespace(|| stringify!($var)), $name.value())?; }; } - let c = store.get_constants(); + let c = store.expect_constants(); // SOUNDNESS: All symbols with their own case clause must be represented in this list def_head_val!(head_is_lambda0, c.lambda); def_head_val!(head_is_let, c.let_); @@ -1395,9 +1587,10 @@ fn reduce_cons, C: Coprocessor>( let head_is_current_env = and!(cs, &head_is_current_env0, &head_is_a_sym)?; let head_is_if = and!(cs, &head_is_if0, &head_is_a_sym)?; - let mut head_is_coprocessor_bools = Vec::with_capacity(lang.coprocessors().len()); + let mut head_is_coprocessor_bools = + Vec::with_capacity(folding_config.lang().coprocessors().len()); - for (sym, (coproc, z_ptr)) in lang.coprocessors().iter() { + for (sym, (coproc, z_ptr)) in folding_config.lang().coprocessors().iter() { if !coproc.has_circuit() { continue; }; @@ -2610,50 +2803,93 @@ fn reduce_cons, C: Coprocessor>( // The latter also means that all Coprocessor invocations only require 1 iteration. let mut coprocessor_results = Vec::new(); - - { - if !lang.coprocessors().is_empty() { - let max_coprocessor_arity = lang.max_coprocessor_arity(); - - let (inputs, actual_length) = destructure_list( - &mut cs.namespace(|| "coprocessor inputs"), - store, - g, - max_coprocessor_arity, - &rest, - )?; - - for (sym, (coproc, z_ptr)) in lang.coprocessors().iter() { - if !coproc.has_circuit() { - continue; - }; - - let cs = &mut cs.namespace(|| format!("{} coprocessor", sym)); - - let arity = coproc.arity(); - - let arity_is_correct = alloc_equal_const( - &mut cs.namespace(|| "arity_is_correct"), - &actual_length, - F::from(arity as u64), + let lang = folding_config.lang(); + + if !lang.coprocessors().is_empty() { + match folding_config.as_ref() { + FoldingConfig::NIVC(_lang, _reduction_count) => { + for (_, (coproc, z_ptr)) in lang.coprocessors().iter() { + if !coproc.has_circuit() { + info!("coproc has no circuit, continuing"); + continue; + }; + coprocessor_results.push(( + z_ptr, + expr.clone(), + env.clone(), + cont.clone(), + &g.false_num, + )); + } + } + FoldingConfig::IVC(lang, _reduction_count) => { + let max_coprocessor_arity = lang.max_coprocessor_arity(); + + let (inputs, actual_length) = destructure_list( + &mut cs.namespace(|| "coprocessor inputs"), + store, + g, + max_coprocessor_arity, + &rest, )?; - let (result_expr, result_env, result_cont) = - coproc.synthesize(cs, g, store, &inputs[..arity], env, cont)?; - - let new_expr = pick_ptr!(cs, &arity_is_correct, &result_expr, &rest)?; // TODO: The error case should probably be expr, but this is harder in straight evaluation atm. - let new_env = pick_ptr!(cs, &arity_is_correct, &result_env, &env)?; - let new_cont = - pick_cont_ptr!(cs, &arity_is_correct, &result_cont, &g.error_ptr_cont)?; - - // We can't just call `results.add_clauses_cons` here because of lifetime issues. - coprocessor_results.push((z_ptr, new_expr, new_env, new_cont)); + for (sym, (coproc, z_ptr)) in lang.coprocessors().iter() { + if !coproc.has_circuit() { + continue; + }; + + let cs = &mut cs.namespace(|| format!("{} coprocessor", sym)); + + let arity = coproc.arity(); + + let arity_is_correct = alloc_equal_const( + &mut cs.namespace(|| "arity_is_correct"), + &actual_length, + F::from(arity as u64), + )?; + + let (result_expr, result_env, result_cont) = + coproc.synthesize(cs, g, store, &inputs[..arity], env, cont)?; + + let quoted_expr = AllocatedPtr::construct_list( + &mut cs.namespace(|| "quote coprocessor result"), + g, + store, + &[&g.quote_ptr, &result_expr], + )?; + + let default_num_pair = &[&g.default_num, &g.default_num]; + + // TODO: This should be better abstracted, perhaps by resurrecting historical code. + let tail_components: &[&dyn AsAllocatedHashComponents; 4] = &[ + &result_env, + &result_cont, + default_num_pair, + default_num_pair, + ]; + + let tail_cont = AllocatedContPtr::construct( + &mut cs.namespace(|| "coprocessor tail cont"), + store, + &g.tail_cont_tag, + tail_components, + )?; + + let new_expr = pick_ptr!(cs, &arity_is_correct, "ed_expr, &rest)?; // TODO: The error case should probably be expr, but this is harder in straight evaluation atm. + + let new_env = pick_ptr!(cs, &arity_is_correct, &result_env, &env)?; + let new_cont = + pick_cont_ptr!(cs, &arity_is_correct, &tail_cont, &g.error_ptr_cont)?; + + // We can't just call `results.add_clauses_cons` here because of lifetime issues. + coprocessor_results.push((z_ptr, new_expr, new_env, new_cont, &g.false_num)); + } } } } for c in &coprocessor_results { - results.add_clauses_cons(*c.0.value(), &c.1, &c.2, &c.3, &g.true_num); + results.add_clauses_cons(*c.0.value(), &c.1, &c.2, &c.3, c.4); } let is_zero_arg_call = rest_is_nil; @@ -3108,16 +3344,18 @@ fn apply_continuation>( .and_then(|commit| store.open(commit)) .unwrap_or_else(|| { // nil is dummy - (F::ZERO, store.get_nil()) + (F::ZERO, lurk_sym_ptr!(store, nil)) }); let open_expr = AllocatedPtr::alloc(&mut cs.namespace(|| "open_expr"), || { Ok(store.hash_expr(&open_expr_ptr).unwrap()) - })?; + }) + .expect("alloc was passed an unfallible closure, yet failed!"); - let open_secret = AllocatedNum::alloc(&mut cs.namespace(|| "open_secret"), || { - Ok(open_secret_scalar) - })?; + let open_secret = + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "open_secret"), || { + open_secret_scalar + }); let arg1 = AllocatedPtr::by_index(1, &continuation_components); @@ -3617,7 +3855,7 @@ fn apply_continuation>( let rest_is_nil = allocated_rest.is_nil(&mut cs.namespace(|| "rest_is_nil"), g)?; let rest_not_nil = rest_is_nil.not(); - let begin = store.get_begin(); + let begin = lurk_sym_ptr!(store, begin); let allocated_begin = AllocatedPtr::alloc_ptr(&mut cs.namespace(|| "begin"), store, || Ok(&begin))?; @@ -3934,7 +4172,7 @@ fn apply_continuation>( b, &diff, op2.tag(), - store.get_constants(), + store.expect_constants(), )?; let field_arithmetic_result = AllocatedPtr::pick( @@ -4715,19 +4953,19 @@ fn to_unsigned_integer_helper>( field_bn: &BigUint, field_elem_bits: &[Boolean], size: UnsignedInt, -) -> Result, SynthesisError> { +) -> AllocatedNum { let power_of_two_bn = BigUint::pow(&BigUint::from_u32(2).unwrap(), size.num_bits()); let (q_bn, r_bn) = field_bn.div_rem(&power_of_two_bn); - let q_num = allocate_unconstrained_bignum(&mut cs.namespace(|| "q"), &q_bn)?; - let r_num = allocate_unconstrained_bignum(&mut cs.namespace(|| "r"), &r_bn)?; + let q_num = allocate_unconstrained_bignum(&mut cs.namespace(|| "q"), &q_bn); + let r_num = allocate_unconstrained_bignum(&mut cs.namespace(|| "r"), &r_bn); let pow2_size = match size { UnsignedInt::U32 => &g.power2_32_num, UnsignedInt::U64 => &g.power2_64_num, }; // field element = pow(2, size).q + r - linear( + enforce_product_and_sum( &mut cs, || "product(q,pow(2,size)) + r", &q_num, @@ -4741,9 +4979,9 @@ fn to_unsigned_integer_helper>( &mut cs.namespace(|| "enforce unsigned pack"), r_bits, &r_num, - )?; + ); - Ok(r_num) + r_num } // Convert from num to unsigned integers by taking the least significant bits. @@ -4770,7 +5008,7 @@ fn to_unsigned_integers>( &field_bn, &field_elem_bits, UnsignedInt::U32, - )?; + ); let r64_num = to_unsigned_integer_helper( &mut cs.namespace(|| "enforce u64"), g, @@ -4778,7 +5016,7 @@ fn to_unsigned_integers>( &field_bn, &field_elem_bits, UnsignedInt::U64, - )?; + ); Ok((r32_num, r64_num)) } @@ -4800,7 +5038,7 @@ fn to_u64>( &field_bn, &field_elem_bits, UnsignedInt::U64, - )?; + ); Ok(r64_num) } @@ -4828,14 +5066,14 @@ fn enforce_u64_div_mod>( (0, 0) // Dummy }; - let alloc_r_num = AllocatedNum::alloc(&mut cs.namespace(|| "r num"), || Ok(F::from_u64(r)))?; - let alloc_q_num = AllocatedNum::alloc(&mut cs.namespace(|| "q num"), || Ok(F::from_u64(q)))?; - let alloc_arg1_num = AllocatedNum::alloc(&mut cs.namespace(|| "arg1 num"), || { - Ok(F::from_u64(arg1_u64)) - })?; - let alloc_arg2_num = AllocatedNum::alloc(&mut cs.namespace(|| "arg2 num"), || { - Ok(F::from_u64(arg2_u64)) - })?; + let alloc_r_num = + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "r num"), || F::from_u64(r)); + let alloc_q_num = + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "q num"), || F::from_u64(q)); + let alloc_arg1_num = + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "arg1 num"), || F::from_u64(arg1_u64)); + let alloc_arg2_num = + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "arg2 num"), || F::from_u64(arg2_u64)); // a = b * q + r let product_u64mod = mul( @@ -4863,7 +5101,7 @@ fn enforce_u64_div_mod>( &mut cs.namespace(|| "enforce u64 mod decomposition"), &b_is_not_zero_and_cond, &u64mod_decomp, - )?; + ); enforce_less_than_bound( &mut cs.namespace(|| "remainder in range b"), @@ -4896,7 +5134,8 @@ fn enforce_less_than_bound>( &mut cs.namespace(|| "enforce u64 range"), cond, &diff_bound_num_is_negative.not(), - ) + ); + Ok(()) } // ATTENTION: @@ -4907,13 +5146,12 @@ fn enforce_less_than_bound>( fn allocate_unconstrained_bignum>( mut cs: CS, bn: &BigUint, -) -> Result, SynthesisError> { +) -> AllocatedNum { let bytes_le = bn.to_bytes_le(); let mut bytes_padded = [0u8; 32]; bytes_padded[0..bytes_le.len()].copy_from_slice(&bytes_le); let ff = F::from_bytes(&bytes_padded).unwrap(); - let num = AllocatedNum::alloc(&mut cs.namespace(|| "num"), || Ok(ff)).unwrap(); - Ok(num) + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "num"), || ff) } fn car_cdr_named>( @@ -4941,11 +5179,12 @@ fn car_cdr_named>( )?; if cons_not_dummy.get_value().unwrap_or(false) && !real_cons.get_value().unwrap_or(true) { - dbg!(maybe_cons.hash().get_value(), &allocated_digest.get_value()); - panic!( - "tried to take car_cdr of a non-dummy cons ({:?}) but supplied wrong value", - name + tracing::debug!( + "{:?} {:?}", + maybe_cons.hash().get_value(), + &allocated_digest.get_value() ); + panic!("tried to take car_cdr of a non-dummy cons ({name:?}) but supplied wrong value"); } implies!(cs, &cons_not_dummy, &real_cons); @@ -5090,7 +5329,7 @@ fn extend_rec>( ) } -fn destructure_list>( +pub fn destructure_list>( cs: &mut CS, store: &Store, g: &GlobalAllocations, @@ -5160,10 +5399,12 @@ fn car_cdr>( .car_cdr(ptr) .map_err(|_| SynthesisError::AssignmentMissing)? } else { - (store.get_nil(), store.get_nil()) + let nil_ptr = lurk_sym_ptr!(store, nil); + (nil_ptr, nil_ptr) } } else { - (store.get_nil(), store.get_nil()) + let nil_ptr = lurk_sym_ptr!(store, nil); + (nil_ptr, nil_ptr) }; let allocated_car = AllocatedPtr::alloc_ptr(&mut cs.namespace(|| "car"), store, || Ok(&car))?; @@ -5189,7 +5430,7 @@ fn car_cdr>( &mut cs.namespace(|| "is cons implies real cons"), not_dummy, &real_cons, - )?; + ); Ok((allocated_car, allocated_cdr)) } @@ -5231,10 +5472,10 @@ mod tests { use crate::proof::groth16::Groth16Prover; use crate::proof::{Provable, Prover}; use crate::store::Store; + use bellpepper::util_cs::metric_cs::MetricCS; + use bellpepper_core::test_cs::TestConstraintSystem; + use bellpepper_core::{Comparable, Delta}; use bellperson::groth16; - use bellperson::util_cs::{ - metric_cs::MetricCS, test_cs::TestConstraintSystem, Comparable, Delta, - }; use blstrs::{Bls12, Scalar as Fr}; use ff::{Field, PrimeField}; use pairing::Engine; @@ -5254,7 +5495,7 @@ mod tests { }; let raw_lang = Lang::>::new(); let lang = Arc::new(raw_lang.clone()); - let (_, witness) = input.reduce(&mut store, &lang).unwrap(); + let (_, witness, meta) = input.reduce(&mut store, &lang).unwrap(); let public_params = Groth16Prover::, Fr>::create_groth_params( DEFAULT_REDUCTION_COUNT, @@ -5273,9 +5514,13 @@ mod tests { let mut cs = TestConstraintSystem::new(); let mut cs_blank = MetricCS::::new(); - let blank_multiframe = MultiFrame::<::Fr, _, _, Coproc>::blank( - DEFAULT_REDUCTION_COUNT, + let folding_config = Arc::new(FoldingConfig::new_ivc( lang.clone(), + DEFAULT_REDUCTION_COUNT, + )); + let blank_multiframe = MultiFrame::<::Fr, Coproc>::blank( + folding_config.clone(), + Meta::Lurk, ); blank_multiframe @@ -5289,10 +5534,11 @@ mod tests { output, i: 0, witness, + meta, _p: Default::default(), }], store, - &lang, + folding_config, ); let multiframe = &multiframes[0]; @@ -5303,12 +5549,13 @@ mod tests { .expect("failed to synthesize"); let delta = cs.delta(&cs_blank, false); + tracing::debug!("{:?}", &delta); assert!(delta == Delta::Equal); - //println!("{}", print_cs(&cs)); - assert_eq!(12032, cs.num_constraints()); + // println!("{}", print_cs(&cs)); + assert_eq!(11823, cs.num_constraints()); assert_eq!(13, cs.num_inputs()); - assert_eq!(11688, cs.aux().len()); + assert_eq!(11479, cs.aux().len()); let public_inputs = multiframe.public_inputs(); let mut rng = rand::thread_rng(); @@ -5381,7 +5628,7 @@ mod tests { fn nil_self_evaluating() { let mut store = Store::default(); let env = empty_sym_env(&store); - let nil = store.nil(); + let nil = lurk_sym_ptr!(store, nil); let input = IO { expr: nil, @@ -5390,7 +5637,7 @@ mod tests { }; let lang = Arc::new(Lang::>::new()); - let (_, witness) = input.reduce(&mut store, &lang).unwrap(); + let (_, witness, meta) = input.reduce(&mut store, &lang).unwrap(); store.hydrate_scalar_cache(); let test_with_output = |output: IO, expect_success: bool, store: &Store| { @@ -5401,14 +5648,18 @@ mod tests { output, i: 0, witness, + meta, _p: Default::default(), }; - - MultiFrame::<::Fr, _, _, Coproc>::from_frames( + let folding_config = Arc::new(FoldingConfig::new_ivc( + lang.clone(), + DEFAULT_REDUCTION_COUNT, + )); + MultiFrame::<::Fr, Coproc>::from_frames( DEFAULT_REDUCTION_COUNT, &[frame], store, - &lang, + folding_config, )[0] .clone() .synthesize(&mut cs) @@ -5461,7 +5712,7 @@ mod tests { fn t_self_evaluating() { let mut store = Store::default(); let env = empty_sym_env(&store); - let t = store.t(); + let t = lurk_sym_ptr!(store, t); let input = IO { expr: t, @@ -5470,7 +5721,7 @@ mod tests { }; let lang = Arc::new(Lang::>::new()); - let (_, witness) = input.reduce(&mut store, &lang).unwrap(); + let (_, witness, meta) = input.reduce(&mut store, &lang).unwrap(); store.hydrate_scalar_cache(); let test_with_output = |output: IO, expect_success: bool, store: &Store| { @@ -5481,14 +5732,19 @@ mod tests { output, i: 0, witness, + meta, _p: Default::default(), }; - MultiFrame::<::Fr, _, _, Coproc>::from_frames( + let folding_config = Arc::new(FoldingConfig::new_ivc( + lang.clone(), + DEFAULT_REDUCTION_COUNT, + )); + MultiFrame::<::Fr, Coproc>::from_frames( DEFAULT_REDUCTION_COUNT, &[frame], store, - &lang, + folding_config, )[0] .clone() .synthesize(&mut cs) @@ -5551,7 +5807,7 @@ mod tests { }; let lang = Arc::new(Lang::>::new()); - let (_, witness) = input.reduce(&mut store, &lang).unwrap(); + let (_, witness, meta) = input.reduce(&mut store, &lang).unwrap(); store.hydrate_scalar_cache(); @@ -5563,14 +5819,20 @@ mod tests { output, i: 0, witness, + meta, _p: Default::default(), }; - MultiFrame::<::Fr, _, _, Coproc>::from_frames( + let folding_config = Arc::new(FoldingConfig::new_ivc( + lang.clone(), + DEFAULT_REDUCTION_COUNT, + )); + + MultiFrame::<::Fr, Coproc>::from_frames( DEFAULT_REDUCTION_COUNT, &[frame], store, - &lang, + folding_config, )[0] .clone() .synthesize(&mut cs) @@ -5633,7 +5895,7 @@ mod tests { }; let lang = Arc::new(Lang::>::new()); - let (_, witness) = input.reduce(&mut store, &lang).unwrap(); + let (_, witness, meta) = input.reduce(&mut store, &lang).unwrap(); store.hydrate_scalar_cache(); @@ -5645,14 +5907,19 @@ mod tests { output, i: 0, witness, + meta, _p: Default::default(), }; + let folding_config = Arc::new(FoldingConfig::new_ivc( + lang.clone(), + DEFAULT_REDUCTION_COUNT, + )); - MultiFrame::<::Fr, _, _, Coproc>::from_frames( + MultiFrame::<::Fr, Coproc>::from_frames( DEFAULT_REDUCTION_COUNT, &[frame], store, - &lang, + folding_config, )[0] .clone() .synthesize(&mut cs) @@ -5700,7 +5967,7 @@ mod tests { alloc_b.hash(), &diff, &g.op2_less_tag, - s.get_constants(), + s.expect_constants(), ) .unwrap(); assert!(cs.is_satisfied()); @@ -5740,12 +6007,11 @@ mod tests { let mut cs = TestConstraintSystem::::new(); let alloc_most_positive = - AllocatedNum::alloc(&mut cs.namespace(|| "most positive"), || { - Ok(Fr::most_positive()) - }) - .unwrap(); + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "most positive"), || { + Fr::most_positive() + }); let alloc_num = - AllocatedNum::alloc(&mut cs.namespace(|| "num"), || Ok(Fr::from_u64(42))).unwrap(); + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "num"), || Fr::from_u64(42)); let cond = Boolean::Constant(true); let res = enforce_less_than_bound( @@ -5857,7 +6123,7 @@ mod tests { .unwrap(); let popcount_result = AllocatedNum::alloc(&mut cs.namespace(|| format!("alloc popcount {x}")), || { - Ok(Fr::from(x.count_ones() as u64)) + Ok(Fr::from(u64::from(x.count_ones()))) }) .unwrap(); @@ -5865,8 +6131,7 @@ mod tests { &mut cs.namespace(|| format!("popcount {x}")), &bits, popcount_result.get_variable(), - ) - .unwrap(); + ); } assert!(cs.is_satisfied()); @@ -5896,8 +6161,7 @@ mod tests { &field_bn, &bits, UnsignedInt::U32, - ) - .unwrap(); + ); assert_eq!(a, res.get_value().unwrap()); assert!(cs.is_satisfied()); @@ -5927,8 +6191,7 @@ mod tests { &field_bn, &bits, UnsignedInt::U64, - ) - .unwrap(); + ); assert_eq!(a, res.get_value().unwrap()); assert!(cs.is_satisfied()); @@ -5940,7 +6203,7 @@ mod tests { let a_num = AllocatedNum::alloc(&mut cs.namespace(|| "a num"), || Ok(Fr::from_u64(42))).unwrap(); let bits = a_num.to_bits_le(&mut cs.namespace(|| "bits")).unwrap(); - enforce_pack(&mut cs, &bits, &a_num).unwrap(); + enforce_pack(&mut cs, &bits, &a_num); assert!(cs.is_satisfied()); } } diff --git a/src/circuit/gadgets/case.rs b/src/circuit/gadgets/case.rs index 68431fe8c1..756f8921d3 100644 --- a/src/circuit/gadgets/case.rs +++ b/src/circuit/gadgets/case.rs @@ -3,9 +3,9 @@ use super::data::GlobalAllocations; use crate::field::LurkField; -use bellperson::{ - gadgets::boolean::{AllocatedBit, Boolean}, - gadgets::num::AllocatedNum, +use bellpepper_core::{ + boolean::{AllocatedBit, Boolean}, + num::AllocatedNum, ConstraintSystem, SynthesisError, }; use itertools::Itertools; @@ -362,9 +362,9 @@ mod tests { use super::*; use crate::store::Store; - use bellperson::util_cs::{ - metric_cs::MetricCS, test_cs::TestConstraintSystem, Comparable, Delta, - }; + use bellpepper::util_cs::{metric_cs::MetricCS, Comparable}; + use bellpepper_core::test_cs::TestConstraintSystem; + use bellpepper_core::Delta; #[test] fn simple_case() { diff --git a/src/circuit/gadgets/circom/mod.rs b/src/circuit/gadgets/circom/mod.rs new file mode 100644 index 0000000000..1ae3e68527 --- /dev/null +++ b/src/circuit/gadgets/circom/mod.rs @@ -0,0 +1,26 @@ +//! # Usage of circom coprocessors. +//! +//! See `examples/circom.rs` for a quick example of how to declare a circom coprocessor. + +use crate::{field::LurkField, ptr::Ptr, store::Store}; + +use super::pointer::AllocatedPtr; + +/// An interface to declare a new type of Circom gadget. +/// It requires 3 things: +/// 1. The use defined name of the gadget. This _must_ be an +/// existing name loaded into the file system via the CLI +/// (with `lurk coprocessor --name `) +/// 2. A defined way to take a list of Lurk input pointers +/// and turn them into a Circom input. We do not enforce +/// the shapes of either the Lurk end or the Circom end, +/// so users should take care to define what shape they expect. +/// 3. A defined way *Lurk* should evaluate what this gadget does. +/// This is then the implementation used in the `Coprocessor` trait. +pub trait CircomGadget: Send + Sync + Clone { + fn name(&self) -> &str; + + fn into_circom_input(self, input: &[AllocatedPtr]) -> Vec<(String, Vec)>; + + fn simple_evaluate(&self, s: &mut Store, args: &[Ptr]) -> Ptr; +} diff --git a/src/circuit/gadgets/constraints.rs b/src/circuit/gadgets/constraints.rs index d2f13774ac..68e04cfbe2 100644 --- a/src/circuit/gadgets/constraints.rs +++ b/src/circuit/gadgets/constraints.rs @@ -1,12 +1,9 @@ // Initially taken from: rust-fil-proofs/storage-proofs-core/src/gadgets/ - use crate::field::LurkField; -use bellperson::LinearCombination; -use bellperson::{ - gadgets::{ - boolean::{AllocatedBit, Boolean}, - num::{AllocatedNum, Num}, - }, +use bellpepper_core::LinearCombination; +use bellpepper_core::{ + boolean::{AllocatedBit, Boolean}, + num::{AllocatedNum, Num}, ConstraintSystem, SynthesisError, Variable, }; use ff::PrimeField; @@ -77,6 +74,7 @@ pub(crate) fn enforce_sum>( ); } +/// Compute sum and enforce it. pub(crate) fn add>( mut cs: CS, a: &AllocatedNum, @@ -98,11 +96,10 @@ pub(crate) fn add>( /// Creates a linear combination representing the popcount (sum of one bits) of `v`. pub(crate) fn popcount_lc>( v: &[Boolean], -) -> Result, SynthesisError> { - v.iter() - .try_fold(LinearCombination::::zero(), |acc, bit| { - add_to_lc::(bit, acc, F::ONE) - }) +) -> LinearCombination { + v.iter().fold(LinearCombination::::zero(), |acc, bit| { + add_to_lc::(bit, acc, F::ONE) + }) } /// Adds a constraint to CS, enforcing that the addition of the allocated numbers in vector `v` @@ -111,8 +108,8 @@ pub(crate) fn popcount_equal>( cs: &mut CS, v: &[Boolean], sum: Variable, -) -> Result<(), SynthesisError> { - let popcount = popcount_lc::(v)?; +) { + let popcount = popcount_lc::(v); // popcount * 1 = sum cs.enforce( @@ -121,8 +118,6 @@ pub(crate) fn popcount_equal>( |lc| lc + CS::one(), |lc| lc + sum, ); - - Ok(()) } /// Adds a constraint to CS, enforcing that the addition of the allocated numbers in vector `v` @@ -134,7 +129,7 @@ pub(crate) fn popcount_equal>( pub(crate) fn enforce_popcount_one>( cs: &mut CS, v: &[Boolean], -) -> Result<(), SynthesisError> { +) { popcount_equal(cs, v, CS::one()) } @@ -142,38 +137,73 @@ pub(crate) fn add_to_lc>( b: &Boolean, lc: LinearCombination, scalar: F, -) -> Result, SynthesisError> { - let v_lc = match b { +) -> LinearCombination { + match b { Boolean::Constant(c) => lc + (if *c { scalar } else { F::ZERO }, CS::one()), Boolean::Is(ref v) => lc + (scalar, v.get_variable()), Boolean::Not(ref v) => lc + (scalar, CS::one()) - (scalar, v.get_variable()), - }; + } +} - Ok(v_lc) +/// If premise is true, enforce `a` fits into 64 bits. It shows a non-deterministic +/// partial bit decomposition in order to constraint correct behavior. +pub(crate) fn implies_u64>( + mut cs: CS, + premise: &Boolean, + a: &AllocatedNum, +) -> Result<(), SynthesisError> { + let mut a_u64 = a.get_value().and_then(|a| a.to_u64()).unwrap_or(0); + + let mut bits: Vec = Vec::with_capacity(64); + for i in 0..64 { + let b = a_u64 & 1; + let b_bool = Boolean::Is(AllocatedBit::alloc( + &mut cs.namespace(|| format!("b.{i}")), + Some(b == 1), + )?); + bits.push(b_bool); + + a_u64 /= 2; + } + + // premise -> a = sum(bits) + implies_pack( + &mut cs.namespace(|| "u64 bit decomposition check"), + premise, + &bits, + a, + ); + + Ok(()) } -// Enforce v is the bit decomposition of num, therefore we have that 0 <= num < 2ˆ(sizeof(v)). -pub(crate) fn enforce_pack>( +/// If premise is true, enforce v is the bit decomposition of num, therefore we have that 0 <= num < 2ˆ(sizeof(v)). +pub(crate) fn implies_pack>( mut cs: CS, + premise: &Boolean, v: &[Boolean], num: &AllocatedNum, -) -> Result<(), SynthesisError> { +) { let mut coeff = F::ONE; - - let mut v_lc = LinearCombination::::zero(); + let mut pack = LinearCombination::::zero(); for b in v { - v_lc = add_to_lc::(b, v_lc, coeff)?; + pack = add_to_lc::(b, pack, coeff); coeff = coeff.double(); } + let diff = |_| pack - num.get_variable(); + let premise_lc = |_| premise.lc(CS::one(), F::ONE); + let zero = |lc| lc; - cs.enforce( - || "pack", - |_| v_lc, - |lc| lc + CS::one(), - |lc| lc + num.get_variable(), - ); + cs.enforce(|| "pack", diff, premise_lc, zero); +} - Ok(()) +/// Enforce v is the bit decomposition of num, therefore we have that 0 <= num < 2ˆ(sizeof(v)). +pub(crate) fn enforce_pack>( + cs: CS, + v: &[Boolean], + num: &AllocatedNum, +) { + implies_pack(cs, &Boolean::Constant(true), v, num) } /// Adds a constraint to CS, enforcing a difference relationship between the allocated numbers a, b, and difference. @@ -200,6 +230,7 @@ pub(crate) fn enforce_difference>( ); } +/// Compute difference and enforce it. pub(crate) fn sub>( mut cs: CS, a: &AllocatedNum, @@ -223,7 +254,7 @@ pub(crate) fn sub>( /// a * b + c = num is enforced. /// /// a * b = num - c -pub(crate) fn linear>( +pub(crate) fn enforce_product_and_sum>( cs: &mut CS, annotation: A, a: &AllocatedNum, @@ -265,6 +296,7 @@ pub(crate) fn product>( ); } +/// Compute product and enforce it. pub(crate) fn mul>( mut cs: CS, a: &AllocatedNum, @@ -345,10 +377,7 @@ pub(crate) fn pick>( condition: &Boolean, a: &AllocatedNum, b: &AllocatedNum, -) -> Result, SynthesisError> -where - CS: ConstraintSystem, -{ +) -> Result, SynthesisError> { let c = AllocatedNum::alloc(cs.namespace(|| "pick result"), || { if condition .get_value() @@ -378,10 +407,7 @@ pub(crate) fn pick_const>( condition: &Boolean, a: F, b: F, -) -> Result, SynthesisError> -where - CS: ConstraintSystem, -{ +) -> Result, SynthesisError> { let c = AllocatedNum::alloc(cs.namespace(|| "pick result"), || { if condition .get_value() @@ -405,14 +431,11 @@ where Ok(c) } -/// Convert from Boolean to AllocatedNum +/// Convert from Boolean to AllocatedNum. pub(crate) fn boolean_to_num>( mut cs: CS, bit: &Boolean, -) -> Result, SynthesisError> -where - CS: ConstraintSystem, -{ +) -> Result, SynthesisError> { let num = AllocatedNum::alloc(cs.namespace(|| "Allocate num"), || { if bit.get_value().ok_or(SynthesisError::AssignmentMissing)? { Ok(F::ONE) @@ -432,7 +455,7 @@ where Ok(num) } -// This could now use alloc_is_zero to avoid duplication. +/// This could now use alloc_is_zero to avoid duplication. pub fn alloc_equal, F: PrimeField>( mut cs: CS, a: &AllocatedNum, @@ -441,25 +464,25 @@ pub fn alloc_equal, F: PrimeField>( let equal = a.get_value().and_then(|x| b.get_value().map(|y| x == y)); // Difference between `a` and `b`. This will be zero if `a` and `b` are equal. - let diff = sub(cs.namespace(|| "a - b"), a, b)?; - // result = (a == b) let result = AllocatedBit::alloc(cs.namespace(|| "a = b"), equal)?; - // result * diff = 0 - // This means that at least one of result or diff is zero. + // result * (a - b) = 0 + // This means that at least one of result or a - b is zero. cs.enforce( || "result or diff is 0", |lc| lc + result.get_variable(), - |lc| lc + diff.get_variable(), + |lc| lc + a.get_variable() - b.get_variable(), |lc| lc, ); - // Inverse of `diff`, if it exists, otherwise one. + // Inverse of `a - b`, if it exists, otherwise one. let q = cs.alloc( || "q", || { - let tmp0 = diff.get_value().ok_or(SynthesisError::AssignmentMissing)?; + let a_val = a.get_value().ok_or(SynthesisError::AssignmentMissing)?; + let b_val = b.get_value().ok_or(SynthesisError::AssignmentMissing)?; + let tmp0 = a_val - b_val; let tmp1 = tmp0.invert(); if tmp1.is_some().into() { @@ -470,11 +493,11 @@ pub fn alloc_equal, F: PrimeField>( }, )?; - // (diff + result) * q = 1. + // (a - b + result) * q = 1. // This enforces that diff and result are not both 0. cs.enforce( - || "(diff + result) * q = 1", - |lc| lc + diff.get_variable() + result.get_variable(), + || "(a - b + result) * q = 1", + |lc| lc + a.get_variable() - b.get_variable() + result.get_variable(), |lc| lc + q, |lc| lc + CS::one(), ); @@ -487,7 +510,7 @@ pub fn alloc_equal, F: PrimeField>( Ok(Boolean::Is(result)) } -// Like `alloc_equal`, but with second argument a constant. +/// Like `alloc_equal`, but with second argument a constant. pub(crate) fn alloc_equal_const, F: PrimeField>( mut cs: CS, a: &AllocatedNum, @@ -542,6 +565,7 @@ pub(crate) fn alloc_equal_const, F: PrimeField>( Ok(Boolean::Is(result)) } +/// Allocate a Boolean which is true if and only if `x` is zero. pub(crate) fn alloc_is_zero, F: PrimeField>( cs: CS, x: &AllocatedNum, @@ -549,6 +573,7 @@ pub(crate) fn alloc_is_zero, F: PrimeField>( alloc_num_is_zero(cs, &Num::from(x.clone())) } +/// Allocate a Boolean which is true if and only if `num` is zero. pub(crate) fn alloc_num_is_zero, F: PrimeField>( mut cs: CS, num: &Num, @@ -597,6 +622,7 @@ pub(crate) fn alloc_num_is_zero, F: PrimeField>( Ok(Boolean::Is(result)) } +/// Variadic or. pub(crate) fn or_v, F: PrimeField>( cs: CS, v: &[&Boolean], @@ -609,6 +635,7 @@ pub(crate) fn or_v, F: PrimeField>( or_v_unchecked_for_optimization(cs, v) } +/// Unchecked variadic or. pub(crate) fn or_v_unchecked_for_optimization, F: PrimeField>( mut cs: CS, v: &[&Boolean], @@ -625,6 +652,7 @@ pub(crate) fn or_v_unchecked_for_optimization, F: PrimeF Ok(nor.not()) } +/// Variadic and. pub(crate) fn and_v, F: PrimeField>( mut cs: CS, v: &[&Boolean], @@ -648,6 +676,7 @@ pub(crate) fn and_v, F: PrimeField>( /// This is a replication of Bellperson's original `and`, but receives a mutable /// reference for the constraint system instead of a copy +#[allow(dead_code)] pub(crate) fn and, F: PrimeField>( cs: &mut CS, a: &Boolean, @@ -681,7 +710,7 @@ pub(crate) fn enforce_implication_lc< mut cs: CS, premise: &Boolean, implication_lc: L, -) -> Result<(), SynthesisError> { +) { let premise_b = premise.lc(CS::one(), F::ONE); let premise_c = premise_b.clone(); @@ -692,8 +721,6 @@ pub(crate) fn enforce_implication_lc< |_| premise_b, |_| premise_c, ); - - Ok(()) } /// Takes a boolean premise and a function that produces a `LinearCombination` (with same specification as `enforce`). @@ -706,12 +733,10 @@ pub(crate) fn enforce_implication_lc_zero< mut cs: CS, premise: &Boolean, implication_lc: L, -) -> Result<(), SynthesisError> { +) { let premise_a = premise.lc(CS::one(), F::ONE); // premise * implication = zero cs.enforce(|| "implication", |_| premise_a, implication_lc, |lc| lc); - - Ok(()) } /// Adds a constraint to CS, enforcing that the number of true bits in `Boolean` vector `v` @@ -722,10 +747,10 @@ pub(crate) fn enforce_selector_with_premise Result<(), SynthesisError> { - let popcount = popcount_lc::(v)?; +) { + let popcount = popcount_lc::(v); - enforce_implication_lc(cs, premise, |_| popcount) + enforce_implication_lc(cs, premise, |_| popcount); } /// Enforce `premise` implies `implication`. @@ -733,10 +758,10 @@ pub(crate) fn enforce_implication, F: PrimeField>( cs: CS, premise: &Boolean, implication: &Boolean, -) -> Result<(), SynthesisError> { +) { enforce_implication_lc(cs, premise, |_| // One if implication is true, zero otherwise. - implication.lc(CS::one(), F::ONE)) + implication.lc(CS::one(), F::ONE)); } /// Enforce equality of two allocated numbers given an implication premise @@ -745,23 +770,114 @@ pub(crate) fn implies_equal, F: PrimeField>( premise: &Boolean, a: &AllocatedNum, b: &AllocatedNum, -) -> Result<(), SynthesisError> { +) { enforce_implication_lc_zero(cs, premise, |lc| { // Zero iff `a` == `b`. lc + a.get_variable() - b.get_variable() }) } +/// Enforce equality of an allocated number and a constant given an implication premise +pub(crate) fn implies_equal_const, F: PrimeField>( + cs: &mut CS, + premise: &Boolean, + a: &AllocatedNum, + b: F, +) { + enforce_implication_lc_zero(cs, premise, |lc| lc + a.get_variable() - (b, CS::one())) +} + +/// Enforce inequality of two allocated numbers given an implication premise +#[allow(dead_code)] +pub(crate) fn implies_unequal, F: PrimeField>( + cs: &mut CS, + premise: &Boolean, + a: &AllocatedNum, + b: &AllocatedNum, +) -> Result<(), SynthesisError> { + // We know that `a != b` iff `a-b` has an inverse, i.e. that there exists + // `c` such that `c * (a-b) = 1`. Thus, we can add the constraint that there + // must exist `c` such that `c * (a-b) = premise`, enforcing the difference + // only when `premise = 1`; otherwise the constraint is trivially satisfied + // for `c = 0` + let q = cs.alloc( + || "q", + || { + let premise = premise + .get_value() + .ok_or(SynthesisError::AssignmentMissing)?; + if premise { + let a = a.get_value().ok_or(SynthesisError::AssignmentMissing)?; + let b = b.get_value().ok_or(SynthesisError::AssignmentMissing)?; + let inv = (a - b).invert(); + if inv.is_some().into() { + Ok(inv.unwrap()) + } else { + Ok(F::ZERO) + } + } else { + Ok(F::ZERO) + } + }, + )?; + let maybe_inverse = |lc| lc + q; + let implication_lc = |lc| lc + a.get_variable() - b.get_variable(); + let premise = |_| premise.lc(CS::one(), F::ONE); + + cs.enforce(|| "implication", maybe_inverse, implication_lc, premise); + Ok(()) +} + +/// Enforce inequality of two allocated numbers given an implication premise +pub(crate) fn implies_unequal_const, F: PrimeField>( + cs: &mut CS, + premise: &Boolean, + a: &AllocatedNum, + b: F, +) -> Result<(), SynthesisError> { + // We know that `a != b` iff `a-b` has an inverse, i.e. that there exists + // `c` such that `c * (a-b) = 1`. Thus, we can add the constraint that there + // must exist `c` such that `c * (a-b) = premise`, enforcing the difference + // only when `premise = 1`; otherwise the constraint is trivially satisfied + // for `c = 0` + let q = cs.alloc( + || "q", + || { + let premise = premise + .get_value() + .ok_or(SynthesisError::AssignmentMissing)?; + if premise { + let a = a.get_value().ok_or(SynthesisError::AssignmentMissing)?; + let inv = (a - b).invert(); + if inv.is_some().into() { + Ok(inv.unwrap()) + } else { + Ok(F::ZERO) + } + } else { + Ok(F::ZERO) + } + }, + )?; + let maybe_inverse = |lc| lc + q; + let implication_lc = |lc| lc + a.get_variable() - (b, CS::one()); + let premise = |_| premise.lc(CS::one(), F::ONE); + + cs.enforce(|| "implication", maybe_inverse, implication_lc, premise); + Ok(()) +} + /// Enforce equality of two allocated numbers given an implication premise #[allow(dead_code)] pub(crate) fn implies_equal_zero, F: PrimeField>( cs: &mut CS, premise: &Boolean, a: &AllocatedNum, -) -> Result<(), SynthesisError> { +) { enforce_implication_lc_zero(cs, premise, |lc| lc + a.get_variable()) } +/// Use DeMorgan to constrain or. pub(crate) fn or, F: PrimeField>( mut cs: CS, a: &Boolean, @@ -783,13 +899,13 @@ pub(crate) fn must_be_simple_bit(x: &Boolean) -> AllocatedBit { } } -// Allocate Boolean for predicate "num is negative". -// We have that a number is defined to be negative if the parity bit (the -// least significant bit) is odd after doubling, meaning that the field element -// (after doubling) is larger than the underlying prime p that defines the -// field, then a modular reduction must have been carried out, changing the parity that -// should be even (since we multiplied by 2) to odd. In other words, we define -// negative numbers to be those field elements that are larger than p/2. +/// Allocate Boolean for predicate "num is negative". +/// We have that a number is defined to be negative if the parity bit (the +/// least significant bit) is odd after doubling, meaning that the field element +/// (after doubling) is larger than the underlying prime p that defines the +/// field, then a modular reduction must have been carried out, changing the parity that +/// should be even (since we multiplied by 2) to odd. In other words, we define +/// negative numbers to be those field elements that are larger than p/2. pub(crate) fn allocate_is_negative>( mut cs: CS, num: &AllocatedNum, @@ -809,7 +925,7 @@ pub(crate) fn allocate_is_negative>( mod tests { use super::*; - use bellperson::util_cs::test_cs::TestConstraintSystem; + use bellpepper_core::test_cs::TestConstraintSystem; use blstrs::Scalar as Fr; use ff::Field; use proptest::prelude::*; @@ -859,50 +975,93 @@ mod tests { #[test] fn test_implies_equal_zero( p in any::(), - rand_a in (0u64..u64::MAX), - rand_positive in (1u64..u64::MAX), + rand in prop_oneof![ + (0u64..u64::MAX), + Just(0u64) + ] ) { - let test_premise_num = |premise: bool, n, result: bool| { + let test_premise_num = |premise: bool, n| -> bool { let mut cs = TestConstraintSystem::::new(); let num = AllocatedNum::alloc(cs.namespace(|| "num"), || Ok(Fr::from(n))).unwrap(); let pb = Boolean::constant(premise); - let _ = implies_equal_zero(&mut cs.namespace(|| "implies equal zero"), &pb, &num); - assert_eq!(cs.is_satisfied(), result); - + implies_equal_zero(&mut cs.namespace(|| "implies equal zero"), &pb, &num); + cs.is_satisfied() }; - // any premise - test_premise_num(p, 0, true); + prop_assert!(test_premise_num(p, rand) == (!p || (rand == 0))); + } - // false premise, any value - test_premise_num(false, rand_a, true); + #[test] + fn test_implies_equal(p in any::(), (a, b) in prop_oneof![ + any::<(FWrap, FWrap)>(), + any::>().prop_map(|a| (a, a)), + ]) { + let test_a_b = |premise: bool, a, b| -> bool { + let mut cs = TestConstraintSystem::::new(); + let a_num = AllocatedNum::alloc(cs.namespace(|| "a_num"), || Ok(a)).unwrap(); + let b_num = AllocatedNum::alloc(cs.namespace(|| "b_num"), || Ok(b)).unwrap(); + let pb = Boolean::constant(premise); + implies_equal(&mut cs.namespace(|| "implies equal"), &pb, &a_num, &b_num); + cs.is_satisfied() + }; - // true premise, bad values - test_premise_num(true, rand_positive, false); + prop_assert_eq!(test_a_b(p, a.0, b.0), !p || (a.0 == b.0)); } #[test] - fn test_implies_equal(p in any::(), (a, b) in any::<(FWrap, FWrap)>()) { - prop_assume!(a != b); - - let test_a_b = |premise: bool, a, b, result: bool| { + fn test_implies_unequal(p in any::(), (a, b) in prop_oneof![ + any::<(FWrap, FWrap)>(), + any::>().prop_map(|a| (a, a)), + ]) { + let test_a_b = |premise: bool, a, b| -> bool{ let mut cs = TestConstraintSystem::::new(); let a_num = AllocatedNum::alloc(cs.namespace(|| "a_num"), || Ok(a)).unwrap(); let b_num = AllocatedNum::alloc(cs.namespace(|| "b_num"), || Ok(b)).unwrap(); let pb = Boolean::constant(premise); - let _ = implies_equal(&mut cs.namespace(|| "implies equal"), &pb, &a_num, &b_num); - assert_eq!(cs.is_satisfied(), result); + let _ = implies_unequal(&mut cs.namespace(|| "implies equal"), &pb, &a_num, &b_num); + cs.is_satisfied() }; - // any premise - test_a_b(p, a.0, a.0, true); + prop_assert_eq!(test_a_b(p, a.0, b.0), !p || (a.0 != b.0)); + } - // positive case - test_a_b(false, a.0, b.0, true); + #[test] + fn test_implies_unequal_const( + p in any::(), + candidate in any::>(), + target in any::>() + ) { - // negative case - test_a_b(true, a.0, b.0, false); + let test_premise_unequal = |premise: bool, n, t| -> bool { + let mut cs = TestConstraintSystem::::new(); + let num = AllocatedNum::alloc(cs.namespace(|| "num"), || Ok(n)).unwrap(); + let pb = Boolean::constant(premise); + let _ = implies_unequal_const(&mut cs.namespace(|| "implies equal zero"), &pb, &num, t); + cs.is_satisfied() + }; + + prop_assert_eq!(test_premise_unequal(p, candidate.0, target.0), !p || (candidate != target)); + prop_assert_eq!(test_premise_unequal(p, target.0, target.0), !p); + } + + #[test] + fn test_implies_equal_const( + p in any::(), + candidate in any::>(), + target in any::>() + ) { + + let test_premise_equal = |premise: bool, n, t| -> bool { + let mut cs = TestConstraintSystem::::new(); + let num = AllocatedNum::alloc(cs.namespace(|| "num"), || Ok(n)).unwrap(); + let pb = Boolean::constant(premise); + implies_equal_const(&mut cs.namespace(|| "implies equal zero"), &pb, &num, t); + cs.is_satisfied() + }; + + prop_assert_eq!(test_premise_equal(p, candidate.0, target.0), !p || (candidate == target)); + prop_assert!(test_premise_equal(p, target.0, target.0)); } #[test] @@ -922,7 +1081,7 @@ mod tests { v[e] = Boolean::constant(true); }; let alloc_sum = AllocatedNum::alloc(cs.namespace(|| "sum"), || Ok(Fr::from(sum))); - let _ = popcount_equal(&mut cs.namespace(|| "popcount equal"), &v, alloc_sum.unwrap().get_variable()); + popcount_equal(&mut cs.namespace(|| "popcount equal"), &v, alloc_sum.unwrap().get_variable()); assert_eq!(cs.is_satisfied(), result); }; @@ -973,7 +1132,7 @@ mod tests { } let mut cs = TestConstraintSystem::::new(); let p = Boolean::Constant(premise); - let _ = enforce_selector_with_premise(&mut cs.namespace(|| "enforce selector with premise"), &p, &v); + enforce_selector_with_premise(&mut cs.namespace(|| "enforce selector with premise"), &p, &v); assert_eq!(cs.is_satisfied(), result); }; @@ -993,10 +1152,8 @@ mod tests { fn prop_add_constraint((x, y) in any::<(FWrap, FWrap)>()) { let mut cs = TestConstraintSystem::::new(); - let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(x.0)) - .expect("alloc failed"); - let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(y.0)) - .expect("alloc failed"); + let a = AllocatedNum::alloc_infallible(cs.namespace(|| "a"), || x.0); + let b = AllocatedNum::alloc_infallible(cs.namespace(|| "b"), || y.0); let res = add(cs.namespace(|| "a+b"), &a, &b).expect("add failed"); @@ -1013,10 +1170,8 @@ mod tests { let mut cs = TestConstraintSystem::::new(); - let a = AllocatedNum::alloc(cs.namespace(|| "a"), || Ok(x.0)) - .expect("alloc failed"); - let b = AllocatedNum::alloc(cs.namespace(|| "b"), || Ok(y.0)) - .expect("alloc failed"); + let a = AllocatedNum::alloc_infallible(cs.namespace(|| "a"), || x.0); + let b = AllocatedNum::alloc_infallible(cs.namespace(|| "b"), || y.0); let res = sub(cs.namespace(|| "a-b"), &a, &b).expect("subtraction failed"); @@ -1100,7 +1255,7 @@ mod tests { cs.namespace(|| "enforce_implication_lc"), &premise, |_| lc, - ).expect("enforce_implication_lc failed"); + ); prop_assert!((!premise_val || lc_val) == cs.is_satisfied()) @@ -1130,8 +1285,7 @@ mod tests { cs.namespace(|| "enforce_implication_lc_big"), &premise_true, |_| test_lc_big.clone(), - ) - .expect("enforce_implication_lc failed"); + ); assert!(!cs.is_satisfied()); @@ -1150,8 +1304,7 @@ mod tests { cs.namespace(|| "enforce_implication_lc_big_with_false"), &premise_false, |_| test_lc_big, - ) - .expect("enforce_implication_lc failed"); + ); assert!(cs.is_satisfied()); @@ -1177,8 +1330,7 @@ mod tests { cs.namespace(|| "enforce_implication_lc_arb_num"), &premise_true, |_| test_lc_arb_num, - ) - .expect("enforce_implication_lc failed"); + ); assert!(cs.is_satisfied()); } @@ -1207,8 +1359,7 @@ mod tests { cs.namespace(|| "enforce_implication_lc_big"), &premise_true, |_| test_lc_big.clone(), - ) - .expect("enforce_implication_lc failed"); + ); assert!(!cs.is_satisfied()); @@ -1231,8 +1382,7 @@ mod tests { cs.namespace(|| "enforce_implication_lc_one"), &premise_true, |_| test_lc_one.clone(), - ) - .expect("enforce_implication_lc failed"); + ); assert!(!cs.is_satisfied()); @@ -1252,8 +1402,7 @@ mod tests { cs.namespace(|| "enforce_implication_lc_big_with_false"), &premise_false, |_| test_lc_big, - ) - .expect("enforce_implication_lc failed"); + ); assert!(cs.is_satisfied()); @@ -1277,9 +1426,39 @@ mod tests { cs.namespace(|| "enforce_implication_lc_arb_num"), &premise_true, |_| test_lc_arb_num, - ) - .expect("enforce_implication_lc failed"); + ); assert!(cs.is_satisfied()); } + + #[test] + fn test_implies_u64_negative_edge_case() { + let mut cs = TestConstraintSystem::::new(); + + let alloc_num = AllocatedNum::alloc(&mut cs.namespace(|| "num"), || { + // Edge case: 2ˆ64 = 18446744073709551616 + Ok(Fr::from_str_vartime("18446744073709551616").unwrap()) + }) + .unwrap(); + + let t = Boolean::Constant(true); + implies_u64(&mut cs.namespace(|| "enforce u64"), &t, &alloc_num).unwrap(); + assert!(!cs.is_satisfied()); + } + + proptest! { + #[test] + fn test_implies_u64(f in any::>()) { + let mut cs = TestConstraintSystem::::new(); + + let num = AllocatedNum::alloc(cs.namespace(|| "num"), || Ok(f.0)).unwrap(); + + let t = Boolean::Constant(true); + implies_u64(&mut cs.namespace(|| "enforce u64"), &t, &num).unwrap(); + + let f_u64_roundtrip: Fr = f.0.to_u64_unchecked().into(); + let was_u64 = f_u64_roundtrip == f.0; + prop_assert_eq!(was_u64, cs.is_satisfied()); + } + } } diff --git a/src/circuit/gadgets/data.rs b/src/circuit/gadgets/data.rs index d487f21580..0b932e34d0 100644 --- a/src/circuit/gadgets/data.rs +++ b/src/circuit/gadgets/data.rs @@ -1,9 +1,7 @@ -use bellperson::{ - gadgets::{boolean::Boolean, num::AllocatedNum}, - ConstraintSystem, SynthesisError, -}; +use bellpepper_core::{boolean::Boolean, num::AllocatedNum, ConstraintSystem, SynthesisError}; use neptune::{ circuit2::poseidon_hash_allocated as poseidon_hash, + circuit2_witness::poseidon_hash_allocated_witness, poseidon::{Arity, PoseidonConstants}, }; @@ -18,7 +16,7 @@ use crate::z_ptr::{ZContPtr, ZExprPtr}; use super::pointer::{AllocatedContPtr, AllocatedPtr}; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct GlobalAllocations { pub terminal_ptr: AllocatedContPtr, pub error_ptr_cont: AllocatedContPtr, @@ -79,6 +77,7 @@ pub struct GlobalAllocations { pub op2_greater_equal_tag: AllocatedNum, pub lambda_sym: AllocatedPtr, + pub quote_ptr: AllocatedPtr, pub true_num: AllocatedNum, pub false_num: AllocatedNum, @@ -118,83 +117,77 @@ impl GlobalAllocations { &store.strnil(), )?; - let thunk_tag = ExprTag::Thunk.allocate_constant(&mut cs.namespace(|| "thunk_tag"))?; - let cons_tag = ExprTag::Cons.allocate_constant(&mut cs.namespace(|| "cons_tag"))?; - let char_tag = ExprTag::Char.allocate_constant(&mut cs.namespace(|| "char_tag"))?; - let str_tag = ExprTag::Str.allocate_constant(&mut cs.namespace(|| "str_tag"))?; - let num_tag = ExprTag::Num.allocate_constant(&mut cs.namespace(|| "num_tag"))?; - let u64_tag = ExprTag::U64.allocate_constant(&mut cs.namespace(|| "u64_tag"))?; - let comm_tag = ExprTag::Comm.allocate_constant(&mut cs.namespace(|| "comm_tag"))?; - let fun_tag = ExprTag::Fun.allocate_constant(&mut cs.namespace(|| "fun_tag"))?; + let thunk_tag = ExprTag::Thunk.allocate_constant(&mut cs.namespace(|| "thunk_tag")); + let cons_tag = ExprTag::Cons.allocate_constant(&mut cs.namespace(|| "cons_tag")); + let char_tag = ExprTag::Char.allocate_constant(&mut cs.namespace(|| "char_tag")); + let str_tag = ExprTag::Str.allocate_constant(&mut cs.namespace(|| "str_tag")); + let num_tag = ExprTag::Num.allocate_constant(&mut cs.namespace(|| "num_tag")); + let u64_tag = ExprTag::U64.allocate_constant(&mut cs.namespace(|| "u64_tag")); + let comm_tag = ExprTag::Comm.allocate_constant(&mut cs.namespace(|| "comm_tag")); + let fun_tag = ExprTag::Fun.allocate_constant(&mut cs.namespace(|| "fun_tag")); let outermost_cont_tag = - ContTag::Outermost.allocate_constant(&mut cs.namespace(|| "outermost_cont_tag"))?; + ContTag::Outermost.allocate_constant(&mut cs.namespace(|| "outermost_cont_tag")); let lookup_cont_tag = - ContTag::Lookup.allocate_constant(&mut cs.namespace(|| "lookup_cont_tag"))?; - let let_cont_tag = ContTag::Let.allocate_constant(&mut cs.namespace(|| "let_cont_tag"))?; + ContTag::Lookup.allocate_constant(&mut cs.namespace(|| "lookup_cont_tag")); + let let_cont_tag = ContTag::Let.allocate_constant(&mut cs.namespace(|| "let_cont_tag")); let letrec_cont_tag = - ContTag::LetRec.allocate_constant(&mut cs.namespace(|| "letrec_cont_tag"))?; - let tail_cont_tag = - ContTag::Tail.allocate_constant(&mut cs.namespace(|| "tail_cont_tag"))?; + ContTag::LetRec.allocate_constant(&mut cs.namespace(|| "letrec_cont_tag")); + let tail_cont_tag = ContTag::Tail.allocate_constant(&mut cs.namespace(|| "tail_cont_tag")); let call0_cont_tag = - ContTag::Call0.allocate_constant(&mut cs.namespace(|| "call0_cont_tag"))?; - let call_cont_tag = - ContTag::Call.allocate_constant(&mut cs.namespace(|| "call_cont_tag"))?; + ContTag::Call0.allocate_constant(&mut cs.namespace(|| "call0_cont_tag")); + let call_cont_tag = ContTag::Call.allocate_constant(&mut cs.namespace(|| "call_cont_tag")); let call2_cont_tag = - ContTag::Call2.allocate_constant(&mut cs.namespace(|| "call2_cont_tag"))?; - let unop_cont_tag = - ContTag::Unop.allocate_constant(&mut cs.namespace(|| "unop_cont_tag"))?; - let emit_cont_tag = - ContTag::Emit.allocate_constant(&mut cs.namespace(|| "emit_cont_tag"))?; + ContTag::Call2.allocate_constant(&mut cs.namespace(|| "call2_cont_tag")); + let unop_cont_tag = ContTag::Unop.allocate_constant(&mut cs.namespace(|| "unop_cont_tag")); + let emit_cont_tag = ContTag::Emit.allocate_constant(&mut cs.namespace(|| "emit_cont_tag")); let binop_cont_tag = - ContTag::Binop.allocate_constant(&mut cs.namespace(|| "binop_cont_tag"))?; + ContTag::Binop.allocate_constant(&mut cs.namespace(|| "binop_cont_tag")); let binop2_cont_tag = - ContTag::Binop2.allocate_constant(&mut cs.namespace(|| "binop2_cont_tag"))?; - let if_cont_tag = ContTag::If.allocate_constant(&mut cs.namespace(|| "if_cont_tag"))?; - - let op1_car_tag = Op1::Car.allocate_constant(&mut cs.namespace(|| "op1_car_tag"))?; - let op1_cdr_tag = Op1::Cdr.allocate_constant(&mut cs.namespace(|| "op1_cdr_tag"))?; - let op1_commit_tag = - Op1::Commit.allocate_constant(&mut cs.namespace(|| "op1_commit_tag"))?; - let op1_num_tag = Op1::Num.allocate_constant(&mut cs.namespace(|| "op1_num_tag"))?; - let op1_char_tag = Op1::Char.allocate_constant(&mut cs.namespace(|| "op1_char_tag"))?; - let op1_u64_tag = Op1::U64.allocate_constant(&mut cs.namespace(|| "op1_u64_tag"))?; - let op1_comm_tag = Op1::Comm.allocate_constant(&mut cs.namespace(|| "op1_comm_tag"))?; - let op1_open_tag = Op1::Open.allocate_constant(&mut cs.namespace(|| "op1_open_tag"))?; - let op1_secret_tag = - Op1::Secret.allocate_constant(&mut cs.namespace(|| "op1_secret_tag"))?; - let op1_atom_tag = Op1::Atom.allocate_constant(&mut cs.namespace(|| "op1_atom_tag"))?; - let op1_emit_tag = Op1::Emit.allocate_constant(&mut cs.namespace(|| "op1_emit_tag"))?; - let op2_cons_tag = Op2::Cons.allocate_constant(&mut cs.namespace(|| "op2_cons_tag"))?; + ContTag::Binop2.allocate_constant(&mut cs.namespace(|| "binop2_cont_tag")); + let if_cont_tag = ContTag::If.allocate_constant(&mut cs.namespace(|| "if_cont_tag")); + + let op1_car_tag = Op1::Car.allocate_constant(&mut cs.namespace(|| "op1_car_tag")); + let op1_cdr_tag = Op1::Cdr.allocate_constant(&mut cs.namespace(|| "op1_cdr_tag")); + let op1_commit_tag = Op1::Commit.allocate_constant(&mut cs.namespace(|| "op1_commit_tag")); + let op1_num_tag = Op1::Num.allocate_constant(&mut cs.namespace(|| "op1_num_tag")); + let op1_char_tag = Op1::Char.allocate_constant(&mut cs.namespace(|| "op1_char_tag")); + let op1_u64_tag = Op1::U64.allocate_constant(&mut cs.namespace(|| "op1_u64_tag")); + let op1_comm_tag = Op1::Comm.allocate_constant(&mut cs.namespace(|| "op1_comm_tag")); + let op1_open_tag = Op1::Open.allocate_constant(&mut cs.namespace(|| "op1_open_tag")); + let op1_secret_tag = Op1::Secret.allocate_constant(&mut cs.namespace(|| "op1_secret_tag")); + let op1_atom_tag = Op1::Atom.allocate_constant(&mut cs.namespace(|| "op1_atom_tag")); + let op1_emit_tag = Op1::Emit.allocate_constant(&mut cs.namespace(|| "op1_emit_tag")); + let op2_cons_tag = Op2::Cons.allocate_constant(&mut cs.namespace(|| "op2_cons_tag")); let op2_strcons_tag = - Op2::StrCons.allocate_constant(&mut cs.namespace(|| "op2_strcons_tag"))?; - let op2_hide_tag = Op2::Hide.allocate_constant(&mut cs.namespace(|| "op2_hide_tag"))?; - let op2_begin_tag = Op2::Begin.allocate_constant(&mut cs.namespace(|| "op2_begin_tag"))?; - let op2_sum_tag = Op2::Sum.allocate_constant(&mut cs.namespace(|| "op2_sum_tag"))?; - let op2_diff_tag = Op2::Diff.allocate_constant(&mut cs.namespace(|| "op2_diff_tag"))?; + Op2::StrCons.allocate_constant(&mut cs.namespace(|| "op2_strcons_tag")); + let op2_hide_tag = Op2::Hide.allocate_constant(&mut cs.namespace(|| "op2_hide_tag")); + let op2_begin_tag = Op2::Begin.allocate_constant(&mut cs.namespace(|| "op2_begin_tag")); + let op2_sum_tag = Op2::Sum.allocate_constant(&mut cs.namespace(|| "op2_sum_tag")); + let op2_diff_tag = Op2::Diff.allocate_constant(&mut cs.namespace(|| "op2_diff_tag")); let op2_product_tag = - Op2::Product.allocate_constant(&mut cs.namespace(|| "op2_product_tag"))?; + Op2::Product.allocate_constant(&mut cs.namespace(|| "op2_product_tag")); let op2_quotient_tag = - Op2::Quotient.allocate_constant(&mut cs.namespace(|| "op2_quotient_tag"))?; - let op2_modulo_tag = - Op2::Modulo.allocate_constant(&mut cs.namespace(|| "op2_modulo_tag"))?; + Op2::Quotient.allocate_constant(&mut cs.namespace(|| "op2_quotient_tag")); + let op2_modulo_tag = Op2::Modulo.allocate_constant(&mut cs.namespace(|| "op2_modulo_tag")); let op2_numequal_tag = - AllocatedNum::alloc(&mut cs.namespace(|| "op2_numequal_tag"), || { - Ok(Op2::NumEqual.to_field()) - })?; - let op2_less_tag = Op2::Less.allocate_constant(&mut cs.namespace(|| "op2_less_tag"))?; + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "op2_numequal_tag"), || { + Op2::NumEqual.to_field() + }); + let op2_less_tag = Op2::Less.allocate_constant(&mut cs.namespace(|| "op2_less_tag")); let op2_less_equal_tag = - Op2::LessEqual.allocate_constant(&mut cs.namespace(|| "op2_less_equal_tag"))?; + Op2::LessEqual.allocate_constant(&mut cs.namespace(|| "op2_less_equal_tag")); let op2_greater_tag = - Op2::Greater.allocate_constant(&mut cs.namespace(|| "op2_greater_tag"))?; + Op2::Greater.allocate_constant(&mut cs.namespace(|| "op2_greater_tag")); let op2_greater_equal_tag = - Op2::GreaterEqual.allocate_constant(&mut cs.namespace(|| "op2_greater_equal_tag"))?; - let op2_equal_tag = AllocatedNum::alloc(&mut cs.namespace(|| "op2_equal_tag"), || { - Ok(Op2::Equal.to_field()) - })?; + Op2::GreaterEqual.allocate_constant(&mut cs.namespace(|| "op2_greater_equal_tag")); + let op2_equal_tag = + AllocatedNum::alloc_infallible(&mut cs.namespace(|| "op2_equal_tag"), || { + Op2::Equal.to_field() + }); - let c = store.get_constants(); + let c = store.expect_constants(); macro_rules! defsym { ($var:ident, $name:expr, $cname:ident) => { @@ -207,16 +200,17 @@ impl GlobalAllocations { defsym!(t_ptr, "t", t); defsym!(dummy_arg_ptr, "_", dummy); defsym!(lambda_sym, "lambda", lambda); + defsym!(quote_ptr, "quote", quote); - let true_num = allocate_constant(&mut cs.namespace(|| "true"), F::ONE)?; - let false_num = allocate_constant(&mut cs.namespace(|| "false"), F::ZERO)?; - let default_num = allocate_constant(&mut cs.namespace(|| "default"), F::ZERO)?; + let true_num = allocate_constant(&mut cs.namespace(|| "true"), F::ONE); + let false_num = allocate_constant(&mut cs.namespace(|| "false"), F::ZERO); + let default_num = allocate_constant(&mut cs.namespace(|| "default"), F::ZERO); let power2_32_ff = F::pow_vartime(&F::from_u64(2), [32]); - let power2_32_num = allocate_constant(&mut cs.namespace(|| "pow(2,32)"), power2_32_ff)?; + let power2_32_num = allocate_constant(&mut cs.namespace(|| "pow(2,32)"), power2_32_ff); let power2_64_ff = F::pow_vartime(&F::from_u64(2), [64]); - let power2_64_num = allocate_constant(&mut cs.namespace(|| "pow(2,64)"), power2_64_ff)?; + let power2_64_num = allocate_constant(&mut cs.namespace(|| "pow(2,64)"), power2_64_ff); Ok(Self { terminal_ptr, @@ -275,6 +269,7 @@ impl GlobalAllocations { op2_greater_tag, op2_greater_equal_tag, lambda_sym, + quote_ptr, true_num, false_num, default_num, @@ -285,11 +280,15 @@ impl GlobalAllocations { } pub(crate) fn hash_poseidon, F: LurkField, A: Arity>( - cs: CS, + mut cs: CS, preimage: Vec>, constants: &PoseidonConstants, ) -> Result, SynthesisError> { - poseidon_hash(cs, preimage, constants) + if cs.is_witness_generator() { + poseidon_hash_allocated_witness(&mut cs, &preimage, constants) + } else { + poseidon_hash(cs, preimage, constants) + } } impl Ptr { @@ -387,8 +386,8 @@ impl Ptr { pub fn allocate_constant>( cs: &mut CS, val: F, -) -> Result, SynthesisError> { - let allocated = AllocatedNum::::alloc(cs.namespace(|| "allocate"), || Ok(val))?; +) -> AllocatedNum { + let allocated = AllocatedNum::::alloc_infallible(cs.namespace(|| "allocate"), || val); // allocated * 1 = val cs.enforce( @@ -398,14 +397,14 @@ pub fn allocate_constant>( |_| Boolean::Constant(true).lc(CS::one(), val), ); - Ok(allocated) + allocated } impl ExprTag { pub fn allocate_constant>( &self, cs: &mut CS, - ) -> Result, SynthesisError> { + ) -> AllocatedNum { allocate_constant( &mut cs.namespace(|| format!("{self:?} tag")), self.to_field(), @@ -417,7 +416,7 @@ impl ContTag { pub fn allocate_constant>( &self, cs: &mut CS, - ) -> Result, SynthesisError> { + ) -> AllocatedNum { allocate_constant( &mut cs.namespace(|| format!("{self:?} base continuation tag")), self.to_field(), @@ -429,7 +428,7 @@ impl Op1 { pub fn allocate_constant>( &self, cs: &mut CS, - ) -> Result, SynthesisError> { + ) -> AllocatedNum { allocate_constant( &mut cs.namespace(|| format!("{self:?} tag")), self.to_field(), @@ -441,7 +440,7 @@ impl Op2 { pub fn allocate_constant>( &self, cs: &mut CS, - ) -> Result, SynthesisError> { + ) -> AllocatedNum { allocate_constant( &mut cs.namespace(|| format!("{self:?} tag")), self.to_field(), diff --git a/src/circuit/gadgets/hashes.rs b/src/circuit/gadgets/hashes.rs index 01db245ca2..ebb638974f 100644 --- a/src/circuit/gadgets/hashes.rs +++ b/src/circuit/gadgets/hashes.rs @@ -1,19 +1,23 @@ +use std::collections::HashMap; use std::fmt::Debug; -use bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError}; +use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use neptune::circuit2::poseidon_hash_allocated as poseidon_hash; +use neptune::circuit2_witness::{poseidon_hash_allocated_witness, poseidon_hash_scalar_witness}; use crate::circuit::gadgets::pointer::{AllocatedPtr, AsAllocatedHashComponents}; -use crate::field::LurkField; +use crate::config::CONFIG; +use crate::field::{FWrap, LurkField}; use crate::hash::{HashConst, HashConstants}; -use crate::hash_witness::{ConsName, ConsWitness, ContName, ContWitness, HashName, Stub}; +use crate::hash_witness::{ + ConsCircuitWitness, ConsName, ContCircuitWitness, ContName, Digest, HashName, WitnessBlock, +}; +use crate::ptr::ContPtr; use crate::store::Store; -use crate::tag::ExprTag; -use crate::z_ptr::ZExprPtr; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AllocatedHash { preimage: Vec, digest: AllocatedNum, @@ -60,13 +64,15 @@ impl Slot { - pub(crate) witness: &'a VanillaWitness, // Sometimes used for debugging. +#[derive(Debug)] +pub struct AllocatedWitness { + #[allow(dead_code)] + // pub(crate) witness: &'a VanillaWitness, // Sometimes used for debugging. slots: Vec>, } -impl<'a, VanillaWitness, Name: Debug, F: LurkField, PreimageType> - AllocatedWitness<'a, VanillaWitness, Name, AllocatedHash> +impl + AllocatedWitness> { pub fn assert_final_invariants(&self) { if self.slots[0].is_blank() { @@ -87,16 +93,36 @@ impl<'a, VanillaWitness, Name: Debug, F: LurkField, PreimageType> } } -pub(crate) type AllocatedConsWitness<'a, F> = - AllocatedWitness<'a, ConsWitness, ConsName, AllocatedPtrHash>; -pub(crate) type AllocatedContWitness<'a, F> = - AllocatedWitness<'a, ContWitness, ContName, AllocatedNumHash>; +pub(crate) type AllocatedConsWitness<'a, F> = AllocatedWitness>; +pub(crate) type AllocatedContWitness<'a, F> = AllocatedWitness>; + +type HashCircuitWitnessCache = HashMap>, (Vec, F)>; impl AllocatedPtrHash { fn alloc>( cs: &mut CS, constants: &HashConstants, preimage: Vec>, + hash_circuit_witness_cache: Option<&mut HashCircuitWitnessCache>, + ) -> Result { + let constants = constants.constants((2 * preimage.len()).into()); + + let pr: Vec> = preimage + .iter() + .flat_map(|x| x.as_allocated_hash_components()) + .cloned() + .collect(); + + let digest = constants.hash(cs, pr, hash_circuit_witness_cache)?; + + Ok(Self { preimage, digest }) + } + + fn alloc_with_witness>( + cs: &mut CS, + constants: &HashConstants, + preimage: Vec>, + block: &(WitnessBlock, Digest), ) -> Result { let constants = constants.constants((2 * preimage.len()).into()); @@ -106,7 +132,7 @@ impl AllocatedPtrHash { .cloned() .collect(); - let digest = constants.hash(cs, pr)?; + let digest = constants.hash_with_witness(cs, pr, Some(block))?; Ok(Self { preimage, digest }) } @@ -117,71 +143,182 @@ impl AllocatedNumHash { cs: &mut CS, constants: &HashConstants, preimage: Vec>, + hash_circuit_witness_cache: Option<&mut HashCircuitWitnessCache>, + ) -> Result { + let constants = constants.constants(preimage.len().into()); + + let pr: Vec> = preimage.to_vec(); + + let digest = constants.hash(cs, pr, hash_circuit_witness_cache)?; + + Ok(Self { preimage, digest }) + } + fn alloc_with_witness>( + cs: &mut CS, + constants: &HashConstants, + preimage: Vec>, + block: &(WitnessBlock, Digest), ) -> Result { let constants = constants.constants(preimage.len().into()); let pr: Vec> = preimage.to_vec(); - let digest = constants.hash(cs, pr)?; + let digest = constants.hash_with_witness(cs, pr, Some(block))?; Ok(Self { preimage, digest }) } } +impl<'a, F: LurkField> HashConst<'a, F> { + #[allow(dead_code)] + fn cache_hash_witness>( + &self, + cs: &CS, + preimage: Vec, + hash_circuit_witness_cache: &mut HashCircuitWitnessCache, + ) { + macro_rules! hash { + ($c:ident) => {{ + assert!(cs.is_witness_generator()); + let key: Vec> = preimage.iter().map(|f| FWrap(*f)).collect(); + + let _ = hash_circuit_witness_cache + .entry(key) + .or_insert_with(|| poseidon_hash_scalar_witness(&preimage, $c)); + }}; + } + match self { + HashConst::A3(c) => hash!(c), + HashConst::A4(c) => hash!(c), + HashConst::A6(c) => hash!(c), + HashConst::A8(c) => hash!(c), + } + } +} + +impl<'a, F: LurkField> HashConst<'a, F> { + pub fn cache_hash_witness_aux(&self, preimage: Vec) -> (Vec, F) { + macro_rules! hash { + ($c:ident) => {{ + poseidon_hash_scalar_witness(&preimage, $c) + }}; + } + match self { + HashConst::A3(c) => hash!(c), + HashConst::A4(c) => hash!(c), + HashConst::A6(c) => hash!(c), + HashConst::A8(c) => hash!(c), + } + } +} + impl<'a, F: LurkField> HashConst<'a, F> { fn hash>( &self, cs: &mut CS, preimage: Vec>, + hash_circuit_witness_cache: Option<&mut HashCircuitWitnessCache>, + ) -> Result, SynthesisError> { + let witness_block = if cs.is_witness_generator() { + hash_circuit_witness_cache.map(|cache| { + let key = preimage + .iter() + .map(|allocated| FWrap(allocated.get_value().unwrap())) + .collect::>(); + + let cached = cache.get(&key).unwrap(); + cached + }) + } else { + None + }; + + self.hash_with_witness(cs, preimage, witness_block) + } + + fn hash_with_witness>( + &self, + cs: &mut CS, + preimage: Vec>, + circuit_witness: Option<&(WitnessBlock, Digest)>, ) -> Result, SynthesisError> { + macro_rules! hash { + ($c:ident) => { + if cs.is_witness_generator() { + if let Some((aux_buf, res)) = circuit_witness { + cs.extend_aux(aux_buf); + + AllocatedNum::alloc(cs, || Ok(*res)) + } else { + // We have no cache, just allocate the witness. + poseidon_hash_allocated_witness(cs, &preimage, $c) + } + } else { + // CS is not a witness generator, just hash. + poseidon_hash(cs, preimage, $c) + } + }; + } match self { - HashConst::A3(c) => poseidon_hash(cs, preimage, c), - HashConst::A4(c) => poseidon_hash(cs, preimage, c), - HashConst::A6(c) => poseidon_hash(cs, preimage, c), - HashConst::A8(c) => poseidon_hash(cs, preimage, c), + HashConst::A3(c) => hash!(c), + HashConst::A4(c) => hash!(c), + HashConst::A6(c) => hash!(c), + HashConst::A8(c) => hash!(c), } } } impl<'a, F: LurkField> AllocatedConsWitness<'a, F> { pub fn from_cons_witness>( - cs0: &mut CS, + cs: &mut CS, s: &Store, - cons_witness: &'a ConsWitness, + cons_circuit_witness: &'a ConsCircuitWitness, ) -> Result { + let cons_witness = cons_circuit_witness.hash_witness; let mut slots = Vec::with_capacity(cons_witness.slots.len()); - for (i, (name, p)) in cons_witness.slots.iter().enumerate() { - let cs = &mut cs0.namespace(|| format!("slot-{i}")); - let (car_ptr, cdr_ptr, cons_hash) = match p { - Stub::Dummy => ( - Some(ZExprPtr::from_parts(ExprTag::Nil, F::ZERO)), - Some(ZExprPtr::from_parts(ExprTag::Nil, F::ZERO)), - None, - ), - Stub::Blank => (None, None, None), - Stub::Value(hash) => ( - s.hash_expr(&hash.car), - s.hash_expr(&hash.cdr), - s.hash_expr(&hash.cons), - ), + let names_and_ptrs = cons_circuit_witness.names_and_ptrs(s); + let cons_constants: HashConst<'_, F> = s.poseidon_constants().constants(4.into()); + + let circuit_witness_blocks = + if cs.is_witness_generator() && CONFIG.witness_generation.precompute_neptune { + Some(cons_circuit_witness.circuit_witness_blocks(s, cons_constants)) + } else { + None }; + for (i, (name, spr)) in names_and_ptrs.iter().enumerate() { + let cs = &mut cs.namespace(|| format!("slot-{i}")); + let allocated_car = AllocatedPtr::alloc(&mut cs.namespace(|| "car"), || { - car_ptr.ok_or(SynthesisError::AssignmentMissing) + spr.as_ref() + .map(|x| x.car) + .ok_or(SynthesisError::AssignmentMissing) })?; let allocated_cdr = AllocatedPtr::alloc(&mut cs.namespace(|| "cdr"), || { - cdr_ptr.ok_or(SynthesisError::AssignmentMissing) + spr.as_ref() + .map(|x| x.cdr) + .ok_or(SynthesisError::AssignmentMissing) })?; - let allocated_hash = AllocatedPtrHash::alloc( - &mut cs.namespace(|| "cons"), - s.poseidon_constants(), - vec![allocated_car, allocated_cdr], - )?; + let allocated_hash = if let Some(blocks) = circuit_witness_blocks { + AllocatedPtrHash::alloc_with_witness( + &mut cs.namespace(|| "cons"), + s.poseidon_constants(), + vec![allocated_car, allocated_cdr], + &blocks[i], + )? + } else { + AllocatedPtrHash::alloc( + &mut cs.namespace(|| "cons"), + s.poseidon_constants(), + vec![allocated_car, allocated_cdr], + None, + )? + }; - if cons_hash.is_some() { + if spr.is_some() { slots.push(Slot::new(*name, allocated_hash)); } else { slots.push(Slot::new_dummy(allocated_hash)); @@ -189,8 +326,7 @@ impl<'a, F: LurkField> AllocatedConsWitness<'a, F> { } Ok(Self { - witness: cons_witness, - slots, + slots: slots.to_vec(), }) } @@ -208,7 +344,7 @@ impl<'a, F: LurkField> AllocatedConsWitness<'a, F> { } = &self.slots[index]; if !expect_dummy { match allocated_name { - Err(_) => panic!("requested {:?} but found a dummy allocation", name), + Err(_) => panic!("requested {name:?} but found a dummy allocation"), Ok(alloc_name) => assert_eq!( name, *alloc_name, "requested and allocated names don't match." @@ -225,36 +361,59 @@ impl<'a, F: LurkField> AllocatedConsWitness<'a, F> { } impl<'a, F: LurkField> AllocatedContWitness<'a, F> { + // Currently unused, but not necessarily useless. + #[allow(dead_code)] + fn make_hash_cache>( + cs: &CS, + names_and_ptrs: &[(ContName, (Option>, Option>))], + hash_constants: HashConst<'_, F>, + ) -> Option> { + if cs.is_witness_generator() { + let mut c = HashMap::new(); + + let results = names_and_ptrs + .iter() + .map(|(_, (_, p))| { + let preimage = p.as_ref().unwrap(); + ( + preimage.clone(), + hash_constants.cache_hash_witness_aux(preimage.to_vec()), + ) + }) + .collect::>(); + + for (preimage, x) in results.iter() { + let key: Vec> = preimage.iter().map(|f| FWrap(*f)).collect(); + c.insert(key, x.clone()); + } + Some(c) + } else { + None + } + } + pub fn from_cont_witness>( - cs0: &mut CS, + cs: &mut CS, s: &Store, - cont_witness: &'a ContWitness, + cont_circuit_witness: &'a ContCircuitWitness, ) -> Result { + let cont_witness = cont_circuit_witness.hash_witness; let mut slots = Vec::with_capacity(cont_witness.slots.len()); - for (i, (name, p)) in cont_witness.slots.iter().enumerate() { - let cs = &mut cs0.namespace(|| format!("slot-{i}")); - let (cont_ptr, components) = match p { - Stub::Dummy => ( - None, - Some([ - F::ZERO, - F::ZERO, - F::ZERO, - F::ZERO, - F::ZERO, - F::ZERO, - F::ZERO, - F::ZERO, - ]), - ), - Stub::Blank => (None, None), - Stub::Value(cont) => ( - Some(cont.cont_ptr), - s.get_hash_components_cont(&cont.cont_ptr), - ), + let names_and_ptrs = cont_circuit_witness.names_and_ptrs(s); + let cont_constants: HashConst<'_, F> = s.poseidon_constants().constants(8.into()); + + let circuit_witness_blocks = + if cs.is_witness_generator() && CONFIG.witness_generation.precompute_neptune { + Some(cont_circuit_witness.circuit_witness_blocks(s, cont_constants)) + } else { + None }; + for (i, (name, spr)) in names_and_ptrs.iter().enumerate() { + let cs = &mut cs.namespace(|| format!("slot-{i}")); + + let components = spr.as_ref().map(|spr| spr.components); let allocated_components = if let Some(components) = components { components .iter() @@ -279,23 +438,30 @@ impl<'a, F: LurkField> AllocatedContWitness<'a, F> { .collect::>() }; - let allocated_hash = AllocatedNumHash::alloc( - &mut cs.namespace(|| "cont"), - s.poseidon_constants(), - allocated_components, - )?; + let allocated_hash = if let Some(blocks) = circuit_witness_blocks { + AllocatedNumHash::alloc_with_witness( + &mut cs.namespace(|| "cont"), + s.poseidon_constants(), + allocated_components, + &blocks[i], + )? + } else { + AllocatedNumHash::alloc( + &mut cs.namespace(|| "cont"), + s.poseidon_constants(), + allocated_components, + None, + )? + }; - if cont_ptr.is_some() { + if spr.as_ref().map(|spr| spr.cont).is_some() { slots.push(Slot::new(*name, allocated_hash)); } else { slots.push(Slot::new_dummy(allocated_hash)); } } - Ok(Self { - witness: cont_witness, - slots, - }) + Ok(Self { slots }) } pub fn get_components( @@ -312,8 +478,7 @@ impl<'a, F: LurkField> AllocatedContWitness<'a, F> { if !expect_dummy { match allocated_name { Err(_) => { - dbg!(&self.witness); - panic!("requested {:?} but found a dummy allocation", name) + panic!("requested {name:?} but found a dummy allocation") } Ok(alloc_name) => { assert_eq!( diff --git a/src/circuit/gadgets/macros.rs b/src/circuit/gadgets/macros.rs index c1c1015ce6..bc59127a6f 100644 --- a/src/circuit/gadgets/macros.rs +++ b/src/circuit/gadgets/macros.rs @@ -119,7 +119,7 @@ macro_rules! implies_equal { }), $condition, &equal, - )?; + ); }}; } @@ -154,7 +154,7 @@ macro_rules! implies { }), $condition, $implication, - )?; + ); }}; } diff --git a/src/circuit/gadgets/mod.rs b/src/circuit/gadgets/mod.rs index 3a547dace6..c23515901a 100644 --- a/src/circuit/gadgets/mod.rs +++ b/src/circuit/gadgets/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod macros; pub(crate) mod case; +pub mod circom; pub mod constraints; pub mod data; pub(crate) mod hashes; diff --git a/src/circuit/gadgets/pointer.rs b/src/circuit/gadgets/pointer.rs index 8b2db60e3b..1a3243476a 100644 --- a/src/circuit/gadgets/pointer.rs +++ b/src/circuit/gadgets/pointer.rs @@ -1,22 +1,20 @@ use std::fmt::Debug; -use bellperson::{ - gadgets::{boolean::Boolean, num::AllocatedNum}, - ConstraintSystem, SynthesisError, -}; +use bellpepper_core::{boolean::Boolean, num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::PrimeField; -use crate::hash::IntoHashComponents; use crate::{ cont::Continuation, expr::{Expression, Thunk}, field::LurkField, + hash::IntoHashComponents, hash_witness::{ConsName, ContName}, ptr::{ContPtr, Ptr}, + state::initial_lurk_state, store::Store, tag::{ExprTag, Tag}, writer::Write, - z_ptr::{ZContPtr, ZExprPtr}, + z_ptr::{ZContPtr, ZExprPtr, ZPtr}, }; use super::{ @@ -55,12 +53,12 @@ impl Debug for AllocatedPtr { } impl AllocatedPtr { - pub fn alloc>( + pub fn alloc, T: Tag>( cs: &mut CS, value: Fo, ) -> Result where - Fo: FnOnce() -> Result, SynthesisError>, + Fo: FnOnce() -> Result, SynthesisError>, { let mut hash = None; let alloc_tag = AllocatedNum::alloc(&mut cs.namespace(|| "tag"), || { @@ -84,7 +82,7 @@ impl AllocatedPtr { tag: F, alloc_hash: AllocatedNum, ) -> Result { - let alloc_tag = allocate_constant(&mut cs.namespace(|| "tag"), tag)?; + let alloc_tag = allocate_constant(&mut cs.namespace(|| "tag"), tag); Ok(AllocatedPtr { tag: alloc_tag, @@ -96,8 +94,8 @@ impl AllocatedPtr { cs: &mut CS, value: ZExprPtr, ) -> Result { - let alloc_tag = allocate_constant(&mut cs.namespace(|| "tag"), value.tag_field())?; - let alloc_hash = allocate_constant(&mut cs.namespace(|| "hash"), *value.value())?; + let alloc_tag = allocate_constant(&mut cs.namespace(|| "tag"), value.tag_field()); + let alloc_hash = allocate_constant(&mut cs.namespace(|| "hash"), *value.value()); Ok(AllocatedPtr { tag: alloc_tag, @@ -193,20 +191,19 @@ impl AllocatedPtr { cs: &mut CS, premise: &Boolean, other: &AllocatedPtr, - ) -> Result<(), SynthesisError> { + ) { implies_equal( &mut cs.namespace(|| "implies tag equal"), premise, self.tag(), other.tag(), - )?; + ); implies_equal( &mut cs.namespace(|| "implies hash equal"), premise, self.hash(), other.hash(), - )?; - Ok(()) + ); } pub fn is_nil>( @@ -258,9 +255,10 @@ impl AllocatedPtr { } pub fn fetch_and_write_str(&self, store: &Store) -> String { - self.ptr(store) - .map(|a| a.fmt_to_string(store)) - .unwrap_or_else(|| "".to_string()) + self.ptr(store).map_or_else( + || "".to_string(), + |a| a.fmt_to_string(store, initial_lurk_state()), + ) } pub fn allocate_thunk_components_unconstrained>( @@ -344,7 +342,7 @@ impl AllocatedPtr { implies!(cs, not_dummy, &cons_is_real); if not_dummy.get_value().unwrap_or(false) && !cons_is_real.get_value().unwrap_or(true) { - dbg!(name); + tracing::debug!("{:?}", name); panic!("uh oh!"); } @@ -502,7 +500,7 @@ impl AllocatedPtr { store: &Store, boolean: &Boolean, ) -> Result, SynthesisError> { - let c = store.get_constants(); + let c = store.expect_constants(); AllocatedPtr::pick_const( cs.namespace(|| "allocated lurk bool"), boolean, @@ -598,8 +596,8 @@ impl AllocatedContPtr { cs: &mut CS, value: ZContPtr, ) -> Result { - let alloc_tag = allocate_constant(&mut cs.namespace(|| "tag"), value.tag_field())?; - let alloc_hash = allocate_constant(&mut cs.namespace(|| "hash"), *value.value())?; + let alloc_tag = allocate_constant(&mut cs.namespace(|| "tag"), value.tag_field()); + let alloc_hash = allocate_constant(&mut cs.namespace(|| "hash"), *value.value()); Ok(AllocatedContPtr { tag: alloc_tag, @@ -693,9 +691,10 @@ impl AllocatedContPtr { } pub fn fetch_and_write_cont_str(&self, store: &Store) -> String { - self.get_cont_ptr(store) - .map(|a| a.fmt_to_string(store)) - .unwrap_or_else(|| "no cont ptr".to_string()) + self.get_cont_ptr(store).map_or_else( + || "no cont ptr".to_string(), + |a| a.fmt_to_string(store, initial_lurk_state()), + ) } /// Takes two allocated numbers (`a`, `b`) and returns `a` if the condition is true, and `b` otherwise. @@ -743,6 +742,31 @@ impl AllocatedContPtr { } } + pub fn construct>( + mut cs: CS, + store: &Store, + cont_tag: &AllocatedNum, + components: &[&dyn AsAllocatedHashComponents; 4], + ) -> Result { + let components = components + .iter() + .flat_map(|c| c.as_allocated_hash_components()) + .cloned() + .collect(); + + let hash = hash_poseidon( + cs.namespace(|| "Continuation"), + components, + store.poseidon_constants().c8(), + )?; + + let cont = AllocatedContPtr { + tag: cont_tag.clone(), + hash, + }; + Ok(cont) + } + pub fn construct_named>( mut cs: CS, name: ContName, @@ -785,7 +809,7 @@ impl AllocatedContPtr { &mut cs.namespace(|| format!("not_dummy implies real cont {:?}", &name)), not_dummy, &acc.expect("acc was never initialized"), - )?; + ); let cont = AllocatedContPtr { tag: cont_tag.clone(), diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 5860078f59..825d4d4c62 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -1,4 +1,3 @@ -#![allow(clippy::too_many_arguments)] use crate::field::LurkField; use crate::eval::IO; diff --git a/src/cli/backend.rs b/src/cli/backend.rs new file mode 100644 index 0000000000..d981b29f7a --- /dev/null +++ b/src/cli/backend.rs @@ -0,0 +1,49 @@ +use anyhow::{bail, Result}; + +use crate::field::LanguageField; + +pub enum Backend { + Nova, + SnarkPackPlus, +} + +impl std::fmt::Display for Backend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Nova => write!(f, "Nova"), + Self::SnarkPackPlus => write!(f, "SnarkPack+"), + } + } +} + +impl Backend { + pub(crate) fn default_field(&self) -> LanguageField { + match self { + Self::Nova => LanguageField::Pallas, + Self::SnarkPackPlus => LanguageField::BLS12_381, + } + } + + fn compatible_fields(&self) -> Vec { + use LanguageField::{Pallas, Vesta, BLS12_381}; + match self { + Self::Nova => vec![Pallas, Vesta], + Self::SnarkPackPlus => vec![BLS12_381], + } + } + + pub(crate) fn validate_field(&self, field: &LanguageField) -> Result<()> { + let compatible_fields = self.compatible_fields(); + if !compatible_fields.contains(field) { + bail!( + "Backend {self} is incompatible with field {field}. Compatible fields are:\n {}", + compatible_fields + .iter() + .map(|f| f.to_string()) + .collect::>() + .join(", ") + ) + } + Ok(()) + } +} diff --git a/src/cli/circom.rs b/src/cli/circom.rs new file mode 100644 index 0000000000..4a44fbd6d9 --- /dev/null +++ b/src/cli/circom.rs @@ -0,0 +1,132 @@ +use std::{fs, path::Path, process::Command}; + +#[cfg(unix)] +use std::os::unix::fs::PermissionsExt; + +use ansi_term::Colour::{Green, Red}; +use anyhow::{bail, Result}; +use camino::Utf8PathBuf; + +use crate::cli::paths::{circom_binary_path, circom_dir}; + +const CIRCOM_VERSION: &str = "2.1.6"; + +#[cfg(target_arch = "wasm32")] +fn download_circom_binary(_path: impl AsRef) -> Result { + bail!("wasm does not support downloading") +} + +#[cfg(not(target_arch = "wasm32"))] +fn download_circom_binary(path: impl AsRef) -> Result { + use std::io::Write; + + let url = match std::env::consts::OS { + "linux" => format!("https://github.com/iden3/circom/releases/download/v{CIRCOM_VERSION}/circom-linux-amd64"), + "macos" => format!("https://github.com/iden3/circom/releases/download/v{CIRCOM_VERSION}/circom-macos-amd64"), + "windows" => { + format!("https://github.com/iden3/circom/releases/download/v{CIRCOM_VERSION}/circom-windows-amd64.exe") + } + os => { + bail!("Unsupported OS: {os}. Unable to automatically download the necessary circom binary, please manually download Circom v{CIRCOM_VERSION} to `.lurk/circom/circom`"); + } + }; + + let response = reqwest::blocking::get(url)?.bytes()?; + let mut out = fs::File::create(path.as_ref())?; + out.write_all(&response)?; + + #[cfg(unix)] + fs::set_permissions(path.as_ref(), fs::Permissions::from_mode(0o755))?; + + Ok(Command::new(path.as_ref().as_os_str())) +} + +/// We try to find the circom binary at `/circom`, +/// where `` can be configured via the config file, +/// a environment variable, or through a CLI argument, in that order. +/// +/// We *do not* consider the case where the user already has some +/// `circom` binary available in their `$PATH`. The user will have two +/// possibly conflicting circom binaries floating around. However, things +/// should be kept separate as Lurk will never touch the user binary +/// and the user should never manually call the Lurk Circom binary. +/// +/// Whatever path is chosen, we then test if the `circom` binary +/// exists. If it does, we return the path. Otherwise we download +/// the binary to the location and return the path. +fn get_circom_binary() -> Result { + let circom_path = circom_binary_path(); + + let output = Command::new(&circom_path).arg("--version").output(); + + let success = match output { + Ok(output) => { + // TODO: in future add back checksum check? + output.status.success() + && String::from_utf8_lossy(&output.stdout).contains(CIRCOM_VERSION) + } + Err(_) => false, + }; + + if success { + Ok(Command::new(circom_path)) + } else { + download_circom_binary(circom_path) + } +} + +pub(crate) fn create_circom_gadget(circom_folder: Utf8PathBuf, name: String) -> Result<()> { + let circom_gadget = circom_dir().join(&name); + let circom_file = circom_folder.join(&name).with_extension("circom"); + + // TODO: support for other fields + let default_field = "vesta"; + let field = if let Ok(lurk_field) = std::env::var("LURK_FIELD") { + // FG: The prime is actually the reverse of the field in $LURK_FIELD, + // because circom and lurk have different semantics about which field should be specified + // (circom wants the base field and lurk the scalar field). + match lurk_field.as_str() { + "BLS12-381" => "bn128", + "PALLAS" => "vesta", + "VESTA" => "pallas", + _ => bail!("unsupported field"), + } + } else { + default_field + }; + + println!("Running circom binary to generate r1cs and witness files to {circom_gadget:?}"); + fs::create_dir_all(&circom_gadget)?; + let output = get_circom_binary()? + .args(&[ + circom_file, + "--r1cs".into(), + "--wasm".into(), + "--output".into(), + circom_gadget.clone(), + "--prime".into(), + field.into(), + ]) + .output() + .expect("circom failed"); + + if !output.status.success() { + println!( + "{} Please check that your input files are correct,", + Red.bold().paint("Circom failed.") + ); + println!(" and refer to the circom stderr output for further information:\n"); + bail!("{}", String::from_utf8_lossy(&output.stderr)); + } + + // get out `name`_js/`name`.wasm and `name`.r1cs + // and put them in /`name`/* + fs::copy( + circom_gadget.join(format!("{}_js/{}.wasm", &name, &name)), + circom_gadget.join(format!("{}.wasm", &name)), + )?; + fs::remove_dir_all(circom_gadget.join(format!("{}_js", &name)))?; + + println!("{}", Green.bold().paint("Circom success")); + Ok(()) +} diff --git a/src/cli/lurk_proof.rs b/src/cli/lurk_proof.rs index 3522c6e04e..21676d7a5f 100644 --- a/src/cli/lurk_proof.rs +++ b/src/cli/lurk_proof.rs @@ -1,3 +1,5 @@ +use ::nova::traits::Group; +use abomonation::Abomonation; use anyhow::Result; use pasta_curves::pallas::Scalar; use serde::{Deserialize, Serialize}; @@ -6,7 +8,7 @@ use crate::{ coprocessor::Coprocessor, eval::lang::{Coproc, Lang}, field::LurkField, - proof::nova::{self, CurveCycleEquipped}, + proof::nova::{self, CurveCycleEquipped, G1, G2}, public_parameters::public_params, z_ptr::{ZContPtr, ZExprPtr}, z_store::ZStore, @@ -48,6 +50,8 @@ impl HasFieldModulus for LurkProofMeta { pub(crate) enum LurkProof<'a, F: CurveCycleEquipped> where Coproc: Coprocessor, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, { Nova { proof: nova::Proof<'a, F, Coproc>, @@ -62,6 +66,8 @@ where impl<'a, F: CurveCycleEquipped> HasFieldModulus for LurkProof<'a, F> where Coproc: Coprocessor, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, { fn field_modulus() -> String { F::MODULUS.to_owned() @@ -78,6 +84,8 @@ impl LurkProofMeta { impl<'a, F: CurveCycleEquipped + Serialize> LurkProof<'a, F> where Coproc: Coprocessor, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, { #[inline] pub(crate) fn persist(self, proof_key: &str) -> Result<()> { @@ -96,8 +104,8 @@ impl<'a> LurkProof<'a, Scalar> { rc, lang, } => { - log::info!("Loading public parameters"); - let pp = public_params(rc, std::sync::Arc::new(lang), &public_params_dir())?; + tracing::info!("Loading public parameters"); + let pp = public_params(rc, true, std::sync::Arc::new(lang), &public_params_dir())?; Ok(proof.verify(&pp, num_steps, &public_inputs, &public_outputs)?) } } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index aa76e10936..a30e4add6f 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,8 +1,10 @@ +pub mod backend; +mod circom; mod commitment; mod field_data; mod lurk_proof; -mod paths; -mod repl; +pub mod paths; +pub mod repl; use anyhow::{bail, Context, Result}; use camino::Utf8PathBuf; @@ -21,9 +23,11 @@ use crate::{ use crate::cli::{ paths::set_lurk_dirs, - repl::{validate_non_zero, Backend, Repl}, + repl::{validate_non_zero, Repl}, }; +use self::backend::Backend; + const DEFAULT_LIMIT: usize = 100_000_000; const DEFAULT_RC: usize = 10; const DEFAULT_BACKEND: Backend = Backend::Nova; @@ -43,6 +47,11 @@ enum Command { Repl(ReplArgs), /// Verifies a Lurk proof Verify(VerifyArgs), + /// Instantiates a new circom gadget to interface with bellperson. + /// + /// See `lurk circom --help` for more details + #[command(verbatim_doc_comment)] + Circom(CircomArgs), } #[derive(Args, Debug)] @@ -90,11 +99,15 @@ struct LoadArgs { /// Path to commitments directory #[clap(long, value_parser)] commits_dir: Option, + + /// Path to circom directory + #[clap(long, value_parser)] + circom_dir: Option, } #[derive(Parser, Debug)] struct LoadCli { - #[clap(value_parser)] + #[clap(value_parser = parse_filename)] lurk_file: Utf8PathBuf, #[clap(long, value_parser)] @@ -126,6 +139,9 @@ struct LoadCli { #[clap(long, value_parser)] commits_dir: Option, + + #[clap(long, value_parser)] + circom_dir: Option, } impl LoadArgs { @@ -142,6 +158,7 @@ impl LoadArgs { public_params_dir: self.public_params_dir, proofs_dir: self.proofs_dir, commits_dir: self.commits_dir, + circom_dir: self.circom_dir, } } } @@ -187,6 +204,10 @@ struct ReplArgs { /// Path to commitments directory #[clap(long, value_parser)] commits_dir: Option, + + /// Path to circom directory + #[clap(long, value_parser)] + circom_dir: Option, } #[derive(Parser, Debug)] @@ -220,6 +241,9 @@ struct ReplCli { #[clap(long, value_parser)] commits_dir: Option, + + #[clap(long, value_parser)] + circom_dir: Option, } impl ReplArgs { @@ -235,6 +259,7 @@ impl ReplArgs { public_params_dir: self.public_params_dir, proofs_dir: self.proofs_dir, commits_dir: self.commits_dir, + circom_dir: self.circom_dir, } } } @@ -256,6 +281,14 @@ fn parse_field(field_str: &String) -> Result { } } +fn parse_filename(file: &str) -> Result { + if file == "help" { + bail!("help is not a valid filename. printing help console instead"); + } + let path: Utf8PathBuf = file.into(); + Ok(path) +} + fn get_parsed_usize( param_name: &str, arg: &Option, @@ -316,9 +349,8 @@ fn get_store serde::de::Deserialize<'a>>( macro_rules! new_repl { ( $cli: expr, $rc: expr, $limit: expr, $field: path, $backend: expr ) => {{ - let mut store = get_store(&$cli.zstore).with_context(|| "reading store from file")?; - let env = store.nil(); - Repl::<$field>::new(store, env, $rc, $limit, $backend) + let store = get_store(&$cli.zstore).with_context(|| "reading store from file")?; + Repl::<$field>::new(store, $rc, $limit, $backend) }}; } @@ -334,12 +366,13 @@ impl ReplCli { }}; } let config = get_config(&self.config)?; - log::info!("Configured variables: {:?}", config); + tracing::info!("Configured variables: {:?}", config); set_lurk_dirs( &config, &self.public_params_dir, &self.proofs_dir, &self.commits_dir, + &self.circom_dir, ); let rc = get_parsed_usize("rc", &self.rc, &config, DEFAULT_RC)?; let limit = get_parsed_usize("limit", &self.limit, &config, DEFAULT_LIMIT)?; @@ -384,12 +417,13 @@ impl LoadCli { }}; } let config = get_config(&self.config)?; - log::info!("Configured variables: {:?}", config); + tracing::info!("Configured variables: {:?}", config); set_lurk_dirs( &config, &self.public_params_dir, &self.proofs_dir, &self.commits_dir, + &self.circom_dir, ); let rc = get_parsed_usize("rc", &self.rc, &config, DEFAULT_RC)?; let limit = get_parsed_usize("limit", &self.limit, &config, DEFAULT_LIMIT)?; @@ -440,6 +474,34 @@ struct VerifyArgs { proofs_dir: Option, } +/// To setup a new circom gadget ``, place your circom files in a designated folder and +/// create a file called `.circom`. `/.circom` is the input file +/// for the `circom` binary; in this file you must declare your circom main component. +/// +/// Then run `lurk circom --name ` to instantiate a new gadget ``. +/// The new components are stored in `//*`. +#[derive(Args, Debug)] +struct CircomArgs { + /// Path to the circom folder to be integrated. + /// Lurk will look for `/.circom` + /// as the input file for the `circom` binary. + #[clap(value_parser)] + #[arg(verbatim_doc_comment)] + circom_folder: Utf8PathBuf, + + /// The name of the circom gadget (the name cannot be `main`, see circom documentation) + #[clap(long, value_parser)] + name: String, + + /// Config file, containing the lowest precedence parameters + #[clap(long, value_parser)] + config: Option, + + /// Path to proofs directory + #[clap(long, value_parser)] + circom_dir: Option, +} + impl Cli { fn run(self) -> Result<()> { match self.command { @@ -449,20 +511,35 @@ impl Cli { Command::Verify(verify_args) => { use crate::cli::lurk_proof::LurkProof; let config = get_config(&verify_args.config)?; - log::info!("Configured variables: {:?}", config); + tracing::info!("Configured variables: {:?}", config); set_lurk_dirs( &config, &verify_args.public_params_dir, &verify_args.proofs_dir, &None, + &None, ); LurkProof::verify_proof(&verify_args.proof_id)?; Ok(()) } + Command::Circom(circom_args) => { + use crate::cli::circom::create_circom_gadget; + if circom_args.name == "main" { + bail!("Circom gadget name cannot be `main`, see circom documentation") + } + + let config = get_config(&circom_args.config)?; + tracing::info!("Configured variables: {:?}", config); + set_lurk_dirs(&config, &None, &None, &None, &circom_args.circom_dir); + + create_circom_gadget(circom_args.circom_folder, circom_args.name)?; + Ok(()) + } } } } +// TODO: deal with `clap_verbosity_flag` and set logger here instead? /// Parses CLI arguments and continues the program flow accordingly pub fn parse_and_run() -> Result<()> { if let Ok(cli) = Cli::try_parse() { diff --git a/src/cli/paths.rs b/src/cli/paths.rs index 785d971c77..5469ded3aa 100644 --- a/src/cli/paths.rs +++ b/src/cli/paths.rs @@ -13,6 +13,7 @@ pub(crate) struct LurkDirs { public_params: Utf8PathBuf, proofs: Utf8PathBuf, commits: Utf8PathBuf, + circom: Utf8PathBuf, } #[cfg(not(target_arch = "wasm32"))] @@ -42,8 +43,26 @@ pub(crate) fn proofs_default_dir() -> Utf8PathBuf { Utf8PathBuf::from(".lurk/public_params") } +#[cfg(not(target_arch = "wasm32"))] +pub(crate) fn commits_default_dir() -> Utf8PathBuf { + let home = home::home_dir().unwrap(); + Utf8PathBuf::from_path_buf(home.join(".lurk/commits")).expect("path contains invalid Unicode") +} + +#[cfg(target_arch = "wasm32")] pub(crate) fn commits_default_dir() -> Utf8PathBuf { - Utf8PathBuf::from("commits") + Utf8PathBuf::from(".lurk/commits") +} + +#[cfg(not(target_arch = "wasm32"))] +pub(crate) fn circom_default_dir() -> Utf8PathBuf { + let home = home::home_dir().unwrap(); + Utf8PathBuf::from_path_buf(home.join(".lurk/circom")).expect("path contains invalid Unicode") +} + +#[cfg(target_arch = "wasm32")] +pub(crate) fn circom_default_dir() -> Utf8PathBuf { + Utf8PathBuf::from(".lurk/circom") } pub(crate) fn public_params_dir() -> Utf8PathBuf { @@ -70,8 +89,21 @@ pub(crate) fn commits_dir() -> Utf8PathBuf { .to_owned() } -fn lurk_leaf_dirs() -> [Utf8PathBuf; 3] { - [proofs_dir(), commits_dir(), public_params_dir()] +pub(crate) fn circom_dir() -> Utf8PathBuf { + LURK_DIRS + .get() + .expect("failed to initialize beforehand with `set_lurk_dirs()`") + .circom + .to_owned() +} + +fn lurk_leaf_dirs() -> [Utf8PathBuf; 4] { + [ + proofs_dir(), + commits_dir(), + public_params_dir(), + circom_dir(), + ] } pub(crate) fn set_lurk_dirs( @@ -79,6 +111,7 @@ pub(crate) fn set_lurk_dirs( public_params_dir: &Option, proofs_dir: &Option, commits_dir: &Option, + circom_dir: &Option, ) { let get_path = |given_path: &Option, config_key: &str, default: Utf8PathBuf| { given_path.clone().unwrap_or_else(|| { @@ -95,11 +128,13 @@ pub(crate) fn set_lurk_dirs( ); let proofs = get_path(proofs_dir, "proofs", proofs_default_dir()); let commits = get_path(commits_dir, "commits", commits_default_dir()); + let circom = get_path(circom_dir, "circom", circom_default_dir()); LURK_DIRS.get_or_init(|| LurkDirs { public_params, proofs, commits, + circom, }); create_lurk_dirs().unwrap(); @@ -133,3 +168,7 @@ pub(crate) fn proof_meta_path(name: &str) -> Utf8PathBuf { .join(Utf8Path::new(name)) .with_extension("meta") } + +pub(crate) fn circom_binary_path() -> Utf8PathBuf { + circom_dir().join("circom") +} diff --git a/src/cli/repl.rs b/src/cli/repl.rs index 5c697f4118..f1ac9cbeb7 100644 --- a/src/cli/repl.rs +++ b/src/cli/repl.rs @@ -1,10 +1,12 @@ +mod meta_cmd; + +use std::cell::RefCell; use std::fs::read_to_string; -use std::process; +use std::rc::Rc; use std::sync::Arc; use anyhow::{bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; -use log::info; use rustyline::{ error::ReadlineError, history::DefaultHistory, @@ -12,32 +14,34 @@ use rustyline::{ Config, Editor, }; use rustyline_derive::{Completer, Helper, Highlighter, Hinter}; +use tracing::info; + +use super::{backend::Backend, commitment::Commitment, field_data::load, paths::commitment_path}; -use super::{commitment::Commitment, field_data::load, paths::commitment_path}; -use crate::cli::paths::{proof_path, public_params_dir}; use crate::{ + cli::paths::{proof_path, public_params_dir}, eval::{ lang::{Coproc, Lang}, Evaluator, Frame, Witness, IO, }, - field::{LanguageField, LurkField}, - parser, + field::LurkField, + lurk_sym_ptr, parser, + proof::{nova::NovaProver, Prover}, ptr::Ptr, + public_parameters::public_params, + state::State, store::Store, tag::{ContTag, ExprTag}, writer::Write, z_ptr::ZExprPtr, - Num, -}; - -use crate::{ - proof::{nova::NovaProver, Prover}, - public_parameters::public_params, z_store::ZStore, + Num, Symbol, }; use super::lurk_proof::{LurkProof, LurkProofMeta}; +use meta_cmd::MetaCmd; + #[derive(Completer, Helper, Highlighter, Hinter)] struct InputValidator { brackets: MatchingBracketValidator, @@ -49,67 +53,24 @@ impl Validator for InputValidator { } } -pub(crate) enum Backend { - Nova, - SnarkPackPlus, -} - -impl std::fmt::Display for Backend { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Nova => write!(f, "Nova"), - Self::SnarkPackPlus => write!(f, "SnarkPack+"), - } - } -} - -impl Backend { - pub(crate) fn default_field(&self) -> LanguageField { - match self { - Self::Nova => LanguageField::Pallas, - Self::SnarkPackPlus => LanguageField::BLS12_381, - } - } - - fn compatible_fields(&self) -> Vec { - use LanguageField::*; - match self { - Self::Nova => vec![Pallas, Vesta], - Self::SnarkPackPlus => vec![BLS12_381], - } - } - - pub(crate) fn validate_field(&self, field: &LanguageField) -> Result<()> { - let compatible_fields = self.compatible_fields(); - if !compatible_fields.contains(field) { - bail!( - "Backend {self} is incompatible with field {field}. Compatible fields are:\n {}", - compatible_fields - .iter() - .map(|f| f.to_string()) - .collect::>() - .join(", ") - ) - } - Ok(()) - } -} - #[allow(dead_code)] -struct Evaluation { - frames: Vec, Witness, Coproc>>, +struct Evaluation { + frames: Vec, Witness, F, C>>, iterations: usize, } #[allow(dead_code)] -pub(crate) struct Repl { +pub struct Repl { store: Store, + state: Rc>, env: Ptr, lang: Arc>>, rc: usize, limit: usize, backend: Backend, - evaluation: Option>, + evaluation: Option>>, + pwd_path: Utf8PathBuf, + meta: std::collections::HashMap<&'static str, MetaCmd>, } pub(crate) fn validate_non_zero(name: &str, x: usize) -> Result<()> { @@ -127,29 +88,69 @@ fn pad(a: usize, m: usize) -> usize { (a + m - 1) / m * m } +impl Repl { + fn peek1(&self, cmd: &str, args: &Ptr) -> Result> { + let (first, rest) = self.store.car_cdr(args)?; + if !rest.is_nil() { + bail!("`{cmd}` accepts at most one argument") + } + Ok(first) + } + + fn peek2(&self, cmd: &str, args: &Ptr) -> Result<(Ptr, Ptr)> { + let (first, rest) = self.store.car_cdr(args)?; + let (second, rest) = self.store.car_cdr(&rest)?; + if !rest.is_nil() { + bail!("`{cmd}` accepts at most two arguments") + } + Ok((first, second)) + } + + fn get_string(&self, ptr: &Ptr) -> Result { + match self.store.fetch_string(ptr) { + None => bail!( + "Expected string. Got {}", + ptr.fmt_to_string(&self.store, &self.state.borrow()) + ), + Some(string) => Ok(string), + } + } + + fn get_symbol(&self, ptr: &Ptr) -> Result { + match self.store.fetch_symbol(ptr) { + None => bail!( + "Expected symbol. Got {}", + ptr.fmt_to_string(&self.store, &self.state.borrow()) + ), + Some(symbol) => Ok(symbol), + } + } +} + type F = pasta_curves::pallas::Scalar; // TODO: generalize this impl Repl { - pub(crate) fn new( - store: Store, - env: Ptr, - rc: usize, - limit: usize, - backend: Backend, - ) -> Repl { + pub fn new(store: Store, rc: usize, limit: usize, backend: Backend) -> Repl { let limit = pad(limit, rc); info!( "Launching REPL with backend {backend}, field {}, rc {rc} and limit {limit}", F::FIELD ); + let current_dir = std::env::current_dir().expect("couldn't capture current directory"); + let pwd_path = + Utf8PathBuf::from_path_buf(current_dir).expect("path contains invalid Unicode"); + let env = lurk_sym_ptr!(store, nil); Repl { store, + state: State::init_lurk_state().rccell(), env, lang: Arc::new(Lang::new()), rc, limit, backend, evaluation: None, + pwd_path, + meta: MetaCmd::cmds(), } } @@ -195,14 +196,14 @@ impl Repl { } pub(crate) fn prove_last_frames(&mut self) -> Result<()> { - match self.evaluation.as_mut() { + match self.evaluation.as_ref() { None => bail!("No evaluation to prove"), Some(Evaluation { frames, iterations }) => match self.backend { Backend::Nova => { info!("Hydrating the store"); self.store.hydrate_scalar_cache(); - let mut n_frames = frames.len(); + let n_frames = frames.len(); // saving to avoid clones let input = &frames[0].input; @@ -232,24 +233,19 @@ impl Repl { // TODO: make sure that the proof file is not corrupted } else { info!("Proof not cached"); - // padding the frames, if needed - let n_pad = pad(n_frames, self.rc) - n_frames; - if n_pad != 0 { - frames.extend(vec![frames[n_frames - 1].clone(); n_pad]); - n_frames = frames.len(); - } info!("Loading public parameters"); - let pp = public_params(self.rc, self.lang.clone(), &public_params_dir())?; + let pp = + public_params(self.rc, true, self.lang.clone(), &public_params_dir())?; let prover = NovaProver::new(self.rc, (*self.lang).clone()); info!("Proving"); let (proof, public_inputs, public_outputs, num_steps) = - prover.prove(&pp, frames, &mut self.store, self.lang.clone())?; + prover.prove(&pp, frames, &self.store, self.lang.clone())?; info!("Compressing proof"); let proof = proof.compress(&pp)?; - assert_eq!(self.rc * num_steps, n_frames); + assert_eq!(self.rc * num_steps, pad(n_frames, self.rc)); assert!(proof.verify(&pp, num_steps, &public_inputs, &public_outputs)?); let lurk_proof = LurkProof::Nova { @@ -291,7 +287,7 @@ impl Repl { commitment.persist()?; println!( "Data: {}\nHash: 0x{hash_str}", - payload.fmt_to_string(&self.store) + payload.fmt_to_string(&self.store, &self.state.borrow()) ); Ok(()) } @@ -311,7 +307,7 @@ impl Repl { .unwrap(); if print_data { let data = self.store.fetch_comm(&comm_ptr).unwrap().1; - println!("{}", data.fmt_to_string(&self.store)); + println!("{}", data.fmt_to_string(&self.store, &self.state.borrow())); } else { println!("Data is now available"); } @@ -344,6 +340,22 @@ impl Repl { } } + fn eval_expr_allowing_error_continuation( + &mut self, + expr_ptr: Ptr, + ) -> Result<(IO, usize, Vec>)> { + let ret = + Evaluator::new(expr_ptr, self.env, &mut self.store, self.limit, &self.lang).eval()?; + if matches!(ret.0.cont.tag, ContTag::Terminal | ContTag::Error) { + Ok(ret) + } else { + bail!( + "Limit reached after {}", + Self::pretty_iterations_display(ret.1) + ) + } + } + fn eval_expr_and_memoize(&mut self, expr_ptr: Ptr) -> Result<(IO, usize)> { let frames = Evaluator::new(expr_ptr, self.env, &mut self.store, self.limit, &self.lang) .get_frames()?; @@ -364,28 +376,11 @@ impl Repl { Ok((last_output, iterations)) } - fn peek1(&self, cmd: &str, args: &Ptr) -> Result> { - let (first, rest) = self.store.car_cdr(args)?; - if !rest.is_nil() { - bail!("`{cmd}` accepts at most one argument") - } - Ok(first) - } - - fn peek2(&self, cmd: &str, args: &Ptr) -> Result<(Ptr, Ptr)> { - let (first, rest) = self.store.car_cdr(args)?; - let (second, rest) = self.store.car_cdr(&rest)?; - if !rest.is_nil() { - bail!("`{cmd}` accepts at most two arguments") - } - Ok((first, second)) - } - #[allow(dead_code)] fn get_comm_hash(&mut self, cmd: &str, args: &Ptr) -> Result { let first = self.peek1(cmd, args)?; - let n = self.store.lurk_sym("num"); - let expr = self.store.list(&[n, first]); + let num = lurk_sym_ptr!(self.store, num); + let expr = self.store.list(&[num, first]); let (expr_io, ..) = self .eval_expr(expr) .with_context(|| "evaluating first arg")?; @@ -396,188 +391,6 @@ impl Repl { Ok(hash.into_scalar()) } - fn handle_meta_cases(&mut self, cmd: &str, args: &Ptr, pwd_path: &Utf8Path) -> Result<()> { - match cmd { - "def" => { - // Extends env with a non-recursive binding. - // - // This: !(:def foo (lambda () 123)) - // - // Gets macroexpanded to this: (let ((foo (lambda () 123))) - // (current-env)) - // - // And the state's env is set to the result. - let (first, second) = self.peek2(cmd, args)?; - let l = &self.store.lurk_sym("let"); - let current_env = &self.store.lurk_sym("current-env"); - let binding = &self.store.list(&[first, second]); - let bindings = &self.store.list(&[*binding]); - let current_env_call = &self.store.list(&[*current_env]); - let expanded = &self.store.list(&[*l, *bindings, *current_env_call]); - let (expanded_io, ..) = self.eval_expr(*expanded)?; - - self.env = expanded_io.expr; - - let (new_binding, _) = &self.store.car_cdr(&expanded_io.expr)?; - let (new_name, _) = self.store.car_cdr(new_binding)?; - println!("{}", new_name.fmt_to_string(&self.store)); - } - "defrec" => { - // Extends env with a recursive binding. - // - // This: !(:defrec foo (lambda () 123)) - // - // Gets macroexpanded to this: (letrec ((foo (lambda () 123))) - // (current-env)) - // - // And the state's env is set to the result. - let (first, second) = self.peek2(cmd, args)?; - let l = &self.store.lurk_sym("letrec"); - let current_env = &self.store.lurk_sym("current-env"); - let binding = &self.store.list(&[first, second]); - let bindings = &self.store.list(&[*binding]); - let current_env_call = &self.store.list(&[*current_env]); - let expanded = &self.store.list(&[*l, *bindings, *current_env_call]); - let (expanded_io, ..) = self.eval_expr(*expanded)?; - - self.env = expanded_io.expr; - - let (new_binding_outer, _) = &self.store.car_cdr(&expanded_io.expr)?; - let (new_binding_inner, _) = &self.store.car_cdr(new_binding_outer)?; - let (new_name, _) = self.store.car_cdr(new_binding_inner)?; - println!("{}", new_name.fmt_to_string(&self.store)); - } - "load" => { - let first = self.peek1(cmd, args)?; - match self.store.fetch_string(&first) { - Some(path) => { - let joined = pwd_path.join(Utf8Path::new(&path)); - self.load_file(&joined)? - } - _ => bail!("Argument of `load` must be a string."), - } - std::io::Write::flush(&mut std::io::stdout()).unwrap(); - } - "assert" => { - let first = self.peek1(cmd, args)?; - let (first_io, ..) = self.eval_expr(first)?; - if first_io.expr.is_nil() { - eprintln!( - "`assert` failed. {} evaluates to nil", - first.fmt_to_string(&self.store) - ); - process::exit(1); - } - } - "assert-eq" => { - let (first, second) = self.peek2(cmd, args)?; - let (first_io, ..) = self - .eval_expr(first) - .with_context(|| "evaluating first arg")?; - let (second_io, ..) = self - .eval_expr(second) - .with_context(|| "evaluating second arg")?; - if !&self.store.ptr_eq(&first_io.expr, &second_io.expr)? { - eprintln!( - "`assert-eq` failed. Expected:\n {} = {}\nGot:\n {} ≠ {}", - first.fmt_to_string(&self.store), - second.fmt_to_string(&self.store), - first_io.expr.fmt_to_string(&self.store), - second_io.expr.fmt_to_string(&self.store) - ); - process::exit(1); - } - } - "assert-emitted" => { - let (first, second) = self.peek2(cmd, args)?; - let (first_io, ..) = self - .eval_expr(first) - .with_context(|| "evaluating first arg")?; - let (.., emitted) = self - .eval_expr(second) - .with_context(|| "evaluating second arg")?; - let (mut first_emitted, mut rest_emitted) = self.store.car_cdr(&first_io.expr)?; - for (i, elem) in emitted.iter().enumerate() { - if elem != &first_emitted { - eprintln!( - "`assert-emitted` failed at position {i}. Expected {}, but found {}.", - first_emitted.fmt_to_string(&self.store), - elem.fmt_to_string(&self.store), - ); - process::exit(1); - } - (first_emitted, rest_emitted) = self.store.car_cdr(&rest_emitted)?; - } - } - "assert-error" => { - let first = self.peek1(cmd, args)?; - if self.eval_expr(first).is_ok() { - eprintln!( - "`assert-error` failed. {} doesn't result on evaluation error.", - first.fmt_to_string(&self.store) - ); - process::exit(1); - } - } - "lurk.commit" => { - let first = self.peek1(cmd, args)?; - let (first_io, ..) = self.eval_expr(first)?; - self.hide(ff::Field::ZERO, first_io.expr)?; - } - "lurk.hide" => { - let (first, second) = self.peek2(cmd, args)?; - let (first_io, ..) = self - .eval_expr(first) - .with_context(|| "evaluating first arg")?; - let (second_io, ..) = self - .eval_expr(second) - .with_context(|| "evaluating second arg")?; - let Some(secret) = self.store.fetch_num(&first_io.expr) else { - bail!( - "Secret must be a number. Got {}", - first_io.expr.fmt_to_string(&self.store) - ) - }; - self.hide(secret.into_scalar(), second_io.expr)?; - } - "fetch" => { - let hash = self.get_comm_hash(cmd, args)?; - self.fetch(&hash, false)?; - } - "lurk.open" => { - let hash = self.get_comm_hash(cmd, args)?; - self.fetch(&hash, true)?; - } - "clear" => self.env = self.store.nil(), - "set-env" => { - // The state's env is set to the result of evaluating the first argument. - let first = self.peek1(cmd, args)?; - let (first_io, ..) = self.eval_expr(first)?; - self.env = first_io.expr; - } - "prove" => { - if !args.is_nil() { - self.eval_expr_and_memoize(self.peek1(cmd, args)?)?; - } - self.prove_last_frames()?; - } - "verify" => { - let first = self.peek1(cmd, args)?; - match self.store.fetch_string(&first) { - None => bail!( - "Proof ID {} not parsed as a string", - first.fmt_to_string(&self.store) - ), - Some(proof_id) => { - LurkProof::verify_proof(&proof_id)?; - } - } - } - _ => bail!("Unsupported meta command: {cmd}"), - } - Ok(()) - } - fn handle_non_meta(&mut self, expr_ptr: Ptr) -> Result<()> { self.eval_expr_and_memoize(expr_ptr) .map(|(output, iterations)| { @@ -586,7 +399,7 @@ impl Repl { ContTag::Terminal => { println!( "[{iterations_display}] => {}", - output.expr.fmt_to_string(&self.store) + output.expr.fmt_to_string(&self.store, &self.state.borrow()) ) } ContTag::Error => { @@ -597,42 +410,47 @@ impl Repl { }) } - fn handle_meta(&mut self, expr_ptr: Ptr, pwd_path: &Utf8Path) -> Result<()> { + fn handle_meta(&mut self, expr_ptr: Ptr) -> Result<()> { let (car, cdr) = self.store.car_cdr(&expr_ptr)?; - match &self.store.fetch_symbol(&car) { + match &self.store.fetch_sym(&car) { Some(symbol) => { - self.handle_meta_cases(format!("{}", symbol).as_str(), &cdr, pwd_path)? + let cmdstr = symbol.name()?; + match self.meta.get(cmdstr) { + Some(cmd) => match (cmd.run)(self, cmdstr, &cdr) { + Ok(()) => (), + Err(e) => println!("meta command failed with {}", e), + }, + None => bail!("Unsupported meta command: {cmdstr}"), + } } None => bail!( "Meta command must be a symbol. Found {}", - car.fmt_to_string(&self.store) + car.fmt_to_string(&self.store, &self.state.borrow()) ), } Ok(()) } - fn handle_form<'a>( - &mut self, - input: parser::Span<'a>, - pwd_path: &Utf8Path, - ) -> Result> { - let (input, ptr, is_meta) = self.store.read_maybe_meta(input)?; + fn handle_form<'a>(&mut self, input: parser::Span<'a>) -> Result> { + let (input, ptr, is_meta) = self + .store + .read_maybe_meta_with_state(self.state.clone(), input)?; if is_meta { - self.handle_meta(ptr, pwd_path)?; + self.handle_meta(ptr)?; } else { self.handle_non_meta(ptr)?; } Ok(input) } - pub(crate) fn load_file(&mut self, file_path: &Utf8Path) -> Result<()> { + pub fn load_file(&mut self, file_path: &Utf8Path) -> Result<()> { let input = read_to_string(file_path)?; - println!("Loading {}", file_path); + println!("Loading {file_path}"); let mut input = parser::Span::new(&input); loop { - match self.handle_form(input, file_path) { + match self.handle_form(input) { Ok(new_input) => input = new_input, Err(e) => { if let Some(parser::Error::NoInput) = e.downcast_ref::() { @@ -649,9 +467,6 @@ impl Repl { pub(crate) fn start(&mut self) -> Result<()> { println!("Lurk REPL welcomes you."); - let pwd_path = Utf8PathBuf::from_path_buf(std::env::current_dir()?) - .expect("path contains invalid Unicode"); - let mut editor: Editor = Editor::with_config( Config::builder() .color_mode(rustyline::ColorMode::Enabled) @@ -670,13 +485,21 @@ impl Repl { } loop { - match editor.readline("> ") { + match editor.readline(&format!( + "{}> ", + self.state + .borrow() + .fmt_to_string(self.state.borrow().get_current_package_name()) + )) { Ok(line) => { editor.save_history(history_path)?; - match self.store.read_maybe_meta(parser::Span::new(&line)) { + match self + .store + .read_maybe_meta_with_state(self.state.clone(), parser::Span::new(&line)) + { Ok((_, expr_ptr, is_meta)) => { if is_meta { - if let Err(e) = self.handle_meta(expr_ptr, &pwd_path) { + if let Err(e) = self.handle_meta(expr_ptr) { println!("!Error: {e}"); } } else if let Err(e) = self.handle_non_meta(expr_ptr) { diff --git a/src/cli/repl/meta_cmd.rs b/src/cli/repl/meta_cmd.rs new file mode 100644 index 0000000000..d8d37ad7f2 --- /dev/null +++ b/src/cli/repl/meta_cmd.rs @@ -0,0 +1,581 @@ +use anyhow::{bail, Context, Result}; +use camino::Utf8Path; +use std::process; + +use crate::{ + cli::lurk_proof::LurkProof, + field::LurkField, + lurk_sym_ptr, + package::{Package, SymbolRef}, + ptr::Ptr, + tag::{ContTag, ExprTag}, + writer::Write, +}; + +use super::Repl; + +pub(super) struct MetaCmd { + name: &'static str, + summary: &'static str, + format: &'static str, + description: &'static [&'static str], + example: &'static [&'static str], + pub(super) run: fn(repl: &mut Repl, cmd: &str, args: &Ptr) -> Result<()>, +} + +type F = pasta_curves::pallas::Scalar; // TODO: generalize this + +impl MetaCmd { + const LOAD: MetaCmd = MetaCmd { + name: "load", + summary: "Load lurk expressions from a file path.", + format: "!(load )", + description: &[], + example: &["Load lurk expressions from a file path."], + run: |repl, cmd, args| { + let first = repl.peek1(cmd, args)?; + match repl.store.fetch_string(&first) { + Some(path) => { + let joined = repl.pwd_path.join(Utf8Path::new(&path)); + repl.load_file(&joined)? + } + _ => bail!("Argument of `load` must be a string."), + } + Ok(()) + }, + }; +} + +impl MetaCmd { + const DEF: MetaCmd = MetaCmd { + name: "def", + summary: "Extends env with a non-recursive binding.", + format: "!(def )", + description: &[ + "Gets macroexpanded to this: (let ((foo (lambda () 123))) (current-env))", + "The state's env is set to the result.", + ], + example: &["!(def foo (lambda () 123))"], + run: |repl: &mut Repl, cmd: &str, args: &Ptr| { + let (first, second) = repl.peek2(cmd, args)?; + let l = lurk_sym_ptr!(&repl.store, let_); + let current_env = lurk_sym_ptr!(&repl.store, current_env); + let binding = &repl.store.list(&[first, second]); + let bindings = &repl.store.list(&[*binding]); + let current_env_call = &repl.store.list(&[current_env]); + let expanded = &repl.store.list(&[l, *bindings, *current_env_call]); + let (expanded_io, ..) = repl.eval_expr(*expanded)?; + repl.env = expanded_io.expr; + let (new_binding, _) = &repl.store.car_cdr(&expanded_io.expr)?; + let (new_name, _) = repl.store.car_cdr(new_binding)?; + println!( + "{}", + new_name.fmt_to_string(&repl.store, &repl.state.borrow()) + ); + Ok(()) + }, + }; +} + +impl MetaCmd { + const DEFREC: MetaCmd = MetaCmd { + name: "defrec", + summary: "Extends the env with a recursive binding.", + format: "!(defrec )", + description: &[ + "Gets macroexpanded to this: (letrec ((foo (lambda () 123))) (current-env))", + "The state's env is set ot the result.", + ], + example: &[ + "!(defrec sum (lambda (l) (if (eq l nil) 0 (+ (car l) (sum (cdr l))))))", + "(sum '(1 2 3))", + ], + run: |repl, cmd, args| { + let (first, second) = repl.peek2(cmd, args)?; + let l = lurk_sym_ptr!(&repl.store, letrec); + let current_env = lurk_sym_ptr!(&repl.store, current_env); + let binding = &repl.store.list(&[first, second]); + let bindings = &repl.store.list(&[*binding]); + let current_env_call = &repl.store.list(&[current_env]); + let expanded = &repl.store.list(&[l, *bindings, *current_env_call]); + let (expanded_io, ..) = repl.eval_expr(*expanded)?; + + repl.env = expanded_io.expr; + + let (new_binding_outer, _) = &repl.store.car_cdr(&expanded_io.expr)?; + let (new_binding_inner, _) = &repl.store.car_cdr(new_binding_outer)?; + let (new_name, _) = repl.store.car_cdr(new_binding_inner)?; + println!( + "{}", + new_name.fmt_to_string(&repl.store, &repl.state.borrow()) + ); + Ok(()) + }, + }; +} + +impl MetaCmd { + const ASSERT: MetaCmd = MetaCmd { + name: "assert", + summary: "Assert that an expression evaluates to true.", + format: "!(assert )", + description: &[], + example: &["!(assert t)", "!(assert (eq 3 (+ 1 2)))"], + run: |repl, cmd, args| { + let first = repl.peek1(cmd, args)?; + let (first_io, ..) = repl.eval_expr(first)?; + if first_io.expr.is_nil() { + eprintln!( + "`assert` failed. {} evaluates to nil", + first.fmt_to_string(&repl.store, &repl.state.borrow()) + ); + process::exit(1); + } + Ok(()) + }, + }; +} + +impl MetaCmd { + const ASSERT_EQ: MetaCmd = MetaCmd { + name: "assert-eq", + summary: "Assert that two expressions evaluate to the same value.", + format: "!(assert-eq )", + description: &[], + example: &["!(assert-eq 3 (+ 1 2))"], + run: |repl, cmd, args| { + let (first, second) = repl.peek2(cmd, args)?; + let (first_io, ..) = repl + .eval_expr(first) + .with_context(|| "evaluating first arg")?; + let (second_io, ..) = repl + .eval_expr(second) + .with_context(|| "evaluating second arg")?; + if !&repl.store.ptr_eq(&first_io.expr, &second_io.expr)? { + eprintln!( + "`assert-eq` failed. Expected:\n {} = {}\nGot:\n {} ≠ {}", + first.fmt_to_string(&repl.store, &repl.state.borrow()), + second.fmt_to_string(&repl.store, &repl.state.borrow()), + first_io + .expr + .fmt_to_string(&repl.store, &repl.state.borrow()), + second_io + .expr + .fmt_to_string(&repl.store, &repl.state.borrow()) + ); + process::exit(1); + } + Ok(()) + }, + }; +} + +impl MetaCmd { + const ASSERT_EMITTED: MetaCmd = MetaCmd { + name: + "assert-emitted", + summary: + "Assert that the evaluation of an expr emits values", + format: + "!(assert-emitted )", + description: &[ + "Assert that the list of values in the first are emitted by the validation of the second .", + ], + example: &[ + "!(assert-emitted '(1 2) (begin (emit 1) (emit 2)))" + ], + run: |repl, cmd, args| { + let (first, second) = repl.peek2(cmd, args)?; + let (first_io, ..) = repl + .eval_expr(first) + .with_context(|| "evaluating first arg")?; + let (.., emitted) = repl + .eval_expr(second) + .with_context(|| "evaluating second arg")?; + let (mut first_emitted, mut rest_emitted) = repl.store.car_cdr(&first_io.expr)?; + for (i, elem) in emitted.iter().enumerate() { + if elem != &first_emitted { + eprintln!( + "`assert-emitted` failed at position {i}. Expected {}, but found {}.", + first_emitted.fmt_to_string(&repl.store, &repl.state.borrow()), + elem.fmt_to_string(&repl.store, &repl.state.borrow()), + ); + process::exit(1); + } + (first_emitted, rest_emitted) = repl.store.car_cdr(&rest_emitted)?; + } + Ok(()) + }, + }; +} + +impl MetaCmd { + const ASSERT_ERROR: MetaCmd = MetaCmd { + name: "assert-error", + summary: "Assert that a evaluation of fails.", + format: "!(assert-error )", + description: &[], + example: &["!(assert-error (1 1))"], + run: |repl, cmd, args| { + let first = repl.peek1(cmd, args)?; + let (first_io, ..) = repl.eval_expr_allowing_error_continuation(first)?; + if first_io.cont.tag != ContTag::Error { + eprintln!( + "`assert-error` failed. {} doesn't result on evaluation error.", + first.fmt_to_string(&repl.store, &repl.state.borrow()) + ); + process::exit(1); + } + Ok(()) + }, + }; +} + +impl MetaCmd { + const COMMIT: MetaCmd = MetaCmd { + name: + "commit", + summary: + "Compute the commitment of .", + format: + "!(commit )", + description: &[], + example: &[ + "!(commit '(13 . 21))", + "(let ((n (open 0x2c4e1dc8a344764c52d97c691ef0d8312e07b38e99f12cf2f200891c53fb36c0))) (* (car n) (cdr n)))", + ], + run: |repl, cmd, args| { + let first = repl.peek1(cmd, args)?; + let (first_io, ..) = repl.eval_expr(first)?; + repl.hide(ff::Field::ZERO, first_io.expr)?; + Ok(()) + } + }; +} + +impl MetaCmd { + const HIDE: MetaCmd = MetaCmd { + name: "hide", + summary: "Return and persist the commitment of using secret .", + format: "!(hide )", + description: &[], + example: &[ + "!(hide 12345 '(13 . 21))", + "(secret (comm 0x3be5f551534baa53a9c180e49b48c4a75ed7642a82197be5f674d54681de4425))", + "(open 0x3be5f551534baa53a9c180e49b48c4a75ed7642a82197be5f674d54681de4425)", + ], + run: |repl, cmd, args| { + let (first, second) = repl.peek2(cmd, args)?; + let (first_io, ..) = repl + .eval_expr(first) + .with_context(|| "evaluating first arg")?; + let (second_io, ..) = repl + .eval_expr(second) + .with_context(|| "evaluating second arg")?; + let Some(secret) = repl.store.fetch_num(&first_io.expr) else { + bail!( + "Secret must be a number. Got {}", + first_io + .expr + .fmt_to_string(&repl.store, &repl.state.borrow()) + ) + }; + repl.hide(secret.into_scalar(), second_io.expr)?; + Ok(()) + }, + }; +} + +impl MetaCmd { + const FETCH: MetaCmd = MetaCmd { + name: "fetch", + summary: "Add data from a commitment to the repl store.", + format: "!(fetch )", + description: &[], + example: &[ + "!(commit '(13 . 21))", + "(fetch 0x2c4e1dc8a344764c52d97c691ef0d8312e07b38e99f12cf2f200891c53fb36c0)", + ], + run: |repl, cmd, args| { + let hash = repl.get_comm_hash(cmd, args)?; + repl.fetch(&hash, false)?; + Ok(()) + }, + }; +} + +impl MetaCmd { + const OPEN: MetaCmd = MetaCmd { + name: "open", + summary: "Open a commitment.", + format: "!(open )", + description: &[], + example: &[ + "!(commit '(13 . 21))", + "!(open 0x2c4e1dc8a344764c52d97c691ef0d8312e07b38e99f12cf2f200891c53fb36c0)", + ], + run: |repl, cmd, args| { + let hash = repl.get_comm_hash(cmd, args)?; + repl.fetch(&hash, true)?; + Ok(()) + }, + }; +} + +impl MetaCmd { + const CLEAR: MetaCmd = MetaCmd { + name: "clear", + summary: "Reset the current environment to be empty.", + format: "!(clear)", + description: &[], + example: &["!(def a 1)", "(current-env)", "!(clear)", "(current-env)"], + run: |repl, _cmd, _args| { + repl.env = lurk_sym_ptr!(&repl.store, nil); + Ok(()) + }, + }; +} + +impl MetaCmd { + const SET_ENV: MetaCmd = MetaCmd { + name: "set-env", + summary: "Set the env to the result of evaluating the first argument.", + format: "!(set-env )", + description: &[], + example: &["!(set-env '((a . 1) (b . 2)))", "a"], + run: |repl, cmd, args| { + // The state's env is set to the result of evaluating the first argument. + let first = repl.peek1(cmd, args)?; + let (first_io, ..) = repl.eval_expr(first)?; + repl.env = first_io.expr; + Ok(()) + }, + }; +} + +impl MetaCmd { + const PROVE: MetaCmd = MetaCmd { + name: + "prove", + summary: + "Evaluate and prove ", + format: + "!(prove )", + description: &[ + "Persist the proof and prints the proof id.", + ], + example: &[ + "!(prove '(1 2 3))", + "!(verify \"Nova_Pallas_10_166fafef9d86d1ddd29e7b62fa5e4fb2d7f4d885baf28e23187860d0720f74ca\")", + "!(open 0x166fafef9d86d1ddd29e7b62fa5e4fb2d7f4d885baf28e23187860d0720f74ca)", + ], + run: |repl, cmd, args| { + if !args.is_nil() { + repl.eval_expr_and_memoize(repl.peek1(cmd, args)?)?; + } + repl.prove_last_frames()?; + Ok(()) + } + }; +} + +impl MetaCmd { + const VERIFY: MetaCmd = MetaCmd { + name: + "verify", + summary: + "Verify a proof", + format: + "!(verify )", + description: &[ + "Verify proof id and print the result.", + ], + example: &[ + "!(prove '(1 2 3))", + "!(verify \"Nova_Pallas_10_166fafef9d86d1ddd29e7b62fa5e4fb2d7f4d885baf28e23187860d0720f74ca\")", + "!(open 0x166fafef9d86d1ddd29e7b62fa5e4fb2d7f4d885baf28e23187860d0720f74ca)", + ], + run: |repl, cmd, args| { + let first = repl.peek1(cmd, args)?; + let proof_id = repl.get_string(&first)?; + LurkProof::verify_proof(&proof_id)?; + Ok(()) + } + }; +} + +impl MetaCmd { + const DEFPACKAGE: MetaCmd = MetaCmd { + name: "defpackage", + summary: "Add a package to the state.", + format: "!(defpackage )", + description: &[], + example: &["!(defpackage abc)"], + run: |repl, _cmd, args| { + // TODO: handle args + let (name, _args) = repl.store.car_cdr(args)?; + let name = match name.tag { + ExprTag::Str => repl.state.borrow_mut().intern(repl.get_string(&name)?), + ExprTag::Sym => repl.get_symbol(&name)?.into(), + _ => bail!("Package name must be a string or a symbol"), + }; + println!("{}", repl.state.borrow().fmt_to_string(&name)); + let package = Package::new(name); + repl.state.borrow_mut().add_package(package); + Ok(()) + }, + }; +} + +impl MetaCmd { + const IMPORT: MetaCmd = MetaCmd { + name: "import", + summary: "Import a single or several packages.", + format: "!(import ...)", + description: &[], + example: &[], + run: |repl, _cmd, args| { + // TODO: handle pkg + let (mut symbols, _pkg) = repl.store.car_cdr(args)?; + if symbols.tag == ExprTag::Sym { + let sym = SymbolRef::new(repl.get_symbol(&symbols)?); + repl.state.borrow_mut().import(&[sym])?; + } else { + let mut symbols_vec = vec![]; + loop { + { + let (head, tail) = repl.store.car_cdr(&symbols)?; + let sym = repl.get_symbol(&head)?; + symbols_vec.push(SymbolRef::new(sym)); + if tail.is_nil() { + break; + } + symbols = tail; + } + } + repl.state.borrow_mut().import(&symbols_vec)?; + } + Ok(()) + }, + }; +} + +impl MetaCmd { + const IN_PACKAGE: MetaCmd = MetaCmd { + name: "in-package", + summary: "set the current package.", + format: "!(in-package )", + description: &[], + example: &[ + "!(defpackage abc)", + "!(in-package abc)", + "!(def two (.lurk.+ 1 1))", + "!(in-package .lurk.user)", + "(.lurk.user.abc.two)", + ], + run: |repl, cmd, args| { + let first = repl.peek1(cmd, args)?; + match first.tag { + ExprTag::Str => { + let name = repl.get_string(&first)?; + let package_name = repl.state.borrow_mut().intern(name); + repl.state.borrow_mut().set_current_package(package_name)?; + } + ExprTag::Sym => { + let package_name = repl.get_symbol(&first)?; + repl.state + .borrow_mut() + .set_current_package(package_name.into())?; + } + _ => bail!( + "Expected string or symbol. Got {}", + first.fmt_to_string(&repl.store, &repl.state.borrow()) + ), + } + Ok(()) + }, + }; +} + +impl MetaCmd { + const HELP: MetaCmd = MetaCmd { + name: "help", + summary: "Print help message.", + format: "!(help [|])", + description: &[ + "Without arguments it prints a summary of all available commands.", + "Otherwise the full help for the command in the first argument is printed.", + ], + example: &["!(help)", "!(help verify)", "!(help \"load\")"], + run: |repl, cmd, args| { + let first = repl.peek1(cmd, args)?; + match first.tag { + ExprTag::Str => { + let name = repl.get_string(&first)?; + Self::meta_help(&name); + } + ExprTag::Sym => { + let sym = repl.get_symbol(&first)?; + let name = sym.path().last().unwrap(); + Self::meta_help(name); + } + ExprTag::Nil => { + use itertools::Itertools; + println!("Available commands:"); + for (_, i) in MetaCmd::cmds().iter().sorted_by_key(|x| x.0) { + println!(" {} - {}", i.name, i.summary); + } + } + _ => bail!("The optional argument of `help` must be a string or symbol"), + } + Ok(()) + }, + }; + + fn meta_help(cmd: &str) { + match MetaCmd::cmds().get(cmd) { + Some(i) => { + println!("{} - {}", i.name, i.summary); + for &e in i.description.iter() { + println!(" {}", e); + } + println!(" Usage: {}", i.format); + if !i.example.is_empty() { + println!(" Example:"); + } + for &e in i.example.iter() { + println!(" {}", e); + } + } + None => println!("unknown command {}", cmd), + } + } +} + +impl MetaCmd { + const CMDS: [MetaCmd; 19] = [ + MetaCmd::LOAD, + MetaCmd::DEF, + MetaCmd::DEFREC, + MetaCmd::ASSERT, + MetaCmd::ASSERT_EQ, + MetaCmd::ASSERT_EMITTED, + MetaCmd::ASSERT_ERROR, + MetaCmd::COMMIT, + MetaCmd::HIDE, + MetaCmd::FETCH, + MetaCmd::OPEN, + MetaCmd::CLEAR, + MetaCmd::SET_ENV, + MetaCmd::PROVE, + MetaCmd::VERIFY, + MetaCmd::DEFPACKAGE, + MetaCmd::IMPORT, + MetaCmd::IN_PACKAGE, + MetaCmd::HELP, + ]; + + pub(super) fn cmds() -> std::collections::HashMap<&'static str, MetaCmd> { + std::collections::HashMap::from(Self::CMDS.map(|x| (x.name, x))) + } +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000000..a36b6485d5 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,150 @@ +//! Global config for parallelism. +use anyhow::bail; +use once_cell::sync::Lazy; + +pub static CONFIG: Lazy = Lazy::new(init_config); + +fn canned_config_from_env() -> Option { + if let Ok(x) = std::env::var("LURK_CANNED_CONFIG") { + let canned = CannedConfig::try_from(x.as_str()).ok(); + + tracing::debug!("{:?}", &canned); + + canned + } else { + None + } +} + +#[derive(Default, Debug)] +pub enum Flow { + #[default] + Sequential, + Parallel, // Try to be smart. + ParallelN(usize), // How many threads to use? (Advisory, might be ignored.) +} + +impl Flow { + pub fn is_sequential(&self) -> bool { + matches!(self, Self::Sequential) + } + + pub fn is_parallel(&self) -> bool { + !self.is_sequential() + } + + pub fn num_threads(&self) -> usize { + match self { + Self::Sequential => 1, + Self::Parallel => num_cpus::get(), + Self::ParallelN(threads) => *threads, + } + } + + pub fn chunk_size(&self, total_n: usize, min_chunk_size: usize) -> usize { + if self.is_sequential() { + total_n + } else { + let num_threads = self.num_threads(); + let divides_evenly = total_n % num_threads == 0; + + ((total_n / num_threads) + usize::from(!divides_evenly)).max(min_chunk_size) + } + } +} + +#[derive(Default, Debug)] +pub struct ParallelConfig { + pub recursive_steps: Flow, // Multiple `StepCircuit`s. + pub synthesis: Flow, // Synthesis (within one `StepCircuit`) + pub poseidon_witnesses: Flow, // The poseidon witness part of synthesis. +} + +/// Should we use optimized witness-generation when possible? +#[derive(Debug, Default)] +pub struct WitnessGeneration { + // NOTE: Neptune itself *will* do this transparently at the level of individual hashes, where possible. + // so this configuration is only required for higher-level decisions. + pub precompute_neptune: bool, +} + +#[derive(Default, Debug)] +pub struct Config { + pub parallelism: ParallelConfig, + pub witness_generation: WitnessGeneration, +} + +impl Config { + fn fully_sequential() -> Self { + Self { + parallelism: ParallelConfig { + recursive_steps: Flow::Sequential, + synthesis: Flow::Sequential, + poseidon_witnesses: Flow::Sequential, + }, + witness_generation: WitnessGeneration { + precompute_neptune: false, + }, + } + } + + fn max_parallel_simple() -> Self { + Self { + parallelism: ParallelConfig { + recursive_steps: Flow::Parallel, + synthesis: Flow::Parallel, + poseidon_witnesses: Flow::Parallel, + }, + witness_generation: WitnessGeneration { + precompute_neptune: true, + }, + } + } + + fn parallel_steps_only() -> Self { + Self { + parallelism: ParallelConfig { + recursive_steps: Flow::Parallel, + synthesis: Flow::Sequential, + poseidon_witnesses: Flow::Sequential, + }, + witness_generation: WitnessGeneration { + precompute_neptune: true, + }, + } + } +} + +#[derive(Debug)] +enum CannedConfig { + FullySequential, + MaxParallelSimple, + ParallelStepsOnly, +} + +impl From for Config { + fn from(canned: CannedConfig) -> Self { + match canned { + CannedConfig::FullySequential => Self::fully_sequential(), + CannedConfig::MaxParallelSimple => Self::max_parallel_simple(), + CannedConfig::ParallelStepsOnly => Self::parallel_steps_only(), + } + } +} + +impl TryFrom<&str> for CannedConfig { + type Error = anyhow::Error; + + fn try_from(s: &str) -> Result { + match s { + "FULLY-SEQUENTIAL" => Ok(Self::FullySequential), + "MAX-PARALLEL-SIMPLE" => Ok(Self::MaxParallelSimple), + "PARALLEL-STEPS-ONLY" => Ok(Self::ParallelStepsOnly), + _ => bail!("Invalid CannedConfig: {s}"), + } + } +} + +fn init_config() -> Config { + canned_config_from_env().map_or_else(Config::fully_sequential, |x| x.into()) +} diff --git a/src/coprocessor/circom.rs b/src/coprocessor/circom.rs new file mode 100644 index 0000000000..c9a0955e7c --- /dev/null +++ b/src/coprocessor/circom.rs @@ -0,0 +1,180 @@ +// # Usage of circom coprocessors. +// +// See `examples/circom.rs` for a quick example of how to declare a circom coprocessor. + +/// Some circom features require non WASM platform features, so we seal the API here. +#[cfg(not(target_arch = "wasm32"))] +pub mod non_wasm { + use core::fmt::Debug; + use std::{collections::HashMap, fs::read_dir}; + + use ansi_term::Colour::Red; + use anyhow::{bail, Result}; + use bellpepper_core::{ConstraintSystem, SynthesisError}; + use circom_scotia::r1cs::CircomConfig; + + use crate::{ + circuit::gadgets::{ + circom::CircomGadget, + data::GlobalAllocations, + pointer::{AllocatedContPtr, AllocatedPtr}, + }, + cli::paths::{circom_dir, set_lurk_dirs}, + coprocessor::{CoCircuit, Coprocessor}, + field::LurkField, + ptr::Ptr, + store::Store, + }; + + fn print_error(name: &str, available: Vec) -> Result<()> { + let available = available.join("\n "); + bail!( + " +{}: no circom gadget named `{name}`. +Available circom gadgets: + + {available} + +If you want to setup a new circom gadget `{name}`, place your circom files in a designated folder and +create a file called `{name}.circom`. The circom binary expects `{}_FOLDER>/{name}.circom` +as the input file; in this file you must declare your circom main component. + +Then run `lurk coprocessor --name {name} <{}_FOLDER>` to instantiate a new gadget `{name}`.", + Red.bold().paint("error"), + name.to_ascii_uppercase(), + name.to_ascii_uppercase(), + ); + } + + fn validate_gadget>(gadget: &C) -> Result<()> { + // TODO: This is a temporary hack, see: https://github.com/lurk-lab/lurk-rs/issues/621 + set_lurk_dirs(&HashMap::new(), &None, &None, &None, &None); + + if !circom_dir().exists() { + std::fs::create_dir_all(circom_dir())?; + return print_error(gadget.name(), vec![]); + } + + let name = gadget.name(); + let circom_folder = circom_dir().join(name); + + if circom_folder.exists() { + return Ok(()); + }; + + let mut subdirs = Vec::new(); + + for entry in read_dir(circom_dir())? { + let entry = entry?; + let path = entry.path(); + + if path.is_dir() { + if let Some(dir_name) = path.file_name() { + if let Some(dir_name) = dir_name.to_str() { + subdirs.push(dir_name.to_string()); + } + } + } + } + + if subdirs.contains(&gadget.name().into()) { + return Ok(()); + } + + print_error(gadget.name(), subdirs) + } + + /// A concrete instantiation of a [CircomGadget] with a corresponding [CircomConfig] as a coprocessor. + /// + /// To create a concrete Coproc from this, simply declare something like this: + /// ```ignore + /// #[derive(Clone, Debug, Coproc)] + /// enum ConcreteCoproc { + /// SC(CircomCoprocessor>), + /// } + /// ``` + #[derive(Debug)] + pub struct CircomCoprocessor> { + gadget: C, + config: CircomConfig, + } + + impl> Clone for CircomCoprocessor { + fn clone(&self) -> Self { + CircomCoprocessor::new(self.gadget.clone()) + } + } + + impl> CoCircuit for CircomCoprocessor { + /// TODO: Generalize + fn arity(&self) -> usize { + 0 + } + + fn synthesize>( + &self, + cs: &mut CS, + g: &GlobalAllocations, + _store: &Store, + input_exprs: &[AllocatedPtr], + input_env: &AllocatedPtr, + input_cont: &AllocatedContPtr, + ) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedContPtr), SynthesisError> + { + let input = self.gadget.clone().into_circom_input(input_exprs); + let witness = + circom_scotia::calculate_witness(&self.config, input, true).map_err(|e| { + eprintln!("{:?}", e); + SynthesisError::Unsatisfiable + })?; + let output = circom_scotia::synthesize(cs, self.config.r1cs.clone(), Some(witness))?; + + let res = AllocatedPtr::from_parts(g.num_tag.clone(), output); + + Ok((res, input_env.clone(), input_cont.clone())) + } + } + + impl + Debug> Coprocessor for CircomCoprocessor { + /// TODO: Generalize + fn eval_arity(&self) -> usize { + 0 + } + + fn simple_evaluate(&self, s: &mut Store, args: &[Ptr]) -> Ptr { + self.gadget.simple_evaluate(s, args) + } + + fn has_circuit(&self) -> bool { + true + } + } + + impl> CircomCoprocessor { + /// Creates a [CircomConfig] by loading in the data in `//*` + pub fn create(gadget: C) -> Result { + validate_gadget(&gadget)?; + + let name = gadget.name(); + let circom_folder = circom_dir().join(name); + + let r1cs = circom_folder.join(format!("{name}.r1cs")); + let wasm = circom_folder.join(name).with_extension("wasm"); + + let config = CircomConfig::::new(wasm, r1cs)?; + let coprocessor = Self { config, gadget }; + + Ok(coprocessor) + } + + /// Creates a [CircomCoprocessor] and panics if it fails + pub fn new(gadget: C) -> Self { + CircomCoprocessor::create(gadget).unwrap() + } + + /// The defined name of this coprocessor, which is just the inner gadget's name + pub fn name(&self) -> &str { + self.gadget.name() + } + } +} diff --git a/src/coprocessor/mod.rs b/src/coprocessor/mod.rs index ab6e4a22f2..a1ba648e99 100644 --- a/src/coprocessor/mod.rs +++ b/src/coprocessor/mod.rs @@ -1,14 +1,17 @@ use std::fmt::Debug; -use bellperson::{ConstraintSystem, SynthesisError}; +use bellpepper_core::{ConstraintSystem, SynthesisError}; +use crate::circuit::circuit_frame::destructure_list; +use crate::circuit::gadgets::constraints::alloc_equal_const; use crate::circuit::gadgets::data::GlobalAllocations; -use crate::circuit::gadgets::pointer::{AllocatedContPtr, AllocatedPtr}; +use crate::circuit::gadgets::pointer::{AllocatedContPtr, AllocatedPtr, AsAllocatedHashComponents}; use crate::eval::IO; use crate::field::LurkField; use crate::ptr::{ContPtr, Ptr}; use crate::store::Store; +pub mod circom; pub mod trie; /// `Coprocessor` is a trait that represents a generalized interface for coprocessors. @@ -60,6 +63,71 @@ pub trait Coprocessor: Clone + Debug + Sync + Send + CoCircuit fn has_circuit(&self) -> bool { false } + + fn synthesize_step_circuit>( + &self, + cs: &mut CS, + store: &Store, + g: &GlobalAllocations, + input_expr: &AllocatedPtr, + input_env: &AllocatedPtr, + input_cont: &AllocatedContPtr, + ) -> Result<(AllocatedPtr, AllocatedPtr, AllocatedContPtr), SynthesisError> { + // TODO: This code is almost identical to that in circuit_frame.rs (the arg destructuring is factored out and shared there). + // Refactor to share. + let arity = self.arity(); + let (form, actual_length) = destructure_list( + &mut cs.namespace(|| "coprocessor form"), + store, + g, + arity + 1, + input_expr, + )?; + let _head = &form[0]; + let inputs = &form[1..]; + + let arity_is_correct = alloc_equal_const( + &mut cs.namespace(|| "arity_is_correct"), + &actual_length, + F::from(1 + arity as u64), + )?; + + let (result_expr, result_env, result_cont) = + self.synthesize(cs, g, store, &inputs[..arity], input_env, input_cont)?; + + let quoted_expr = AllocatedPtr::construct_list( + &mut cs.namespace(|| "quote coprocessor result"), + g, + store, + &[&g.quote_ptr, &result_expr], + )?; + + let default_num_pair = &[&g.default_num, &g.default_num]; + + // TODO: This should be better abstracted, perhaps by resurrecting historical code. + let tail_components: &[&dyn AsAllocatedHashComponents; 4] = &[ + &result_env, + &result_cont, + default_num_pair, + default_num_pair, + ]; + + let tail_cont = AllocatedContPtr::construct( + &mut cs.namespace(|| "coprocessor tail cont"), + store, + &g.tail_cont_tag, + tail_components, + )?; + + // FIXME: technically, the error is defined to be rest -- which is the cdr of input_expr. + //let new_expr = pick_ptr!(cs, &arity_is_correct, "ed_expr, &rest)?; + let new_expr = pick_ptr!(cs, &arity_is_correct, "ed_expr, &input_expr)?; + + let new_env = pick_ptr!(cs, &arity_is_correct, &result_env, &input_env)?; + let new_cont = pick_cont_ptr!(cs, &arity_is_correct, &tail_cont, &g.error_ptr_cont)?; + + Ok((new_expr, new_env, new_cont)) + } } /// `CoCircuit` is a trait that represents a generalized interface for coprocessors. diff --git a/src/coprocessor/trie/mod.rs b/src/coprocessor/trie/mod.rs index 11acc19dd4..081a720c0c 100644 --- a/src/coprocessor/trie/mod.rs +++ b/src/coprocessor/trie/mod.rs @@ -10,17 +10,21 @@ //! proof, will actually be a proof *verifying* that the vanilla operation was correctly performed. Therefore, the //! vanilla operation needs to provide such a proof so the circuit can verify it. +use std::cell::RefCell; // TODO: // - Implement circuit (https://github.com/lurk-lab/lurk-rs/issues/421). // - Adapt to ongoing changes to general coprocessor API, most importantly, absorb // https://github.com/lurk-lab/lurk-rs/issues/398. - If #398 is smooth enough, no actual implementation changes // should be required here, but the test in src/eval/tests/trie.rs can and should be updated. use std::marker::PhantomData; +use std::rc::Rc; use lurk_macros::Coproc; use serde::{Deserialize, Serialize}; -use crate::{self as lurk, sym}; +use crate::package::Package; +use crate::state::State; +use crate::{self as lurk, Symbol}; use crate::coprocessor::{CoCircuit, Coprocessor}; use crate::eval::lang::Lang; @@ -124,34 +128,34 @@ impl CoCircuit for InsertCoprocessor {} /// Add the `Trie`-associated functions to a `Lang` with standard bindings. // TODO: define standard patterns for such modularity. -pub fn install(s: &mut Store, lang: &mut Lang>) { +pub fn install( + s: &mut Store, + state: Rc>, + lang: &mut Lang>, +) { + lang.add_binding((".lurk.trie.new", NewCoprocessor::default().into()), s); lang.add_binding( - ( - sym!("lurk", "trie", "new"), - NewCoprocessor::default().into(), - ), + (".lurk.trie.lookup", LookupCoprocessor::default().into()), s, ); lang.add_binding( - ( - sym!("lurk", "trie", "lookup"), - LookupCoprocessor::default().into(), - ), - s, - ); - lang.add_binding( - ( - sym!("lurk", "trie", "insert"), - InsertCoprocessor::default().into(), - ), + (".lurk.trie.insert", InsertCoprocessor::default().into()), s, ); + + let name: Symbol = ".lurk.trie".into(); + let mut package = Package::new(name.into()); + package.intern("new".into()); + package.intern("lookup".into()); + package.intern("insert".into()); + state.borrow_mut().add_package(package); } //pub type ChildMap = HashMap, [F; ARITY]>; pub type ChildMap = InversePoseidonCache; /// A sparse Trie. +#[derive(Debug)] pub struct Trie<'a, F: LurkField, const ARITY: usize, const HEIGHT: usize> { root: F, empty_roots: [F; HEIGHT], @@ -159,6 +163,7 @@ pub struct Trie<'a, F: LurkField, const ARITY: usize, const HEIGHT: usize> { children: &'a mut ChildMap, } +#[derive(Debug)] pub struct LookupProof { preimage_path: PreimagePath, } @@ -185,6 +190,7 @@ impl LookupProof { old_proof: LookupProof, new_proof: LookupProof, @@ -358,7 +364,7 @@ impl<'a, F: LurkField, const ARITY: usize, const HEIGHT: usize> Trie<'a, F, ARIT None => { let field_significant_bits = F::NUM_BITS as usize; let height_needed_for_field = field_significant_bits / arity_bits - + ((field_significant_bits % arity_bits != 0) as usize); + + usize::from(field_significant_bits % arity_bits != 0); // HEIGHT must be exactly the minimum required so that every field element has a unique path. assert_eq!(height_needed_for_field, HEIGHT); diff --git a/src/error.rs b/src/error.rs index bb1ef2f2c7..7d93e701e3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -3,7 +3,7 @@ use crate::field::LurkField; use crate::hash_witness::ConsName; use crate::store; -use bellperson::SynthesisError; +use bellpepper_core::SynthesisError; use nova::errors::NovaError; use thiserror::Error; diff --git a/src/eval/lang.rs b/src/eval/lang.rs index 4e491251b7..651852bb84 100644 --- a/src/eval/lang.rs +++ b/src/eval/lang.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::fmt::Debug; use std::marker::PhantomData; +use indexmap::IndexMap; use lurk_macros::Coproc; use serde::{Deserialize, Serialize}; @@ -12,7 +13,7 @@ use crate::store::Store; use crate::symbol::Symbol; use crate::z_ptr::ZExprPtr; -use crate as lurk; +use crate::{self as lurk, lurk_sym_ptr}; /// `DummyCoprocessor` is a concrete implementation of the [`crate::coprocessor::Coprocessor`] trait. /// @@ -36,7 +37,7 @@ impl Coprocessor for DummyCoprocessor { /// but for now it exists as an exemplar demonstrating the intended shape of enums like the default, `Coproc`. fn simple_evaluate(&self, s: &mut Store, args: &[Ptr]) -> Ptr { assert!(args.is_empty()); - s.get_nil() + lurk_sym_ptr!(s, nil) } } @@ -78,21 +79,22 @@ pub enum Coproc { // TODO: Define a trait for the Hash and parameterize on that also. #[derive(Debug, Default, Clone, Deserialize, Serialize)] pub struct Lang> { - // A HashMap that stores coprocessors with their associated `Sym` keys. - coprocessors: HashMap)>, + /// An IndexMap that stores coprocessors with their associated `Sym` keys. + coprocessors: IndexMap)>, + index: HashMap, usize>, } impl> Lang { + #[inline] pub fn new() -> Self { Self { coprocessors: Default::default(), + index: Default::default(), } } pub fn new_with_bindings>>(s: &mut Store, bindings: Vec) -> Self { - let mut new = Self { - coprocessors: Default::default(), - }; + let mut new = Self::new(); for b in bindings { new.add_binding(b.into(), s); } @@ -104,8 +106,7 @@ impl> Lang { let mut key = String::new(); if self.has_coprocessors() { for coprocessor in &self.coprocessors { - let name = coprocessor.0.path.join("-"); - + let name = coprocessor.0.path().join("-"); key += name.as_str() } } else { @@ -121,22 +122,21 @@ impl> Lang { store: &mut Store, ) { let name = name.into(); - // TODO: Check if intern_symbol should take a reference - let ptr = store.intern_symbol(name.clone()); + let ptr = store.intern_symbol(&name); let z_ptr = store.hash_expr(&ptr).unwrap(); - self.coprocessors.insert(name, (cproc.into(), z_ptr)); + self.coprocessors + .insert(name.clone(), (cproc.into(), z_ptr)); + self.index.insert(z_ptr, self.index.len()); } pub fn add_binding>>(&mut self, binding: B, store: &mut Store) { let Binding { name, coproc, _p } = binding.into(); - let ptr = store.intern_symbol(name.clone()); - let z_ptr = store.hash_expr(&ptr).unwrap(); - - self.coprocessors.insert(name, (coproc, z_ptr)); + self.add_coprocessor(name, coproc, store); } - pub fn coprocessors(&self) -> &HashMap)> { + #[inline] + pub fn coprocessors(&self) -> &IndexMap)> { &self.coprocessors } @@ -154,17 +154,45 @@ impl> Lang { maybe_sym.and_then(|sym| self.coprocessors.get(&sym)) } + #[inline] pub fn has_coprocessors(&self) -> bool { !self.coprocessors.is_empty() } + #[inline] pub fn is_default(&self) -> bool { !self.has_coprocessors() } + + #[inline] + pub fn coprocessor_count(&self) -> usize { + self.coprocessors.len() + } + + #[inline] + pub fn get_index(&self, z_ptr: &ZExprPtr) -> Option { + self.index.get(z_ptr).copied() + } + + pub fn get_coprocessor(&self, index: usize) -> Option<&C> { + self.coprocessors.get_index(index).map(|(_, (c, _))| c) + } + + pub fn get_coprocessor_z_ptr(&self, index: usize) -> Option<&ZExprPtr> { + self.coprocessors + .get_index(index) + .map(|(_, (_, z_ptr))| z_ptr) + } + + pub fn get_coprocessor_from_zptr(&self, z_ptr: &ZExprPtr) -> Option<&C> { + self.get_index(z_ptr) + .and_then(|index| self.get_coprocessor(index)) + } } /// A `Binding` associates a name (`Sym`) and `Coprocessor`. It facilitates modular construction of `Lang`s using /// `Coprocessor`s. +#[derive(Debug)] pub struct Binding> { name: Symbol, coproc: C, diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 4750b2981b..44fd0122ed 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -4,13 +4,14 @@ use crate::expr::Expression; use crate::field::LurkField; use crate::hash_witness::{ConsWitness, ContWitness}; use crate::ptr::{ContPtr, Ptr}; -use crate::store; +use crate::state::{initial_lurk_state, State}; use crate::store::Store; use crate::tag::ContTag; use crate::writer::Write; +use crate::z_ptr::ZExprPtr; +use crate::{lurk_sym_ptr, store}; use lang::Lang; -use log::info; #[cfg(not(target_arch = "wasm32"))] use lurk_macros::serde_test; #[cfg(not(target_arch = "wasm32"))] @@ -19,6 +20,7 @@ use serde::{Deserialize, Serialize}; use std::cmp::PartialEq; use std::iter::{Iterator, Take}; use std::marker::PhantomData; +use tracing::info; pub mod lang; @@ -35,40 +37,71 @@ pub struct IO { } impl Write for IO { - fn fmt(&self, store: &Store, w: &mut W) -> std::io::Result<()> { + fn fmt( + &self, + store: &Store, + state: &State, + w: &mut W, + ) -> std::io::Result<()> { write!(w, "IO {{ expr: ")?; - self.expr.fmt(store, w)?; + self.expr.fmt(store, state, w)?; write!(w, ", env: ")?; - self.env.fmt(store, w)?; + self.env.fmt(store, state, w)?; write!(w, ", cont: ")?; - self.cont.fmt(store, w)?; + self.cont.fmt(store, state, w)?; write!(w, " }}") } } impl std::fmt::Display for IO { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - write!(f, "{:?}", self) + write!(f, "{self:?}") } } -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Frame { +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum Meta { + #[default] + Lurk, + Coprocessor(ZExprPtr), +} + +impl Meta { + pub fn is_lurk(&self) -> bool { + *self == Self::Lurk + } + + pub fn is_coprocessor(&self) -> bool { + matches!(self, Self::Coprocessor(_)) + } + + pub fn get_coprocessor_z_ptr(&self) -> Option<&ZExprPtr> { + match self { + Self::Lurk => None, + Self::Coprocessor(z_ptr) => Some(z_ptr), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Frame { pub input: T, pub output: T, pub i: usize, pub witness: W, + pub meta: Meta, pub _p: PhantomData, } -impl Frame { +impl Frame { #[inline] - fn new(input: T, output: T, i: usize, witness: W) -> Self { + fn new(input: T, output: T, i: usize, witness: W, meta: Meta) -> Self { Self { input, output, i, witness, + meta, _p: Default::default(), } } @@ -137,7 +170,7 @@ impl From> for Status { } } -impl> Frame, W, C> { +impl> Frame, W, F, C> { pub fn precedes(&self, maybe_next: &Self) -> bool { let sequential = self.i + 1 == maybe_next.i; let io_match = self.output == maybe_next.input; @@ -156,7 +189,7 @@ impl> Frame, W, C> { as Evaluable, C>>::log(&self.output, store, self.i + 1); } - pub fn significant_frame_count(frames: &[Frame, W, C>]) -> usize { + pub fn significant_frame_count(frames: &[Frame, W, F, C>]) -> usize { frames .iter() .rev() @@ -166,7 +199,11 @@ impl> Frame, W, C> { } pub trait Evaluable> { - fn reduce(&self, store: &mut Store, lang: &Lang) -> Result<(Self, W), ReductionError> + fn reduce( + &self, + store: &mut Store, + lang: &Lang, + ) -> Result<(Self, W, Meta), ReductionError> where Self: Sized; @@ -183,10 +220,10 @@ impl> Evaluable, C> for IO { &self, store: &mut Store, lang: &Lang, - ) -> Result<(Self, Witness), ReductionError> { - let (expr, env, cont, witness) = + ) -> Result<(Self, Witness, Meta), ReductionError> { + let (expr, env, cont, witness, meta) = reduction::reduce(self.expr, self.env, self.cont, store, lang)?; - Ok((Self { expr, env, cont }, witness)) + Ok((Self { expr, env, cont }, witness, meta)) } fn status(&self) -> Status { @@ -209,11 +246,14 @@ impl> Evaluable, C> for IO { info!( "Frame: {}\n\tExpr: {}\n\tEnv: {}\n\tCont: {}{}", i, - self.expr.fmt_to_string(store), - self.env.fmt_to_string(store), - self.cont.fmt_to_string(store), + self.expr.fmt_to_string(store, initial_lurk_state()), + self.env.fmt_to_string(store, initial_lurk_state()), + self.cont.fmt_to_string(store, initial_lurk_state()), if let Some(emitted) = self.maybe_emitted_expression(store) { - format!("\n\tOutput: {}", emitted.fmt_to_string(store)) + format!( + "\n\tOutput: {}", + emitted.fmt_to_string(store, initial_lurk_state()) + ) } else { "".to_string() } @@ -260,11 +300,8 @@ impl IO { } } -impl< - F: LurkField, - T: Evaluable, C> + Clone + PartialEq + Copy, - C: Coprocessor, - > Frame, C> +impl, C> + Copy, C: Coprocessor> + Frame, F, C> { pub(crate) fn next( &self, @@ -272,7 +309,7 @@ impl< lang: &Lang, ) -> Result { let input = self.output; - let (output, witness) = input.reduce(store, lang)?; + let (output, witness, meta) = input.reduce(store, lang)?; // FIXME: Why isn't this method found? // self.log(store); @@ -282,16 +319,14 @@ impl< output, i: self.i + 1, witness, + meta, _p: Default::default(), }) } } -impl< - F: LurkField, - T: Evaluable, C> + Clone + PartialEq + Copy, - C: Coprocessor, - > Frame, C> +impl, C> + Copy, C: Coprocessor + Clone> + Frame, F, C> { fn from_initial_input( input: T, @@ -299,12 +334,13 @@ impl< lang: &Lang, ) -> Result { input.log(store, 0); - let (output, witness) = input.reduce(store, lang)?; + let (output, witness, meta) = input.reduce(store, lang)?; Ok(Self { input, output, i: 0, witness, + meta, _p: Default::default(), }) } @@ -313,7 +349,7 @@ impl< #[derive(Debug)] pub struct FrameIt<'a, W: Copy, F: LurkField, C: Coprocessor> { first: bool, - frame: Frame, W, C>, + frame: Frame, W, F, C>, store: &'a mut Store, lang: &'a Lang, } @@ -341,7 +377,7 @@ struct ResultFrame<'a, F: LurkField, C: Coprocessor>( ); impl<'a, F: LurkField, C: Coprocessor> Iterator for ResultFrame<'a, F, C> { - type Item = Result, Witness, C>, ReductionError>; + type Item = Result, Witness, F, C>, ReductionError>; fn next(&mut self) -> Option<::Item> { let frame_it = match &mut self.0 { Ok(f) => f, @@ -367,7 +403,7 @@ impl<'a, F: LurkField, C: Coprocessor> Iterator for ResultFrame<'a, F, C> { } impl<'a, F: LurkField, C: Coprocessor> Iterator for FrameIt<'a, Witness, F, C> { - type Item = Frame, Witness, C>; + type Item = Frame, Witness, F, C>; fn next(&mut self) -> Option<::Item> { // skip first iteration, as one evaluation happens on construction if self.first { @@ -386,7 +422,7 @@ impl<'a, F: LurkField, C: Coprocessor> Iterator for FrameIt<'a, Witness, F } } -#[derive(Clone, Copy, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Witness { pub(crate) prethunk_output_expr: Ptr, pub(crate) prethunk_output_env: Ptr, @@ -428,7 +464,7 @@ where if Evaluable::, C>::is_complete(&io) { break; } - (io, _) = io.reduce(self.store, self.lang)?; + (io, _, _) = io.reduce(self.store, self.lang)?; if let Some(emitted) = io.maybe_emitted_expression(self.store) { emitted_vec.push(emitted); } @@ -458,13 +494,13 @@ where /// Note: the output will have an identity frame at the end if there's still /// room, that is, if `self.limit` hasn't been reached. This is useful for /// proving when padding the last frame is necessary. - pub fn get_frames(&mut self) -> Result, Witness, C>>, ReductionError> { + pub fn get_frames(&mut self) -> Result, Witness, F, C>>, ReductionError> { let mut input = self.initial(); Evaluable::, C>::log(&input, self.store, 0); let mut frames = vec![]; for i in 0..self.limit { - let (output, witness) = input.reduce(self.store, self.lang)?; - let frame = Frame::new(input, output, i, witness); + let (output, witness, meta) = input.reduce(self.store, self.lang)?; + let frame = Frame::new(input, output, i, witness, meta); let is_complete = frame.is_complete(); frames.push(frame); if is_complete { @@ -477,6 +513,7 @@ where Ok(frames) } + #[tracing::instrument(skip_all, name = "Evaluator::generate_frames")] pub fn generate_frames bool>( expr: Ptr, env: Ptr, @@ -484,7 +521,7 @@ where limit: usize, needs_frame_padding: Fp, lang: &'a Lang, - ) -> Result, Witness, C>>, ReductionError> { + ) -> Result, Witness, F, C>>, ReductionError> { let mut evaluator = Self::new(expr, env, store, limit, lang); let mut frames = evaluator.get_frames()?; @@ -508,8 +545,9 @@ where } } +#[inline] pub fn empty_sym_env(store: &Store) -> Ptr { - store.get_nil() + lurk_sym_ptr!(store, nil) } // Convenience functions, mostly for use in tests. @@ -527,6 +565,7 @@ pub fn eval_to_ptr>( .expr) } +#[derive(Debug)] pub struct Evaluator<'a, F: LurkField, C: Coprocessor> { expr: Ptr, env: Ptr, diff --git a/src/eval/reduction.rs b/src/eval/reduction.rs index 263f1b8623..4dbe9b1a33 100644 --- a/src/eval/reduction.rs +++ b/src/eval/reduction.rs @@ -1,4 +1,4 @@ -use super::{empty_sym_env, Witness}; +use super::{empty_sym_env, Meta, Witness}; use crate::cont::Continuation; use crate::coprocessor::Coprocessor; use crate::error::ReductionError; @@ -8,10 +8,11 @@ use crate::field::LurkField; use crate::hash_witness::{ConsName, ConsWitness, ContName, ContWitness}; use crate::num::Num; use crate::ptr::{ContPtr, Ptr, TypePredicates}; -use crate::store; +use crate::state::initial_lurk_state; use crate::store::{NamedConstants, Store}; use crate::tag::{ContTag, ExprTag, Op1, Op2}; use crate::writer::Write; +use crate::{lurk_sym_ptr, store}; pub(crate) fn reduce>( expr: Ptr, @@ -19,12 +20,12 @@ pub(crate) fn reduce>( cont: ContPtr, store: &mut Store, lang: &Lang, -) -> Result<(Ptr, Ptr, ContPtr, Witness), ReductionError> { - let c = *store.get_constants(); - let (ctrl, witness) = reduce_with_witness(expr, env, cont, store, &c, lang)?; +) -> Result<(Ptr, Ptr, ContPtr, Witness, Meta), ReductionError> { + let c = *store.expect_constants(); + let (ctrl, witness, meta) = reduce_with_witness(expr, env, cont, store, &c, lang)?; let (new_expr, new_env, new_cont) = ctrl.into_results(store); - Ok((new_expr, new_env, new_cont, witness)) + Ok((new_expr, new_env, new_cont, witness, meta)) } #[derive(Debug, Clone)] @@ -38,9 +39,9 @@ enum Control { impl Control { fn into_results(self, store: &mut Store) -> (Ptr, Ptr, ContPtr) { match self { - Self::Return(expr, env, cont) => (expr, env, cont), - Self::MakeThunk(expr, env, cont) => (expr, env, cont), - Self::ApplyContinuation(expr, env, cont) => (expr, env, cont), + Self::Return(expr, env, cont) + | Self::MakeThunk(expr, env, cont) + | Self::ApplyContinuation(expr, env, cont) => (expr, env, cont), Self::Error(expr, env) => (expr, env, store.intern_cont_error()), } } @@ -56,7 +57,6 @@ impl Control { } } -#[allow(clippy::too_many_arguments)] fn reduce_with_witness_inner>( expr: Ptr, env: Ptr, @@ -66,7 +66,7 @@ fn reduce_with_witness_inner>( cont_witness: &mut ContWitness, c: &NamedConstants, lang: &Lang, -) -> Result<(Control, Option>), ReductionError> { +) -> Result<(Control, Option>, Meta), ReductionError> { // sanity-check: this should return the number of iterations // of the last 5s of computation metrics::counter!("evaluation", 1, "type" => "step"); @@ -101,7 +101,7 @@ fn reduce_with_witness_inner>( }, ExprTag::Sym => { - if expr == c.nil.ptr() || (expr == store.t()) { + if expr == c.nil.ptr() || (expr == lurk_sym_ptr!(store, t)) { // NIL and T are self-evaluating symbols, pass them to the continuation in a thunk. // NOTE: For now, NIL is its own type, but this will change soon, so leave the check here. @@ -278,7 +278,7 @@ fn reduce_with_witness_inner>( let pair = cons_witness.car_cdr_named($cons_name, store, $cons); if matches!(pair, Err(ReductionError::CarCdrType(_))) { - return Ok((Control::Error(expr, env), None)); + return Ok((Control::Error(expr, env), None, Meta::Lurk)); } else { pair } @@ -323,7 +323,7 @@ fn reduce_with_witness_inner>( let (arg, _rest) = if args.is_nil() { // (LAMBDA () STUFF) // becomes (LAMBDA (DUMMY) STUFF) - (dummy_arg, store.nil()) + (dummy_arg, lurk_sym_ptr!(store, nil)) } else { cons_witness.car_cdr_named(ConsName::ExprCadr, store, &args)? }; @@ -345,7 +345,7 @@ fn reduce_with_witness_inner>( ); let l = cons_witness.cons_named(ConsName::Lambda, store, lambda, inner); - let nil = store.nil(); + let nil = lurk_sym_ptr!(store, nil); cons_witness.cons_named(ConsName::InnerBody, store, l, nil) }; let function = store.intern_fun(arg, inner_body, env); @@ -492,7 +492,7 @@ fn reduce_with_witness_inner>( } } else if head == c.eval.ptr() { if rest.is_nil() { - return Ok((Control::Error(expr, env), None)); + return Ok((Control::Error(expr, env), None, Meta::Lurk)); } let (arg1, more) = car_cdr_named!(ConsName::ExprCdr, &rest)?; @@ -552,21 +552,43 @@ fn reduce_with_witness_inner>( let args = rest; // NOTE: Any Coprocessor found will take precedence, which means coprocessor bindings cannot be shadowed. - if let Some((coprocessor, _z_ptr)) = lang.lookup(store, head) { + if let Some((coprocessor, z_ptr)) = lang.lookup(store, head) { let (_arg, _more_args) = car_cdr_named!(ConsName::ExprCdr, &args)?; - let IO { expr, env, cont } = - coprocessor.evaluate(store, args, env, cont); + let IO { + expr, + env, + // This continuation can't be used directly because we are returning a quoted result for evaluation. + // This is to avoid having to begin the first reduction (in NIVC) after an evaluation, in the middle (ApplyContinuation), + // or alternately performing the ApplyContinuation part of reduction at the end of each (NIVC) coprocessor step. + // Instead, we wrap this in a (potentially unoptimized) tail call. + cont, + } = coprocessor.evaluate(store, args, env, cont); + + if cont == store.intern_cont_error() { + return Ok(( + Control::Error(expr, env), + None, + Meta::Coprocessor(*z_ptr), + )); + } else { + let quoted = store.list(&[quote, expr]); + + // NOTE: This will be a potentially redundant tail cont, which is 'okay' as long as circuit does same. + // Alternately, here and in circuit(s), we could use the dynamic logic to avoid. + let tail_cont = make_tail_continuation_raw(env, cont, store); - return Ok(( - Control::ApplyContinuation(expr, env, cont), - closure_to_extend, - )); + return Ok(( + Control::Return(quoted, env, tail_cont), + None, + Meta::Coprocessor(*z_ptr), + )); + } }; // `fun_form` must be a function or potentially evaluate to one. if !fun_form.is_potentially(ExprTag::Fun) { - dbg!("not potentially fun"); + tracing::debug!("not potentially fun"); Control::Error(expr, env) } else if args.is_nil() { Control::Return( @@ -602,7 +624,7 @@ fn reduce_with_witness_inner>( _ => { // Interpreting as multi-arg call. // (fn arg . more_args) => ((fn arg) . more_args) - let nil = store.nil(); + let nil = lurk_sym_ptr!(store, nil); let expanded_inner0 = cons_witness.cons_named( ConsName::ExpandedInner0, store, @@ -630,6 +652,7 @@ fn reduce_with_witness_inner>( } }, closure_to_extend, + Meta::Lurk, )) } @@ -640,11 +663,11 @@ fn reduce_with_witness>( store: &mut Store, c: &NamedConstants, lang: &Lang, -) -> Result<(Control, Witness), ReductionError> { +) -> Result<(Control, Witness, Meta), ReductionError> { let cons_witness = &mut ConsWitness::::new_dummy(); let cont_witness = &mut ContWitness::::new_dummy(); - let (control, closure_to_extend) = + let (control, closure_to_extend, meta) = reduce_with_witness_inner(expr, env, cont, store, cons_witness, cont_witness, c, lang)?; let (new_expr, new_env, new_cont) = control.clone().into_results(store); @@ -667,7 +690,7 @@ fn reduce_with_witness>( witness.conses.assert_invariants(store); witness.conts.assert_invariants(store); - Ok((ctrl, witness)) + Ok((ctrl, witness, meta)) } fn apply_continuation( @@ -886,11 +909,11 @@ fn apply_continuation( } } Op1::Atom => match result.tag { - ExprTag::Cons => store.nil(), - _ => store.t(), + ExprTag::Cons => lurk_sym_ptr!(store, nil), + _ => lurk_sym_ptr!(store, t), }, Op1::Emit => { - println!("{}", result.fmt_to_string(store)); + println!("{}", result.fmt_to_string(store, initial_lurk_state())); return Ok(Control::MakeThunk( result, env, @@ -1127,8 +1150,7 @@ fn apply_continuation( Err(control) => return Ok(control), } } - (Expression::Char(_), Expression::EmptyStr) - | (Expression::Char(_), Expression::Str(..)) + (Expression::Char(_), Expression::EmptyStr | Expression::Str(..)) if matches!(operator, Op2::StrCons) => { cons_witness.strcons_named(ConsName::TheCons, store, evaled_arg, arg2) @@ -1303,6 +1325,18 @@ fn make_tail_continuation( // point to one another: they can only be nested one deep. } +fn make_tail_continuation_raw( + saved_env: Ptr, + continuation: ContPtr, + store: &mut Store, +) -> ContPtr { + Continuation::Tail { + saved_env, + continuation, + } + .intern_aux(store) +} + // Only used in tests. Real evalution should use extend_name. #[allow(dead_code)] pub(crate) fn extend( @@ -1329,7 +1363,7 @@ fn extend_rec( match var_or_binding.tag { // It's a var, so we are extending a simple env with a recursive env. ExprTag::Sym | ExprTag::Nil => { - let nil = store.nil(); + let nil = lurk_sym_ptr!(store, nil); let list = cons_witness.cons_named(ConsName::NewRec, store, cons, nil); let res = cons_witness.cons_named(ConsName::ExtendedRec, store, list, env); @@ -1377,9 +1411,9 @@ fn extend_closure( impl Store { pub fn as_lurk_boolean(&mut self, x: bool) -> Ptr { if x { - self.t() + lurk_sym_ptr!(self, t) } else { - self.nil() + lurk_sym_ptr!(self, nil) } } } @@ -1393,7 +1427,7 @@ pub(crate) fn lookup( ) -> Result, store::Error> { assert!(matches!(var.tag, ExprTag::Sym)); match env.tag { - ExprTag::Nil => Ok(store.get_nil()), + ExprTag::Nil => Ok(lurk_sym_ptr!(store, nil)), ExprTag::Cons => { let (binding, smaller_env) = store.car_cdr(env)?; let (v, val) = store.car_cdr(&binding)?; diff --git a/src/eval/tests/mod.rs b/src/eval/tests/mod.rs index 9c63ccde59..4ee7fad749 100644 --- a/src/eval/tests/mod.rs +++ b/src/eval/tests/mod.rs @@ -1,8 +1,13 @@ +use std::cell::RefCell; +use std::rc::Rc; + use super::*; use crate::coprocessor::Coprocessor; use crate::eval::lang::Coproc; use crate::eval::reduction::{extend, lookup, reduce}; use crate::num::Num; +use crate::package::Package; +use crate::state::{user_sym, State}; use crate::tag::{ExprTag, Op, Op1, Op2}; use lurk_macros::{let_store, lurk, Coproc}; @@ -11,6 +16,45 @@ use pasta_curves::pallas::Scalar as Fr; use crate as lurk; mod trie; +fn test_aux_with_state>( + s: &mut Store, + state: Rc>, + expr: &str, + expected_result: Option>, + expected_env: Option>, + expected_cont: Option>, + expected_emitted: Option>>, + expected_iterations: usize, + lang: Option<&Lang>, +) { + let ptr = s.read_with_state(state, expr).unwrap(); + + if let Some(lang) = lang { + test_aux2( + s, + &ptr, + expected_result, + expected_env, + expected_cont, + expected_emitted, + expected_iterations, + lang, + ) + } else { + let lang = Lang::>::new(); + test_aux2( + s, + &ptr, + expected_result, + expected_env, + expected_cont, + expected_emitted, + expected_iterations, + &lang, + ) + } +} + fn test_aux>( s: &mut Store, expr: &str, @@ -113,7 +157,7 @@ fn test_reduce_simple() { { let num = store.num(123); - let (result, _new_env, _cont, _witness) = reduce( + let (result, _new_env, _cont, _witness, _meta) = reduce( num, empty_sym_env(&store), store.intern_cont_outermost(), @@ -125,8 +169,8 @@ fn test_reduce_simple() { } { - let (result, _new_env, _cont, _witness) = reduce( - store.nil(), + let (result, _new_env, _cont, _witness, _meta) = reduce( + lurk_sym_ptr!(store, nil), empty_sym_env(&store), store.intern_cont_outermost(), &mut store, @@ -371,14 +415,14 @@ fn evaluate_num_equal() { { let expr = "(= 5 5)"; - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); } { let expr = "(= 5 6)"; - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); } @@ -506,7 +550,7 @@ fn evaluate_letrec_body_nil() { let s = &mut Store::::default(); let expr = "(eq nil (let () nil))"; - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 4, None); } @@ -530,7 +574,7 @@ fn evaluate_arithmetic_let() { (/ (+ a b) c))"; let expected = s.num(3); - let new_env = s.nil(); + let new_env = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -908,7 +952,7 @@ fn evaluate_eq() { let s = &mut Store::::default(); let expr = "(eq 'a 'a)"; - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); } @@ -916,7 +960,7 @@ fn evaluate_eq() { let s = &mut Store::::default(); let expr = "(eq 1 1)"; - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); } @@ -924,7 +968,7 @@ fn evaluate_eq() { let s = &mut Store::::default(); let expr = "(eq 'a 1)"; - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); } @@ -933,7 +977,7 @@ fn evaluate_eq() { let s = &mut Store::::default(); let expr = "(eq 1 'a)"; - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); } @@ -950,10 +994,10 @@ fn evaluate_zero_arg_lambda() { } { let expected = { - let arg = s.sym("x"); + let arg = s.user_sym("x"); let num = s.num(123); let body = s.list(&[num]); - let env = s.nil(); + let env = lurk_sym_ptr!(s, nil); s.intern_fun(arg, body, env) }; @@ -1134,7 +1178,7 @@ fn evaluate_map_tree_numequal_bug() { (= (map-tree f (car tree)) (map-tree f (cdr tree))))))) (map-tree (lambda (x) (+ 1 x)) '((1 . 2) . (3 . 4))))"; - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let error = s.get_cont_error(); test_aux::>(s, expr, Some(expected), None, Some(error), None, 169, None); } @@ -1160,7 +1204,7 @@ fn env_lost_bug() { ) (foo '())) "; - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -1208,7 +1252,7 @@ fn test_str_car_cdr_cons() { let a_pple = s.read(r#" (#\a . "pple") "#).unwrap(); let pple = s.read(r#" "pple" "#).unwrap(); let empty = s.intern_string(""); - let nil = s.nil(); + let nil = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); let error = s.get_cont_error(); @@ -1315,7 +1359,7 @@ fn test_one_arg_cons_error() { #[test] fn test_car_nil() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -1332,7 +1376,7 @@ fn test_car_nil() { #[test] fn test_cdr_nil() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -1437,7 +1481,7 @@ fn begin_current_env() { { let s = &mut Store::::default(); let expr = "(begin (current-env))"; - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); test_aux::>(s, expr, Some(expected), None, None, None, 2, None); } } @@ -1458,7 +1502,7 @@ fn begin_current_env1() { let s = &mut Store::::default(); let expr = "(let ((a 1)) (begin 123 (current-env)))"; - let a = s.sym("a"); + let a = s.user_sym("a"); let one = s.num(1); let binding = s.cons(a, one); let expected = s.list(&[binding]); @@ -1470,7 +1514,7 @@ fn begin_current_env1() { fn hide_open() { let s = &mut Store::::default(); let expr = "(open (hide 123 'x))"; - let x = s.sym("x"); + let x = s.user_sym("x"); test_aux::>(s, expr, Some(x), None, None, None, 5, None); } @@ -1487,8 +1531,8 @@ fn hide_opaque_open_available() { assert!(!comm.is_opaque()); - let open = s.lurk_sym("open"); - let x = s.sym("x"); + let open = lurk_sym_ptr!(s, open); + let x = s.user_sym("x"); let lang = Lang::new(); { @@ -1497,7 +1541,7 @@ fn hide_opaque_open_available() { } { - let secret = s.lurk_sym("secret"); + let secret = lurk_sym_ptr!(s, secret); let expr = s.list(&[secret, comm]); let sec = s.num(123); test_aux2::>(s, &expr, Some(sec), None, None, None, 2, &lang); @@ -1522,8 +1566,8 @@ fn hide_opaque_open_unavailable() { let s2 = &mut Store::::default(); let comm = s2.intern_maybe_opaque_comm(*c); - let open = s2.lurk_sym("open"); - let x = s2.sym("x"); + let open = lurk_sym_ptr!(s2, open); + let x = s2.user_sym("x"); let expr = s2.list(&[open, comm]); let lang = Lang::new(); @@ -1535,7 +1579,7 @@ fn hide_opaque_open_unavailable() { fn commit_open_sym() { let s = &mut Store::::default(); let expr = "(open (commit 'x))"; - let x = s.sym("x"); + let x = s.user_sym("x"); test_aux::>(s, expr, Some(x), None, None, None, 4, None); } @@ -1553,13 +1597,13 @@ fn commitment_value() { fn commit_nil() { let s = &mut Store::::default(); let x = s - .read("0x239b15d97a9a69b3db1c9130601ec2a1f8ac2ed6033633e4fb5232d85c622250") + .read("0x1f7f3e554ed27c104d79bb69346996d61a735d5bbedc2da7da2935036d9c4fad") .unwrap(); let expr = "(num (commit nil))"; test_aux::>(s, expr, Some(x), None, None, None, 4, None); - let expr = "(num (commit 'lurk.nil))"; + let expr = "(num (commit '.lurk.nil))"; test_aux::>(s, expr, Some(x), None, None, None, 4, None); } @@ -1767,7 +1811,7 @@ fn char_invalid_tag() { fn terminal_sym() { let s = &mut Store::::default(); let expr = "(quote x)"; - let x = s.sym("x"); + let x = s.user_sym("x"); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(x), None, Some(terminal), None, 1, None); } @@ -1814,7 +1858,11 @@ fn secret_opaque_commit() { fn relational_aux(s: &mut Store, op: &str, a: &str, b: &str, res: bool) { let expr = &format!("({op} {a} {b})"); - let expected = if res { s.t() } else { s.nil() }; + let expected = if res { + lurk_sym_ptr!(s, t) + } else { + lurk_sym_ptr!(s, nil) + }; let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); @@ -1935,7 +1983,7 @@ fn test_relational() { #[test] fn test_relational_edge_case_identity() { let s = &mut Store::::default(); - let t = s.t(); + let t = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); // Normally, a value cannot be less than the result of incrementing it. @@ -1964,7 +2012,7 @@ fn test_relational_edge_case_identity() { #[test] fn test_num_syntax_implications() { let s = &mut Store::::default(); - let t = s.t(); + let t = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); { @@ -2193,8 +2241,8 @@ fn test_u64_comp() { let expr11 = "(= 0u64 0u64)"; let expr12 = "(= 0u64 1u64)"; - let t = s.t(); - let nil = s.nil(); + let t = lurk_sym_ptr!(s, t); + let nil = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(t), None, Some(terminal), None, 3, None); @@ -2274,8 +2322,8 @@ fn test_u64_num_comparison() { let expr = "(= 1 1u64)"; let expr2 = "(= 1 2u64)"; - let t = s.t(); - let nil = s.nil(); + let t = lurk_sym_ptr!(s, t); + let nil = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(t), None, Some(terminal), None, 3, None); @@ -2314,8 +2362,8 @@ fn test_keyword() { let expr2 = "(eq :asdf :asdf)"; let expr3 = "(eq :asdf 'asdf)"; let res = s.key("asdf"); - let res2 = s.get_t(); - let res3 = s.get_nil(); + let res2 = lurk_sym_ptr!(s, t); + let res3 = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); @@ -2330,8 +2378,7 @@ fn test_root_sym() { let s = &mut Store::::default(); - let sym = Symbol::root(); - let x = s.intern_symbol(sym); + let x = s.intern_symbol(&Symbol::root_sym()); let z_ptr = &s.hash_expr(&x).unwrap(); @@ -2344,11 +2391,27 @@ fn test_sym_hash_values() { use crate::Symbol; let s = &mut Store::::default(); + let state = State::init_lurk_state().rccell(); + + let asdf_sym_package_name = state + .borrow_mut() + .intern_path(&["asdf"], false, false) + .unwrap(); + let asdf_sym_package = Package::new(asdf_sym_package_name); + state.borrow_mut().add_package(asdf_sym_package); + + let asdf_key_package_name = state + .borrow_mut() + .intern_path(&["asdf"], true, false) + .unwrap(); + let asdf_key_package = Package::new(asdf_key_package_name); + state.borrow_mut().add_package(asdf_key_package); - let sym = s.read(".asdf.fdsa").unwrap(); - // TODO: No longer true for keywords - //let key = s.read(":asdf.fdsa").unwrap(); - let expr = s.read("(cons \"fdsa\" 'asdf)").unwrap(); + let sym = s.read_with_state(state.clone(), ".asdf.fdsa").unwrap(); + let key = s.read_with_state(state.clone(), ":asdf.fdsa").unwrap(); + let expr = s + .read_with_state(state.clone(), "(cons \"fdsa\" '.asdf)") + .unwrap(); let limit = 10; let env = empty_sym_env(s); @@ -2363,17 +2426,16 @@ fn test_sym_hash_values() { _emitted, ) = Evaluator::new(expr, env, s, limit, &lang).eval().unwrap(); - let toplevel_sym = s.read(".asdf").unwrap(); + let toplevel_sym = s.read_with_state(state, ".asdf").unwrap(); - let root = Symbol::root(); - let root_sym = s.intern_symbol(root); + let root_sym = s.intern_symbol(&Symbol::root_sym()); let asdf = s.str("asdf"); let consed_with_root = s.cons(asdf, root_sym); let cons_z_ptr = &s.hash_expr(&new_expr).unwrap(); let sym_z_ptr = &s.hash_expr(&sym).unwrap(); - //let key_z_ptr = &s.hash_expr(&key).unwrap(); + let key_z_ptr = &s.hash_expr(&key).unwrap(); let consed_with_root_z_ptr = &s.hash_expr(&consed_with_root).unwrap(); let toplevel_z_ptr = &s.hash_expr(&toplevel_sym).unwrap(); @@ -2381,14 +2443,14 @@ fn test_sym_hash_values() { // Symbol and keyword scalar hash values are the same as // those of the name string consed onto the parent symbol. assert_eq!(cons_z_ptr.value(), sym_z_ptr.value()); - //assert_eq!(cons_z_ptr.value(), key_z_ptr.value()); + assert_eq!(cons_z_ptr.value(), key_z_ptr.value()); // Toplevel symbols also have this property, and their parent symbol is the root symbol. assert_eq!(consed_with_root_z_ptr.value(), toplevel_z_ptr.value()); // The tags differ though. assert_eq!(ExprTag::Sym, sym_z_ptr.tag()); - //assert_eq!(ExprTag::Key, key_z_ptr.tag()); + assert_eq!(ExprTag::Key, key_z_ptr.tag()); } #[test] @@ -2453,29 +2515,29 @@ fn op_syntax_error() { { let expr = format!("({name} . 1)"); - dbg!(&expr); + tracing::debug!("{}", &expr); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } if !op.supports_arity(0) { let expr = format!("({name})"); - dbg!(&expr); + tracing::debug!("{}", &expr); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } if !op.supports_arity(1) { let expr = format!("({name} 123)"); - dbg!(&expr); + tracing::debug!("{}", &expr); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } if !op.supports_arity(2) { let expr = format!("({name} 123 456)"); - dbg!(&expr); + tracing::debug!("{}", &expr); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } if !op.supports_arity(3) { let expr = format!("({name} 123 456 789)"); - dbg!(&expr); + tracing::debug!("{}", &expr); let iterations = if op.supports_arity(2) { 2 } else { 1 }; test_aux::>(s, &expr, None, None, Some(error), None, iterations, None); } @@ -2563,7 +2625,6 @@ pub(crate) mod coproc { use super::*; use crate::coprocessor::test::DumbCoprocessor; use crate::store::Store; - use crate::sym; #[derive(Clone, Debug, Coproc)] pub(crate) enum DumbCoproc { @@ -2576,24 +2637,43 @@ pub(crate) mod coproc { let lang = Lang::>::new_with_bindings( s, - vec![(sym!("cproc", "dumb"), DumbCoprocessor::new().into())], + vec![(user_sym("cproc-dumb"), DumbCoprocessor::new().into())], ); // 9^2 + 8 = 89 - let expr = "(.cproc.dumb 9 8)"; + let expr = "(cproc-dumb 9 8)"; // The dumb coprocessor cannot be shadowed. - let expr2 = "(let ((.cproc.dumb (lambda (a b) (* a b)))) - (.cproc.dumb 9 8))"; + let expr2 = "(let ((cproc-dumb (lambda (a b) (* a b)))) + (cproc-dumb 9 8))"; // The dumb coprocessor cannot be shadowed. - let expr3 = "(.cproc.dumb 9 8 123))"; + let expr3 = "(cproc-dumb 9 8 123))"; let res = s.num(89); let error = s.get_cont_error(); + let terminal = s.get_cont_terminal(); - test_aux(s, expr, Some(res), None, None, None, 1, Some(&lang)); - test_aux(s, expr2, Some(res), None, None, None, 3, Some(&lang)); + test_aux( + s, + expr, + Some(res), + None, + Some(terminal), + None, + 2, + Some(&lang), + ); + test_aux( + s, + expr2, + Some(res), + None, + Some(terminal), + None, + 5, + Some(&lang), + ); test_aux(s, expr3, None, None, Some(error), None, 1, Some(&lang)); } } diff --git a/src/eval/tests/trie.rs b/src/eval/tests/trie.rs index f1d42b2b7e..5828153f6b 100644 --- a/src/eval/tests/trie.rs +++ b/src/eval/tests/trie.rs @@ -6,9 +6,10 @@ fn trie_lang() { use crate::coprocessor::trie::{install, TrieCoproc}; let s = &mut Store::::default(); + let state = State::init_lurk_state().rccell(); let mut lang = Lang::>::new(); - install(s, &mut lang); + install(s, state.clone(), &mut lang); let expr = "(let ((trie (.lurk.trie.new))) trie)"; @@ -16,7 +17,17 @@ fn trie_lang() { .read("0x1cc5b90039db85fd519af975afa1de9d2b92960a585a546637b653b115bc3b53") .unwrap(); - test_aux(s, expr, Some(res), None, None, None, 3, Some(&lang)); + test_aux_with_state( + s, + state.clone(), + expr, + Some(res), + None, + None, + None, + 5, + Some(&lang), + ); // TODO: Coprocessors need to evaluate their arguments for this to work. // See https://github.com/lurk-lab/lurk-rs/issues/398. @@ -24,20 +35,53 @@ fn trie_lang() { // (found (.lurk.trie.lookup trie 123))) // found)"; - let expr2 = "(.lurk.trie.lookup 0x1cc5b90039db85fd519af975afa1de9d2b92960a585a546637b653b115bc3b53 123)"; + let expr2 = + "(.lurk.trie.lookup 0x1cc5b90039db85fd519af975afa1de9d2b92960a585a546637b653b115bc3b53 123)"; let res2 = s.intern_opaque_comm(Fr::zero()); - test_aux(s, expr2, Some(res2), None, None, None, 1, Some(&lang)); + test_aux_with_state( + s, + state.clone(), + expr2, + Some(res2), + None, + None, + None, + 2, + Some(&lang), + ); - let expr3 = "(.lurk.trie.insert 0x1cc5b90039db85fd519af975afa1de9d2b92960a585a546637b653b115bc3b53 123 456)"; + let expr3 = + "(.lurk.trie.insert 0x1cc5b90039db85fd519af975afa1de9d2b92960a585a546637b653b115bc3b53 123 456)"; let res3 = s .read("0x1b22dc5a394231c34e4529af674dc56a736fbd07508acfd1d12c0e67c8b4de27") .unwrap(); - test_aux(s, expr3, Some(res3), None, None, None, 1, Some(&lang)); + test_aux_with_state( + s, + state.clone(), + expr3, + Some(res3), + None, + None, + None, + 2, + Some(&lang), + ); - let expr4 = "(.lurk.trie.lookup 0x1b22dc5a394231c34e4529af674dc56a736fbd07508acfd1d12c0e67c8b4de27 123)"; + let expr4 = + "(.lurk.trie.lookup 0x1b22dc5a394231c34e4529af674dc56a736fbd07508acfd1d12c0e67c8b4de27 123)"; let res4 = s.intern_opaque_comm(Fr::from(456)); - test_aux(s, expr4, Some(res4), None, None, None, 1, Some(&lang)); + test_aux_with_state( + s, + state, + expr4, + Some(res4), + None, + None, + None, + 2, + Some(&lang), + ); } diff --git a/src/expr.rs b/src/expr.rs index 364e29946f..56319ce53f 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -28,6 +28,7 @@ pub enum Expression { Str(Ptr, Ptr), Thunk(Thunk), RootSym, + RootKey, Sym(Ptr, Ptr), Key(Ptr, Ptr), Char(char), diff --git a/src/field.rs b/src/field.rs index 4ef83d6622..f9f26a7bab 100644 --- a/src/field.rs +++ b/src/field.rs @@ -31,7 +31,7 @@ use crate::tag::{ContTag, ExprTag, Op1, Op2}; /// Because confusion on this point, perhaps combined with cargo-cult copying of incorrect previous usage has led to /// inconsistencies and inaccuracies in the code base, please prefer the named Scalar forms when correspondence to a /// named `LanguageField` is important. -#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[cfg_attr(not(target_arch = "wasm32"), serde_test)] pub enum LanguageField { @@ -84,7 +84,7 @@ pub trait LurkField: PrimeField + PrimeFieldBits { let bytes = self.to_bytes(); let mut s = String::with_capacity(bytes.len() * 2); for b in bytes.iter().rev() { - s.push_str(&format!("{:02x?}", b)); + s.push_str(&format!("{b:02x?}")); } s } @@ -141,6 +141,18 @@ pub trait LurkField: PrimeField + PrimeFieldBits { Some(u64::from_le_bytes(byte_array)) } + /// Attempts to convert the field element to a u64 + fn to_u128(&self) -> Option { + for x in &self.to_repr().as_ref()[16..] { + if *x != 0 { + return None; + } + } + let mut byte_array = [0u8; 16]; + byte_array.copy_from_slice(&self.to_repr().as_ref()[0..16]); + Some(u128::from_le_bytes(byte_array)) + } + /// Converts the first 4 bytes of the field element to a u32 fn to_u32_unchecked(&self) -> u32 { let mut byte_array = [0u8; 4]; @@ -155,6 +167,13 @@ pub trait LurkField: PrimeField + PrimeFieldBits { u64::from_le_bytes(byte_array) } + /// Converts the first 16 bytes of the field element to a u128 + fn to_u128_unchecked(&self) -> u128 { + let mut byte_array = [0u8; 16]; + byte_array.copy_from_slice(&self.to_repr().as_ref()[0..16]); + u128::from_le_bytes(byte_array) + } + /// Constructs a field element from a u64 fn from_u64(x: u64) -> Self { x.into() @@ -162,11 +181,11 @@ pub trait LurkField: PrimeField + PrimeFieldBits { /// Constructs a field element from a u32 fn from_u32(x: u32) -> Self { - (x as u64).into() + u64::from(x).into() } /// Constructs a field element from a u16 fn from_u16(x: u16) -> Self { - (x as u64).into() + u64::from(x).into() } /// Constructs a field element from a char fn from_char(x: char) -> Self { @@ -290,7 +309,7 @@ impl Hash for FWrap { impl PartialOrd for FWrap { fn partial_cmp(&self, other: &Self) -> Option { - (self.0.to_repr().as_ref()).partial_cmp(other.0.to_repr().as_ref()) + Some(self.cmp(other)) } } @@ -377,7 +396,7 @@ pub mod tests { let mut res = F::ZERO; let mut bs = bs.iter().rev().peekable(); while let Some(b) = bs.next() { - let b: F = (*b as u64).into(); + let b: F = u64::from(*b).into(); if bs.peek().is_none() { res.add_assign(b) } else { diff --git a/src/hash.rs b/src/hash.rs index 6de8e89f21..00463144e5 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::hash::Hash; +use std::sync::Arc; use crate::cache_map::CacheMap; use crate::field::{FWrap, LurkField}; @@ -23,11 +24,12 @@ impl From for HashArity { 4 => Self::A4, 6 => Self::A6, 8 => Self::A8, - _ => panic!("unsupported arity: {}", n), + _ => panic!("unsupported arity: {n}"), } } } +#[derive(Debug)] pub enum HashConst<'a, F: LurkField> { A3(&'a PoseidonConstants), A4(&'a PoseidonConstants), @@ -36,7 +38,7 @@ pub enum HashConst<'a, F: LurkField> { } /// Holds the constants needed for poseidon hashing. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct HashConstants { c3: OnceCell>, c4: OnceCell>, @@ -82,12 +84,12 @@ impl HashConstants { } } -#[derive(Default, Debug)] +#[derive(Clone, Default, Debug)] pub struct PoseidonCache { - a3: CacheMap, F>, - a4: CacheMap, F>, - a6: CacheMap, F>, - a8: CacheMap, F>, + a3: Arc, F>>, + a4: Arc, F>>, + a6: Arc, F>>, + a8: Arc, F>>, pub constants: HashConstants, } @@ -112,7 +114,7 @@ impl PoseidonCache { } } -#[derive(Default, Debug)] +#[derive(Clone, Default, Debug)] pub struct InversePoseidonCache { a3: HashMap, [F; 3]>, a4: HashMap, [F; 4]>, diff --git a/src/hash_witness.rs b/src/hash_witness.rs index 3385236855..65e705ecf3 100644 --- a/src/hash_witness.rs +++ b/src/hash_witness.rs @@ -2,12 +2,19 @@ use std::collections::HashMap; use std::fmt::Debug; use std::marker::PhantomData; +use anyhow::{anyhow, Result}; +use once_cell::sync::OnceCell; + use crate::cont::Continuation; use crate::error::ReductionError; -use crate::field::LurkField; +use crate::field::{FWrap, LurkField}; +use crate::hash::HashConst; +use crate::lurk_sym_ptr; use crate::ptr::{ContPtr, Ptr}; +use crate::state::State; use crate::store::{self, Store}; use crate::tag::ExprTag; +use crate::z_ptr::{ZContPtr, ZExprPtr}; pub const MAX_CONSES_PER_REDUCTION: usize = 11; pub const MAX_CONTS_PER_REDUCTION: usize = 2; @@ -25,6 +32,27 @@ impl Stub { } } +pub trait ContentAddressed +where + Self::ScalarPtrRepr: CAddr, +{ + type ScalarPtrRepr; + + fn preimage(&self, s: &Store) -> Result> { + self.to_scalar_ptr_repr(s) + .map(|x| x.preimage()) + .ok_or_else(|| anyhow!("failed to get preimage")) + } + fn to_scalar_ptr_repr(&self, s: &Store) -> Option; + fn to_dummy_scalar_ptr_repr() -> Option { + unimplemented!() + } +} + +pub trait CAddr { + fn preimage(&self) -> Preimage; +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Cons { pub car: Ptr, @@ -32,6 +60,106 @@ pub struct Cons { pub cons: Ptr, } +#[derive(Clone, Debug)] +pub struct ScalarCons { + pub car: ZExprPtr, + pub cdr: ZExprPtr, + pub cons: Option>, +} + +#[derive(Clone, Debug)] +pub struct ScalarCont { + pub components: [F; 8], + pub cont: Option>, +} + +impl, T: CAddr> ContentAddressed + for Stub +{ + type ScalarPtrRepr = T; + + fn to_scalar_ptr_repr(&self, s: &Store) -> Option { + match self { + Stub::Dummy => C::to_dummy_scalar_ptr_repr(), + Stub::Blank => None, + Stub::Value(v) => v.to_scalar_ptr_repr(s), + } + } +} + +impl ContentAddressed for Cons { + type ScalarPtrRepr = ScalarCons; + + fn preimage(&self, s: &Store) -> Result> { + let spr = self.to_scalar_ptr_repr(s).ok_or(anyhow!("missing"))?; + Ok(spr.preimage()) + } + + fn to_scalar_ptr_repr(&self, s: &Store) -> Option { + let car = s.hash_expr(&self.car)?; + let cdr = s.hash_expr(&self.cdr)?; + let cons = Some(s.hash_expr(&self.cons)?); + Some(ScalarCons { car, cdr, cons }) + } + + fn to_dummy_scalar_ptr_repr() -> Option { + let car = ZExprPtr::from_parts(ExprTag::Nil, F::ZERO); + let cdr = ZExprPtr::from_parts(ExprTag::Nil, F::ZERO); + let cons = None; + Some(ScalarCons { car, cdr, cons }) + } +} + +impl ContentAddressed for Cont { + type ScalarPtrRepr = ScalarCont; + + fn preimage(&self, s: &Store) -> Result> { + let spr = self.to_scalar_ptr_repr(s).ok_or(anyhow!("missing"))?; + Ok(spr.preimage()) + } + + fn to_scalar_ptr_repr(&self, s: &Store) -> Option { + let cont = s.hash_cont(&self.cont_ptr)?; + let components = s.get_hash_components_cont(&self.cont_ptr).unwrap(); + Some(ScalarCont { + cont: Some(cont), + components, + }) + } + + fn to_dummy_scalar_ptr_repr() -> Option { + let cont = None; + let components = [ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + ]; + Some(ScalarCont { cont, components }) + } +} + +impl CAddr for ScalarCons { + fn preimage(&self) -> Preimage { + vec![ + self.car.tag_field(), + *self.car.value(), + self.cdr.tag_field(), + *self.cdr.value(), + ] + } +} + +impl CAddr for ScalarCont { + fn preimage(&self) -> Preimage { + self.components.to_vec() + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Cont { pub cont_ptr: ContPtr, @@ -76,12 +204,13 @@ pub enum ConsName { Expanded, } -pub trait HashName { +pub trait HashName: Copy { fn index(&self) -> usize; } impl HashName for ConsName { fn index(&self) -> usize { + #[allow(clippy::match_same_arms)] match self { Self::NeverUsed => MAX_CONSES_PER_REDUCTION + 1, Self::Expr => 0, @@ -131,6 +260,7 @@ pub enum ContName { impl HashName for ContName { fn index(&self) -> usize { + #[allow(clippy::match_same_arms)] match self { Self::NeverUsed => MAX_CONTS_PER_REDUCTION + 1, Self::ApplyContinuation => 0, @@ -166,28 +296,6 @@ impl ConsStub { } } - pub fn car_cdr_mut( - &mut self, - s: &mut Store, - cons: &Ptr, - ) -> Result<(Ptr, Ptr), store::Error> { - match self { - Self::Dummy => { - let (car, cdr) = Cons::get_car_cdr_mut(s, cons)?; - - *self = Self::Value(Cons { - car, - cdr, - cons: *cons, - }); - - Ok((car, cdr)) - } - Self::Blank => unreachable!("Blank ConsStub should be used only in blank circuits."), - Self::Value(h) => Ok(h.car_cdr(cons)), - } - } - pub fn cons(&mut self, store: &mut Store, car: Ptr, cdr: Ptr) -> Ptr { match self { Self::Dummy => { @@ -218,12 +326,76 @@ impl ConsStub { impl ContStub {} -#[derive(Clone, Copy, Debug, PartialEq)] +pub type Preimage = Vec; +pub type PreimageKey = Vec>; +pub type WitnessBlock = Vec; +pub type Digest = F; +pub type HashCircuitWitnessCache = HashMap, (WitnessBlock, Digest)>; +pub type HashCircuitWitnessBlocks = Vec<(WitnessBlock, Digest)>; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct HashWitness { pub slots: [(Name, Stub); L], _f: PhantomData, } +#[derive(Clone, Debug, PartialEq)] +pub struct CircuitHashWitness, const L: usize, F: LurkField> +{ + pub hash_witness: HashWitness, + pub names_and_ptrs: OnceCell)>>, + pub circuit_witness_blocks: OnceCell>, +} + +impl, const L: usize, F: LurkField> + From> for CircuitHashWitness +{ + fn from(hash_witness: HashWitness) -> Self { + Self { + hash_witness, + names_and_ptrs: OnceCell::new(), + circuit_witness_blocks: OnceCell::new(), + } + } +} + +impl, const L: usize, F: LurkField> + CircuitHashWitness +where + T::ScalarPtrRepr: Debug, +{ + pub fn names_and_ptrs(&self, s: &Store) -> &Vec<(Name, Option)> { + self.names_and_ptrs.get_or_init(|| { + self.hash_witness + .slots + .iter() + .map(|(name, x)| (*name, (*x).to_scalar_ptr_repr(s))) + .collect::>() + }) + } + + /// Precompute the witness blocks for all the named hashes. + pub fn circuit_witness_blocks( + &self, + s: &Store, + hash_constants: HashConst<'_, F>, + ) -> &HashCircuitWitnessBlocks { + self.circuit_witness_blocks.get_or_init(|| { + // TODO: In order to be interesting or useful, this should call a Neptune + // API function (which doesn't exist yet) to perform batched witness-generation. + // That code could be optimized and parallelized, eventually even performed on GPU. + self.names_and_ptrs(s) + .iter() + .map(|(_, scalar_ptr_repr)| { + let scalar_ptr_repr = scalar_ptr_repr.as_ref().unwrap(); + let preimage = scalar_ptr_repr.preimage(); + hash_constants.cache_hash_witness_aux(preimage) + }) + .collect::>() + }) + } +} + impl HashWitness { pub fn length() -> usize { L @@ -233,9 +405,12 @@ impl HashWitness pub type ConsWitness = HashWitness, MAX_CONSES_PER_REDUCTION, F>; pub type ContWitness = HashWitness, MAX_CONTS_PER_REDUCTION, F>; +pub type ConsCircuitWitness = CircuitHashWitness, MAX_CONSES_PER_REDUCTION, F>; +pub type ContCircuitWitness = CircuitHashWitness, MAX_CONTS_PER_REDUCTION, F>; + impl HashWitness, MAX_CONSES_PER_REDUCTION, F> { #[allow(dead_code)] - fn assert_specific_invariants(&self, store: &Store) { + fn assert_specific_invariants(&self, store: &Store, state: &State) { // Use the commented code below to search for (non-nil) duplicated conses, which could indicate that two // different Cons are being used to reference the identical structural value. In that case, they could be // coalesced, potentially leading to fewer slots being required. @@ -247,19 +422,22 @@ impl HashWitness, MAX_CONSES_PER_REDUCTION, F> { let mut digests = HashMap::new(); for (name, p) in self.slots.iter() { - match p { - Stub::Value(hash) => { - if let Some(existing_name) = digests.insert(hash.cons, name) { - let nil = store.get_nil(); - if !store.ptr_eq(&hash.cons, &nil).unwrap() { - use crate::writer::Write; - let cons = hash.cons.fmt_to_string(store); - dbg!(hash.cons, cons, name, existing_name); - panic!("duplicate"); - } - }; + if let Stub::Value(hash) = p { + if let Some(existing_name) = digests.insert(hash.cons, name) { + let nil = lurk_sym_ptr!(store, nil); + if !store.ptr_eq(&hash.cons, &nil).unwrap() { + use crate::writer::Write; + let cons = hash.cons.fmt_to_string(store, state); + tracing::debug!( + "{:?} {:?} {:?} {:?}", + hash.cons, + cons, + name, + existing_name + ); + panic!("duplicate"); + } } - _ => (), }; } } @@ -372,10 +550,10 @@ impl ConsWitness { pub fn car_cdr_mut_named( &mut self, name: ConsName, - store: &mut Store, + store: &Store, cons: &Ptr, ) -> Result<(Ptr, Ptr), store::Error> { - self.get_assigned_slot(name).car_cdr_mut(store, cons) + self.get_assigned_slot(name).car_cdr(store, cons) } pub fn extend_named( @@ -410,17 +588,13 @@ impl Cons { fn get_car_cdr(s: &Store, cons: &Ptr) -> Result<(Ptr, Ptr), store::Error> { s.car_cdr(cons) } - - fn get_car_cdr_mut(s: &mut Store, cons: &Ptr) -> Result<(Ptr, Ptr), store::Error> { - s.car_cdr(cons) - } } impl ContWitness { pub fn fetch_named_cont( &mut self, name: ContName, - store: &mut Store, + store: &Store, cont: &ContPtr, ) -> Option> { self.get_assigned_slot(name).fetch_cont(store, cont) @@ -438,15 +612,11 @@ impl ContWitness { } impl ContStub { - pub fn fetch_cont( - &mut self, - store: &mut Store, - cont: &ContPtr, - ) -> Option> { + pub fn fetch_cont(&mut self, store: &Store, cont: &ContPtr) -> Option> { match self { Self::Dummy => { let continuation = store.fetch_cont(cont)?; - // dbg!("overwriting dummy", continuation, store.hash_cont(&cont)); + // tracing::debug!("overwriting dummy {:?} {:?}", continuation, store.hash_cont(&cont)); *self = Self::Value(Cont { cont_ptr: *cont, continuation, diff --git a/src/lem/circuit.rs b/src/lem/circuit.rs index 3c81a257ac..15e180554d 100644 --- a/src/lem/circuit.rs +++ b/src/lem/circuit.rs @@ -21,252 +21,406 @@ //! on a concrete or a virtual path and use such booleans as the premises to build //! the constraints we care about with implication gadgets. -use std::collections::{HashMap, HashSet, VecDeque}; - -use anyhow::{Context, Result}; -use bellperson::{ - gadgets::{ +use anyhow::{anyhow, bail, Result}; +use bellpepper_core::{ + ConstraintSystem, SynthesisError, + { boolean::{AllocatedBit, Boolean}, num::AllocatedNum, }, - ConstraintSystem, }; +use std::collections::{HashMap, HashSet, VecDeque}; use crate::circuit::gadgets::{ constraints::{ - add, alloc_equal, alloc_equal_const, and, enforce_selector_with_premise, implies_equal, - mul, sub, + add, alloc_equal, alloc_is_zero, allocate_is_negative, and, div, enforce_pack, + enforce_product_and_sum, enforce_selector_with_premise, implies_equal, implies_equal_const, + implies_u64, implies_unequal_const, mul, or, pick, sub, }, data::{allocate_constant, hash_poseidon}, pointer::AllocatedPtr, }; -use crate::field::{FWrap, LurkField}; -use crate::tag::ExprTag::*; +use crate::{ + field::{FWrap, LurkField}, + tag::ExprTag::{Comm, Nil, Num, Sym}, +}; use super::{ - interpreter::Frame, - pointers::{Ptr, ZPtr}, + interpreter::{Frame, PreimageData}, + pointers::Ptr, slot::*, store::Store, var_map::VarMap, Block, Ctrl, Func, Op, Tag, Var, }; +pub enum AllocatedVal { + Pointer(AllocatedPtr), + Number(AllocatedNum), + Boolean(Boolean), +} + /// Manages global allocations for constants in a constraint system #[derive(Default)] -pub(crate) struct GlobalAllocator(HashMap, AllocatedNum>); +pub struct GlobalAllocator(HashMap, AllocatedNum>); + +impl GlobalAllocator { + /// Checks if the allocation for a numeric variable has already been cached. + /// If so, don't do anything. Otherwise, allocate and cache it. + fn new_const>(&mut self, cs: &mut CS, f: F) { + self.0.entry(FWrap(f)).or_insert_with(|| { + allocate_constant( + &mut cs.namespace(|| format!("allocate constant {}", f.hex_digits())), + f, + ) + }); + } + + #[inline] + fn get_allocated_const(&self, f: F) -> Result<&AllocatedNum> { + self.0 + .get(&FWrap(f)) + .ok_or_else(|| anyhow!("Global allocation not found for {}", f.hex_digits())) + } + + #[inline] + fn get_allocated_const_cloned(&self, f: F) -> Result> { + self.get_allocated_const(f).cloned() + } +} + +pub(crate) type BoundAllocations = VarMap>; + +impl BoundAllocations { + fn get_many_ptr(&self, args: &[Var]) -> Result>> { + args.iter().map(|arg| self.get_ptr(arg).cloned()).collect() + } + + fn get_ptr(&self, var: &Var) -> Result<&AllocatedPtr> { + if let AllocatedVal::Pointer(ptr) = self.get(var)? { + return Ok(ptr); + } + bail!("Expected {var} to be a pointer") + } + + fn insert_ptr(&mut self, var: Var, ptr: AllocatedPtr) -> Option> { + self.insert(var, AllocatedVal::Pointer(ptr)) + } -#[inline] -fn allocate_num>( + fn get_bool(&self, var: &Var) -> Result<&Boolean> { + if let AllocatedVal::Boolean(b) = self.get(var)? { + return Ok(b); + } + bail!("Expected {var} to be a boolean") + } + + fn insert_bool(&mut self, var: Var, b: Boolean) -> Option> { + self.insert(var, AllocatedVal::Boolean(b)) + } +} + +fn allocate_img_for_slot>( cs: &mut CS, - namespace: &str, - value: F, -) -> Result> { - AllocatedNum::alloc(cs.namespace(|| namespace), || Ok(value)) - .with_context(|| format!("allocation for '{namespace}' failed")) + slot: &Slot, + preallocated_preimg: Vec>, + store: &Store, +) -> Result> { + let mut cs = cs.namespace(|| format!("image for slot {slot}")); + let preallocated_img = { + match slot.typ { + SlotType::Hash4 => AllocatedVal::Number(hash_poseidon( + cs, + preallocated_preimg, + store.poseidon_cache.constants.c4(), + )?), + SlotType::Hash6 => AllocatedVal::Number(hash_poseidon( + cs, + preallocated_preimg, + store.poseidon_cache.constants.c6(), + )?), + SlotType::Hash8 => AllocatedVal::Number(hash_poseidon( + cs, + preallocated_preimg, + store.poseidon_cache.constants.c8(), + )?), + SlotType::Commitment => AllocatedVal::Number(hash_poseidon( + cs, + preallocated_preimg, + store.poseidon_cache.constants.c3(), + )?), + SlotType::LessThan => { + // When a and b have the same sign, a < b iff a - b < 0 + // When a and b have different signs, a < b iff a is negative + let a_num = &preallocated_preimg[0]; + let b_num = &preallocated_preimg[1]; + let a_is_negative = allocate_is_negative(cs.namespace(|| "a_is_negative"), a_num)?; + let b_is_negative = allocate_is_negative(cs.namespace(|| "b_is_negative"), b_num)?; + // (same_sign && diff_is_neg) || (!same_sign && a_is_neg) + let same_sign = + Boolean::xor(cs.namespace(|| "same_sign"), &a_is_negative, &b_is_negative)? + .not(); + let diff = sub(cs.namespace(|| "diff"), a_num, b_num)?; + let diff_is_negative = + allocate_is_negative(cs.namespace(|| "diff_is_negative"), &diff)?; + let and1 = and(&mut cs.namespace(|| "and1"), &same_sign, &diff_is_negative)?; + let and2 = and( + &mut cs.namespace(|| "and2"), + &same_sign.not(), + &a_is_negative, + )?; + let lt = or(&mut cs.namespace(|| "or"), &and1, &and2)?; + AllocatedVal::Boolean(lt) + } + } + }; + Ok(preallocated_img) } -#[inline] -fn allocate_const>( +/// Allocates unconstrained slots +fn allocate_slots>( cs: &mut CS, - namespace: &str, - value: F, -) -> Result> { - allocate_constant(&mut cs.namespace(|| namespace), value) - .with_context(|| format!("allocation for '{namespace}' failed")) + preimg_data: &[Option>], + slot_type: SlotType, + num_slots: usize, + store: &Store, +) -> Result>, AllocatedVal)>> { + assert!( + preimg_data.len() == num_slots, + "collected preimages not equal to the number of available slots" + ); + + let mut preallocations = Vec::with_capacity(num_slots); + + // We must perform the allocations for the slots containing data collected + // by the interpreter. The `None` cases must be filled with dummy values + for (slot_idx, maybe_preimg_data) in preimg_data.iter().enumerate() { + if let Some(preimg_data) = maybe_preimg_data { + let slot = Slot { + idx: slot_idx, + typ: slot_type, + }; + + // Allocate the preimage because the image depends on it + let mut preallocated_preimg = Vec::with_capacity(slot_type.preimg_size()); + + match preimg_data { + PreimageData::PtrVec(ptr_vec) => { + let mut component_idx = 0; + for ptr in ptr_vec { + let z_ptr = store.hash_ptr(ptr)?; + + // allocate pointer tag + preallocated_preimg.push(AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component {component_idx} slot {slot}")), + || z_ptr.tag_field(), + )); + + component_idx += 1; + + // allocate pointer hash + preallocated_preimg.push(AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component {component_idx} slot {slot}")), + || *z_ptr.value(), + )); + + component_idx += 1; + } + } + PreimageData::FPtr(f, ptr) => { + let z_ptr = store.hash_ptr(ptr)?; + // allocate first component + preallocated_preimg.push(AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component 0 slot {slot}")), + || *f, + )); + // allocate second component + preallocated_preimg.push(AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component 1 slot {slot}")), + || z_ptr.tag_field(), + )); + // allocate third component + preallocated_preimg.push(AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component 2 slot {slot}")), + || *z_ptr.value(), + )); + } + PreimageData::FPair(a, b) => { + // allocate first component + preallocated_preimg.push(AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component 0 slot {slot}")), + || *a, + )); + + // allocate second component + preallocated_preimg.push(AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component 1 slot {slot}")), + || *b, + )); + } + } + + // Allocate the image by calling the arithmetic function according + // to the slot type + let preallocated_img = + allocate_img_for_slot(cs, &slot, preallocated_preimg.clone(), store)?; + + preallocations.push((preallocated_preimg, preallocated_img)); + } else { + let slot = Slot { + idx: slot_idx, + typ: slot_type, + }; + let preallocated_preimg: Vec<_> = (0..slot_type.preimg_size()) + .map(|component_idx| { + AllocatedNum::alloc_infallible( + cs.namespace(|| format!("component {component_idx} slot {slot}")), + || F::ZERO, + ) + }) + .collect(); + + let preallocated_img = + allocate_img_for_slot(cs, &slot, preallocated_preimg.clone(), store)?; + + preallocations.push((preallocated_preimg, preallocated_img)); + } + } + + Ok(preallocations) } -impl GlobalAllocator { - /// Checks if the allocation for a numeric variable has already been cached. - /// If so, return the cached allocation variable. Allocate as a constant, - /// cache and return otherwise. - pub(crate) fn get_or_alloc_const>( - &mut self, +impl Block { + fn alloc_globals>( + &self, cs: &mut CS, - f: F, - ) -> Result> { - let wrap = FWrap(f); - match self.0.get(&wrap) { - Some(allocated_num) => Ok(allocated_num.to_owned()), - None => { - let allocated_num = - allocate_const(cs, &format!("allocate constant {}", f.hex_digits()), f)?; - self.0.insert(wrap, allocated_num.clone()); - Ok(allocated_num) + store: &Store, + g: &mut GlobalAllocator, + ) -> Result<(), SynthesisError> { + for op in &self.ops { + match op { + Op::Call(_, func, _) => func.body.alloc_globals(cs, store, g)?, + Op::Cons2(_, tag, _) + | Op::Cons3(_, tag, _) + | Op::Cons4(_, tag, _) + | Op::Cast(_, tag, _) => { + g.new_const(cs, tag.to_field()); + } + Op::Lit(_, lit) => { + let lit_ptr = lit.to_ptr_cached(store); + let lit_z_ptr = store.hash_ptr(&lit_ptr).unwrap(); + g.new_const(cs, lit_z_ptr.tag_field()); + g.new_const(cs, *lit_z_ptr.value()); + } + Op::Null(_, tag) => { + use crate::tag::ContTag::{Dummy, Error, Outermost, Terminal}; + g.new_const(cs, tag.to_field()); + match tag { + Tag::Cont(Outermost | Error | Dummy | Terminal) => { + // temporary shim for compatibility with Lurk Alpha + g.new_const(cs, store.poseidon_cache.hash8(&[F::ZERO; 8])); + } + _ => { + g.new_const(cs, F::ZERO); + } + } + } + Op::Not(..) + | Op::And(..) + | Op::Or(..) + | Op::Add(..) + | Op::Sub(..) + | Op::Mul(..) + | Op::Lt(..) + | Op::Trunc(..) + | Op::DivRem64(..) => { + g.new_const(cs, Tag::Expr(Num).to_field()); + } + Op::Div(..) => { + g.new_const(cs, Tag::Expr(Num).to_field()); + g.new_const(cs, F::ONE); + } + Op::Hide(..) | Op::Open(..) => { + g.new_const(cs, Tag::Expr(Num).to_field()); + g.new_const(cs, Tag::Expr(Comm).to_field()); + } + _ => (), + } + } + match &self.ctrl { + Ctrl::If(.., a, b) => { + a.alloc_globals(cs, store, g)?; + b.alloc_globals(cs, store, g)?; + } + Ctrl::MatchTag(_, cases, def) => { + for block in cases.values() { + block.alloc_globals(cs, store, g)?; + } + if let Some(def) = def { + def.alloc_globals(cs, store, g)?; + } + } + Ctrl::MatchSymbol(_, cases, def) => { + g.new_const(cs, Tag::Expr(Sym).to_field()); + for block in cases.values() { + block.alloc_globals(cs, store, g)?; + } + if let Some(def) = def { + def.alloc_globals(cs, store, g)?; + } } + Ctrl::Return(..) => (), } + Ok(()) } } -type BoundAllocations = VarMap>; - impl Func { - /// Allocates an unconstrained pointer - fn allocate_ptr>( + /// Allocates an unconstrained pointer for each output of the frame + fn allocate_output>( + &self, cs: &mut CS, - z_ptr: &ZPtr, - var: &Var, - bound_allocations: &mut BoundAllocations, - ) -> Result> { - let allocated_tag = - allocate_num(cs, &format!("allocate {var}'s tag"), z_ptr.tag.to_field())?; - let allocated_hash = allocate_num(cs, &format!("allocate {var}'s hash"), z_ptr.hash)?; - let allocated_ptr = AllocatedPtr::from_parts(allocated_tag, allocated_hash); - bound_allocations.insert(var.clone(), allocated_ptr.clone()); - Ok(allocated_ptr) + store: &Store, + frame: &Frame, + ) -> Result>> { + assert_eq!(self.output_size, frame.output.len()); + let mut output = Vec::with_capacity(frame.output.len()); + for (i, ptr) in frame.output.iter().enumerate() { + let zptr = store.hash_ptr(ptr)?; + output.push(AllocatedPtr::alloc( + &mut cs.namespace(|| format!("var: output[{}]", i)), + || Ok(zptr), + )?); + } + Ok(output) } /// Allocates an unconstrained pointer for each input of the frame fn allocate_input>( &self, cs: &mut CS, - store: &mut Store, + store: &Store, frame: &Frame, bound_allocations: &mut BoundAllocations, ) -> Result<()> { for (i, ptr) in frame.input.iter().enumerate() { let param = &self.input_params[i]; - Self::allocate_ptr(cs, &store.hash_ptr(ptr)?, param, bound_allocations)?; + let zptr = store.hash_ptr(ptr)?; + let ptr = + AllocatedPtr::alloc(&mut cs.namespace(|| format!("var: {param}")), || Ok(zptr))?; + bound_allocations.insert_ptr(param.clone(), ptr); } Ok(()) } - /// Allocates an unconstrained pointer for each output of the frame - fn allocate_output>( - cs: &mut CS, - store: &mut Store, - frame: &Frame, - bound_allocations: &mut BoundAllocations, - ) -> Result>> { - frame - .output - .iter() - .enumerate() - .map(|(i, ptr)| { - Self::allocate_ptr( - cs, - &store.hash_ptr(ptr)?, - &Var(format!("output[{}]", i).into()), - bound_allocations, - ) - }) - .collect::>() - } - - #[inline] - fn allocate_preimg_component_for_slot>( - cs: &mut CS, - slot: &Slot, - component_idx: usize, - value: F, - ) -> Result> { - allocate_num( - cs, - &format!("component {component_idx} for slot {slot}"), - value, - ) - } - - fn allocate_img_for_slot>( - cs: &mut CS, - slot: &Slot, - preallocated_preimg: Vec>, - store: &mut Store, - ) -> Result> { - let cs = &mut cs.namespace(|| format!("poseidon for slot {slot}")); - let preallocated_img = { - match slot.typ { - SlotType::Hash2 => { - hash_poseidon(cs, preallocated_preimg, store.poseidon_cache.constants.c4())? - } - SlotType::Hash3 => { - hash_poseidon(cs, preallocated_preimg, store.poseidon_cache.constants.c6())? - } - SlotType::Hash4 => { - hash_poseidon(cs, preallocated_preimg, store.poseidon_cache.constants.c8())? - } - } - }; - Ok(preallocated_img) - } - - /// Allocates unconstrained slots - fn allocate_slots>( + pub fn alloc_globals>( + &self, cs: &mut CS, - preimgs: &[Option>>], - slot_type: SlotType, - num_slots: usize, - store: &mut Store, - ) -> Result>, AllocatedNum)>> { - assert!( - preimgs.len() == num_slots, - "collected preimages not equal to the number of available slots" - ); - - let mut preallocations = Vec::with_capacity(num_slots); - - // We must perform the allocations for the slots containing data collected - // by the interpreter. The `None` cases must be filled with dummy values - for (slot_idx, maybe_preimg) in preimgs.iter().enumerate() { - if let Some(preimg) = maybe_preimg { - let slot = Slot { - idx: slot_idx, - typ: slot_type, - }; - // Allocate the preimage because the image depends on it - let mut preallocated_preimg = Vec::with_capacity(2 * preimg.len()); - - let mut component_idx = 0; - for ptr in preimg { - let z_ptr = store.hash_ptr(ptr)?; - - // allocate pointer tag - preallocated_preimg.push(Self::allocate_preimg_component_for_slot( - cs, - &slot, - component_idx, - z_ptr.tag.to_field(), - )?); - - component_idx += 1; - - // allocate pointer hash - preallocated_preimg.push(Self::allocate_preimg_component_for_slot( - cs, - &slot, - component_idx, - z_ptr.hash, - )?); - - component_idx += 1; - } - - // Allocate the image by calling the arithmetic function according - // to the slot type - let preallocated_img = - Self::allocate_img_for_slot(cs, &slot, preallocated_preimg.clone(), store)?; - - preallocations.push((preallocated_preimg, preallocated_img)); - } else { - let slot = Slot { - idx: slot_idx, - typ: slot_type, - }; - let preallocated_preimg: Vec<_> = (0..slot_type.preimg_size()) - .map(|component_idx| { - Self::allocate_preimg_component_for_slot(cs, &slot, component_idx, F::ZERO) - }) - .collect::>()?; - - let preallocated_img = - Self::allocate_img_for_slot(cs, &slot, preallocated_preimg.clone(), store)?; - - preallocations.push((preallocated_preimg, preallocated_img)); - } - } - - Ok(preallocations) + store: &Store, + ) -> Result, SynthesisError> { + let mut g = GlobalAllocator::default(); + self.body.alloc_globals(cs, store, &mut g)?; + Ok(g) } /// Create R1CS constraints for a LEM function given an evaluation frame. This @@ -277,55 +431,69 @@ impl Func { /// each slot and then, as we traverse the function, we add constraints to make /// sure that the witness satisfies the arithmetic equations for the /// corresponding slots. - pub fn synthesize>( + pub fn synthesize_frame>( &self, cs: &mut CS, - store: &mut Store, + store: &Store, frame: &Frame, - ) -> Result<()> { - let mut global_allocator = GlobalAllocator::default(); - let mut bound_allocations = BoundAllocations::new(); - - // Inputs are constrained by their usage inside the function body - self.allocate_input(cs, store, frame, &mut bound_allocations)?; + global_allocator: &GlobalAllocator, + bound_allocations: &mut BoundAllocations, + ) -> Result>> { // Outputs are constrained by the return statement. All functions return - let preallocated_outputs = Func::allocate_output(cs, store, frame, &mut bound_allocations)?; + let preallocated_outputs = self.allocate_output(cs, store, frame)?; // Slots are constrained by their usage inside the function body. The ones // not used in throughout the concrete path are effectively unconstrained, // that's why they are filled with dummies - let preallocated_hash2_slots = Func::allocate_slots( + let preallocated_hash4_slots = allocate_slots( cs, - &frame.preimages.hash2_ptrs, - SlotType::Hash2, - self.slot.hash2, + &frame.preimages.hash4, + SlotType::Hash4, + self.slot.hash4, store, )?; - let preallocated_hash3_slots = Func::allocate_slots( + let preallocated_hash6_slots = allocate_slots( cs, - &frame.preimages.hash3_ptrs, - SlotType::Hash3, - self.slot.hash3, + &frame.preimages.hash6, + SlotType::Hash6, + self.slot.hash6, store, )?; - let preallocated_hash4_slots = Func::allocate_slots( + let preallocated_hash8_slots = allocate_slots( cs, - &frame.preimages.hash4_ptrs, - SlotType::Hash4, - self.slot.hash4, + &frame.preimages.hash8, + SlotType::Hash8, + self.slot.hash8, + store, + )?; + + let preallocated_commitment_slots = allocate_slots( + cs, + &frame.preimages.commitment, + SlotType::Commitment, + self.slot.commitment, + store, + )?; + + let preallocated_less_than_slots = allocate_slots( + cs, + &frame.preimages.less_than, + SlotType::LessThan, + self.slot.less_than, store, )?; struct Globals<'a, F: LurkField> { - store: &'a mut Store, - global_allocator: &'a mut GlobalAllocator, - preallocated_hash2_slots: Vec<(Vec>, AllocatedNum)>, - preallocated_hash3_slots: Vec<(Vec>, AllocatedNum)>, - preallocated_hash4_slots: Vec<(Vec>, AllocatedNum)>, + store: &'a Store, + global_allocator: &'a GlobalAllocator, + preallocated_hash4_slots: Vec<(Vec>, AllocatedVal)>, + preallocated_hash6_slots: Vec<(Vec>, AllocatedVal)>, + preallocated_hash8_slots: Vec<(Vec>, AllocatedVal)>, + preallocated_commitment_slots: Vec<(Vec>, AllocatedVal)>, + preallocated_less_than_slots: Vec<(Vec>, AllocatedVal)>, call_outputs: VecDeque>>, - call_count: usize, } fn recurse>( @@ -337,23 +505,26 @@ impl Func { preallocated_outputs: &Vec>, g: &mut Globals<'_, F>, ) -> Result<()> { - for op in &block.ops { - macro_rules! hash_helper { + for (op_idx, op) in block.ops.iter().enumerate() { + let mut cs = cs.namespace(|| format!("op {op_idx}")); + + macro_rules! cons_helper { ( $img: expr, $tag: expr, $preimg: expr, $slot: expr ) => { // Retrieve allocated preimage - let allocated_preimg = bound_allocations.get_many($preimg)?; + let allocated_preimg = bound_allocations.get_many_ptr($preimg)?; // Retrieve the preallocated preimage and image for this slot let (preallocated_preimg, preallocated_img_hash) = match $slot { - SlotType::Hash2 => { - &g.preallocated_hash2_slots[next_slot.consume_hash2()] - } - SlotType::Hash3 => { - &g.preallocated_hash3_slots[next_slot.consume_hash3()] - } SlotType::Hash4 => { &g.preallocated_hash4_slots[next_slot.consume_hash4()] } + SlotType::Hash6 => { + &g.preallocated_hash6_slots[next_slot.consume_hash6()] + } + SlotType::Hash8 => { + &g.preallocated_hash8_slots[next_slot.consume_hash8()] + } + _ => panic!("Invalid slot type for cons_helper macro"), }; // For each component of the preimage, add implication constraints @@ -362,65 +533,57 @@ impl Func { let var = &$preimg[i]; let ptr_idx = 2 * i; implies_equal( - &mut cs.namespace(|| { - format!( - "implies equal for {var}'s tag (LEMOP {:?}, pos {i})", - &op - ) - }), + &mut cs.namespace(|| format!("implies equal {var}.tag pos {i}")), not_dummy, allocated_ptr.tag(), &preallocated_preimg[ptr_idx], // tag index - )?; + ); implies_equal( - &mut cs.namespace(|| { - format!( - "implies equal for {var}'s hash (LEMOP {:?}, pos {i})", - &op - ) - }), + &mut cs.namespace(|| format!("implies equal {var}.hash pos {i}")), not_dummy, allocated_ptr.hash(), &preallocated_preimg[ptr_idx + 1], // hash index - )?; + ); } // Allocate the image tag if it hasn't been allocated before, // create the full image pointer and add it to bound allocations - let img_tag = g.global_allocator.get_or_alloc_const(cs, $tag.to_field())?; - let img_hash = preallocated_img_hash.clone(); - let img_ptr = AllocatedPtr::from_parts(img_tag, img_hash); - bound_allocations.insert($img, img_ptr); + let img_tag = g + .global_allocator + .get_allocated_const_cloned($tag.to_field())?; + let AllocatedVal::Number(img_hash) = preallocated_img_hash else { bail!("Expected number")}; + let img_ptr = AllocatedPtr::from_parts(img_tag, img_hash.clone()); + bound_allocations.insert_ptr($img, img_ptr); }; } - macro_rules! unhash_helper { + macro_rules! decons_helper { ( $preimg: expr, $img: expr, $slot: expr ) => { // Retrieve allocated image - let allocated_img = bound_allocations.get($img)?; + let allocated_img = bound_allocations.get_ptr($img)?; // Retrieve the preallocated preimage and image for this slot - let (preallocated_preimg, preallocated_img) = match $slot { - SlotType::Hash2 => { - &g.preallocated_hash2_slots[next_slot.consume_hash2()] - } - SlotType::Hash3 => { - &g.preallocated_hash3_slots[next_slot.consume_hash3()] - } + let (preallocated_preimg, preallocated_img_hash) = match $slot { SlotType::Hash4 => { &g.preallocated_hash4_slots[next_slot.consume_hash4()] } + SlotType::Hash6 => { + &g.preallocated_hash6_slots[next_slot.consume_hash6()] + } + SlotType::Hash8 => { + &g.preallocated_hash8_slots[next_slot.consume_hash8()] + } + _ => panic!("Invalid slot type for decons_helper macro"), }; // Add the implication constraint for the image + let AllocatedVal::Number(img_hash) = preallocated_img_hash else { bail!("Expected number")}; implies_equal( - &mut cs.namespace(|| { - format!("implies equal for {}'s hash (LEMOP {:?})", $img, &op) - }), + &mut cs.namespace(|| format!("implies equal {}.hash", $img)), not_dummy, allocated_img.hash(), - &preallocated_img, - )?; + img_hash, + ); // Retrieve preimage hashes and tags create the full preimage pointers // and add them to bound allocations @@ -429,7 +592,7 @@ impl Func { let preimg_hash = &preallocated_preimg[2 * i + 1]; let preimg_ptr = AllocatedPtr::from_parts(preimg_tag.clone(), preimg_hash.clone()); - bound_allocations.insert($preimg[i].clone(), preimg_ptr); + bound_allocations.insert_ptr($preimg[i].clone(), preimg_ptr); } }; } @@ -442,30 +605,36 @@ impl Func { // Note that, because there's currently no way of deferring giving // a value to the allocated nums to be filled later, we must either // add the results of the call to the witness, or recompute them. - let output_vals = if not_dummy.get_value().unwrap() { - g.call_outputs.pop_front().unwrap() + let dummy = Ptr::null(Tag::Expr(Nil)); + let output_vals = if let Some(true) = not_dummy.get_value() { + g.call_outputs + .pop_front() + .unwrap_or_else(|| (0..out.len()).map(|_| dummy).collect()) } else { - let dummy = Ptr::Leaf(Tag::Expr(Nil), F::ZERO); (0..out.len()).map(|_| dummy).collect() }; assert_eq!(output_vals.len(), out.len()); let mut output_ptrs = Vec::with_capacity(out.len()); for (ptr, var) in output_vals.iter().zip(out.iter()) { - let zptr = &g.store.hash_ptr(ptr)?; - output_ptrs.push(Func::allocate_ptr(cs, zptr, var, bound_allocations)?); + let zptr = g.store.hash_ptr(ptr)?; + let ptr = AllocatedPtr::alloc( + &mut cs.namespace(|| format!("var: {var}")), + || Ok(zptr), + )?; + bound_allocations.insert_ptr(var.clone(), ptr.clone()); + output_ptrs.push(ptr); } // Get the pointers for the input, i.e. the arguments - let args = bound_allocations.get_many_cloned(inp)?; + let args = bound_allocations.get_many_ptr(inp)?; // These are the input parameters (formal variables) let param_list = func.input_params.iter(); // Now we bind the `Func`'s input parameters to the arguments in the call. param_list.zip(args.into_iter()).for_each(|(param, arg)| { - bound_allocations.insert(param.clone(), arg); + bound_allocations.insert_ptr(param.clone(), arg); }); // Finally, we synthesize the circuit for the function body - g.call_count += 1; recurse( - &mut cs.namespace(|| format!("Call {}", g.call_count)), + &mut cs.namespace(|| "call"), &func.body, not_dummy, next_slot, @@ -474,141 +643,426 @@ impl Func { g, )?; } - Op::Hash2(img, tag, preimg) => { - hash_helper!(img.clone(), tag, preimg, SlotType::Hash2); + Op::Cons2(img, tag, preimg) => { + cons_helper!(img.clone(), tag, preimg, SlotType::Hash4); } - Op::Hash3(img, tag, preimg) => { - hash_helper!(img.clone(), tag, preimg, SlotType::Hash3); + Op::Cons3(img, tag, preimg) => { + cons_helper!(img.clone(), tag, preimg, SlotType::Hash6); } - Op::Hash4(img, tag, preimg) => { - hash_helper!(img.clone(), tag, preimg, SlotType::Hash4); + Op::Cons4(img, tag, preimg) => { + cons_helper!(img.clone(), tag, preimg, SlotType::Hash8); } - Op::Unhash2(preimg, img) => { - unhash_helper!(preimg, img, SlotType::Hash2); + Op::Decons2(preimg, img) => { + decons_helper!(preimg, img, SlotType::Hash4); } - Op::Unhash3(preimg, img) => { - unhash_helper!(preimg, img, SlotType::Hash3); + Op::Decons3(preimg, img) => { + decons_helper!(preimg, img, SlotType::Hash6); } - Op::Unhash4(preimg, img) => { - unhash_helper!(preimg, img, SlotType::Hash4); + Op::Decons4(preimg, img) => { + decons_helper!(preimg, img, SlotType::Hash8); } Op::Null(tgt, tag) => { - let tag = g.global_allocator.get_or_alloc_const(cs, tag.to_field())?; - let zero = g.global_allocator.get_or_alloc_const(cs, F::ZERO)?; - let allocated_ptr = AllocatedPtr::from_parts(tag, zero); - bound_allocations.insert(tgt.clone(), allocated_ptr); + use crate::tag::ContTag::{Dummy, Error, Outermost, Terminal}; + let tag_num = g + .global_allocator + .get_allocated_const_cloned(tag.to_field())?; + let value = match tag { + Tag::Cont(Outermost | Error | Dummy | Terminal) => { + // temporary shim for compatibility with Lurk Alpha + g.global_allocator.get_allocated_const_cloned( + g.store.poseidon_cache.hash8(&[F::ZERO; 8]), + )? + } + _ => g.global_allocator.get_allocated_const_cloned(F::ZERO)?, + }; + let allocated_ptr = AllocatedPtr::from_parts(tag_num, value); + bound_allocations.insert_ptr(tgt.clone(), allocated_ptr); } Op::Lit(tgt, lit) => { - let lit_ptr = lit.to_ptr(g.store); + let lit_ptr = lit.to_ptr_cached(g.store); let lit_tag = lit_ptr.tag().to_field(); - let lit_hash = g.store.hash_ptr(&lit_ptr)?.hash; - let allocated_tag = g.global_allocator.get_or_alloc_const(cs, lit_tag)?; - let allocated_hash = g.global_allocator.get_or_alloc_const(cs, lit_hash)?; + let allocated_tag = + g.global_allocator.get_allocated_const_cloned(lit_tag)?; + let allocated_hash = g + .global_allocator + .get_allocated_const_cloned(*g.store.hash_ptr(&lit_ptr)?.value())?; let allocated_ptr = AllocatedPtr::from_parts(allocated_tag, allocated_hash); - bound_allocations.insert(tgt.clone(), allocated_ptr); + bound_allocations.insert_ptr(tgt.clone(), allocated_ptr); } Op::Cast(tgt, tag, src) => { - let src = bound_allocations.get(src)?; - let tag = g.global_allocator.get_or_alloc_const(cs, tag.to_field())?; + let src = bound_allocations.get_ptr(src)?; + let tag = g + .global_allocator + .get_allocated_const_cloned(tag.to_field())?; let allocated_ptr = AllocatedPtr::from_parts(tag, src.hash().clone()); - bound_allocations.insert(tgt.clone(), allocated_ptr); + bound_allocations.insert_ptr(tgt.clone(), allocated_ptr); + } + Op::EqTag(tgt, a, b) => { + let a = bound_allocations.get_ptr(a)?; + let b = bound_allocations.get_ptr(b)?; + let a_num = a.tag(); + let b_num = b.tag(); + let eq = alloc_equal(cs.namespace(|| "equal_tag"), a_num, b_num)?; + bound_allocations.insert_bool(tgt.clone(), eq); + } + Op::EqVal(tgt, a, b) => { + let a = bound_allocations.get_ptr(a)?; + let b = bound_allocations.get_ptr(b)?; + let a_num = a.hash(); + let b_num = b.hash(); + let eq = alloc_equal(cs.namespace(|| "equal_val"), a_num, b_num)?; + bound_allocations.insert_bool(tgt.clone(), eq); + } + Op::Not(tgt, a) => { + let a = bound_allocations.get_bool(a)?; + bound_allocations.insert_bool(tgt.clone(), a.not()); + } + Op::And(tgt, a, b) => { + let a = bound_allocations.get_bool(a)?; + let b = bound_allocations.get_bool(b)?; + let c = and(&mut cs.namespace(|| "and"), a, b)?; + bound_allocations.insert_bool(tgt.clone(), c); + } + Op::Or(tgt, a, b) => { + let a = bound_allocations.get_bool(a)?; + let b = bound_allocations.get_bool(b)?; + let c = or(cs.namespace(|| "or"), a, b)?; + bound_allocations.insert_bool(tgt.clone(), c); } Op::Add(tgt, a, b) => { - let a = bound_allocations.get(a)?; - let b = bound_allocations.get(b)?; - // TODO check that the tags are correct + let a = bound_allocations.get_ptr(a)?; + let b = bound_allocations.get_ptr(b)?; let a_num = a.hash(); let b_num = b.hash(); - let c_num = add(&mut cs.namespace(|| "add"), a_num, b_num)?; + let c_num = add(cs.namespace(|| "add"), a_num, b_num)?; let tag = g .global_allocator - .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + .get_allocated_const_cloned(Tag::Expr(Num).to_field())?; let c = AllocatedPtr::from_parts(tag, c_num); - bound_allocations.insert(tgt.clone(), c); + bound_allocations.insert_ptr(tgt.clone(), c); } Op::Sub(tgt, a, b) => { - let a = bound_allocations.get(a)?; - let b = bound_allocations.get(b)?; - // TODO check that the tags are correct + let a = bound_allocations.get_ptr(a)?; + let b = bound_allocations.get_ptr(b)?; let a_num = a.hash(); let b_num = b.hash(); - let c_num = sub(&mut cs.namespace(|| "sub"), a_num, b_num)?; + let c_num = sub(cs.namespace(|| "sub"), a_num, b_num)?; let tag = g .global_allocator - .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + .get_allocated_const_cloned(Tag::Expr(Num).to_field())?; let c = AllocatedPtr::from_parts(tag, c_num); - bound_allocations.insert(tgt.clone(), c); + bound_allocations.insert_ptr(tgt.clone(), c); } Op::Mul(tgt, a, b) => { - let a = bound_allocations.get(a)?; - let b = bound_allocations.get(b)?; - // TODO check that the tags are correct + let a = bound_allocations.get_ptr(a)?; + let b = bound_allocations.get_ptr(b)?; let a_num = a.hash(); let b_num = b.hash(); - let c_num = mul(&mut cs.namespace(|| "mul"), a_num, b_num)?; + let c_num = mul(cs.namespace(|| "mul"), a_num, b_num)?; let tag = g .global_allocator - .get_or_alloc_const(cs, Tag::Expr(Num).to_field())?; + .get_allocated_const_cloned(Tag::Expr(Num).to_field())?; let c = AllocatedPtr::from_parts(tag, c_num); - bound_allocations.insert(tgt.clone(), c); + bound_allocations.insert_ptr(tgt.clone(), c); } - Op::Div(_tgt, _a, _b) => { - // TODO + Op::Div(tgt, a, b) => { + let a = bound_allocations.get_ptr(a)?; + let b = bound_allocations.get_ptr(b)?; + let a_num = a.hash(); + let b_num = b.hash(); + + let b_is_zero = &alloc_is_zero(cs.namespace(|| "b_is_zero"), b_num)?; + let one = g.global_allocator.get_allocated_const(F::ONE)?; + + let divisor = pick( + cs.namespace(|| "maybe-dummy divisor"), + b_is_zero, + one, + b_num, + )?; + + let quotient = div(cs.namespace(|| "quotient"), a_num, &divisor)?; + + let tag = g + .global_allocator + .get_allocated_const_cloned(Tag::Expr(Num).to_field())?; + let c = AllocatedPtr::from_parts(tag, quotient); + bound_allocations.insert_ptr(tgt.clone(), c); + } + Op::Lt(tgt, a, b) => { + let a = bound_allocations.get_ptr(a)?; + let b = bound_allocations.get_ptr(b)?; + let (preallocated_preimg, lt) = + &g.preallocated_less_than_slots[next_slot.consume_less_than()]; + for (i, n) in [a.hash(), b.hash()].into_iter().enumerate() { + implies_equal( + &mut cs.namespace(|| format!("implies equal component {i}")), + not_dummy, + n, + &preallocated_preimg[i], + ); + } + let AllocatedVal::Boolean(lt) = lt else { panic!("Expected boolean") }; + bound_allocations.insert_bool(tgt.clone(), lt.clone()); + } + Op::Trunc(tgt, a, n) => { + assert!(*n <= 64); + let a = bound_allocations.get_ptr(a)?; + let mut trunc_bits = + a.hash().to_bits_le_strict(cs.namespace(|| "to_bits_le"))?; + trunc_bits.truncate(*n as usize); + let trunc = AllocatedNum::alloc(cs.namespace(|| "trunc"), || { + let b = if *n < 64 { (1 << *n) - 1 } else { u64::MAX }; + a.hash() + .get_value() + .map(|a| F::from_u64(a.to_u64_unchecked() & b)) + .ok_or(SynthesisError::AssignmentMissing) + })?; + enforce_pack(cs.namespace(|| "enforce_trunc"), &trunc_bits, &trunc); + let tag = g + .global_allocator + .get_allocated_const_cloned(Tag::Expr(Num).to_field())?; + let c = AllocatedPtr::from_parts(tag, trunc); + bound_allocations.insert_ptr(tgt.clone(), c); + } + Op::DivRem64(tgt, a, b) => { + let a = bound_allocations.get_ptr(a)?.hash(); + let b = bound_allocations.get_ptr(b)?.hash(); + let div_rem = a.get_value().and_then(|a| { + b.get_value().map(|b| { + if not_dummy.get_value().unwrap() { + let a = a.to_u64_unchecked(); + let b = b.to_u64_unchecked(); + (F::from_u64(a / b), F::from_u64(a % b)) + } else { + (F::ZERO, a) + } + }) + }); + let div = + AllocatedNum::alloc(cs.namespace(|| "div"), || Ok(div_rem.unwrap().0))?; + let rem = + AllocatedNum::alloc(cs.namespace(|| "rem"), || Ok(div_rem.unwrap().1))?; + + let diff = sub(cs.namespace(|| "diff for slot {slot}"), b, &rem)?; + implies_u64(cs.namespace(|| "div_u64"), not_dummy, &div)?; + implies_u64(cs.namespace(|| "rem_u64"), not_dummy, &rem)?; + implies_u64(cs.namespace(|| "diff_u64"), not_dummy, &diff)?; + + enforce_product_and_sum( + &mut cs, + || "enforce a = b * div + rem", + b, + &div, + &rem, + a, + ); + let tag = g + .global_allocator + .get_allocated_const_cloned(Tag::Expr(Num).to_field())?; + let div_ptr = AllocatedPtr::from_parts(tag.clone(), div); + let rem_ptr = AllocatedPtr::from_parts(tag, rem); + bound_allocations.insert_ptr(tgt[0].clone(), div_ptr); + bound_allocations.insert_ptr(tgt[1].clone(), rem_ptr); } Op::Emit(_) => (), - Op::Hide(tgt, _sec, _pay) => { - // TODO - let allocated_ptr = AllocatedPtr::from_parts( - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, + Op::Hide(tgt, sec, pay) => { + let sec = bound_allocations.get_ptr(sec)?; + let pay = bound_allocations.get_ptr(pay)?; + let sec_tag = g + .global_allocator + .get_allocated_const(Tag::Expr(Num).to_field())?; + let (preallocated_preimg, hash) = + &g.preallocated_commitment_slots[next_slot.consume_commitment()]; + let AllocatedVal::Number(hash) = hash else { panic!("Excepted number") }; + implies_equal( + &mut cs.namespace(|| "implies equal secret.tag"), + not_dummy, + sec.tag(), + sec_tag, + ); + implies_equal( + &mut cs.namespace(|| "implies equal secret.hash"), + not_dummy, + sec.hash(), + &preallocated_preimg[0], + ); + implies_equal( + &mut cs.namespace(|| "implies equal payload.tag"), + not_dummy, + pay.tag(), + &preallocated_preimg[1], + ); + implies_equal( + &mut cs.namespace(|| "implies equal payload.hash"), + not_dummy, + pay.hash(), + &preallocated_preimg[2], ); - bound_allocations.insert(tgt.clone(), allocated_ptr); + let tag = g + .global_allocator + .get_allocated_const_cloned(Tag::Expr(Comm).to_field())?; + let allocated_ptr = AllocatedPtr::from_parts(tag, hash.clone()); + bound_allocations.insert_ptr(tgt.clone(), allocated_ptr); } - Op::Open(pay, sec, _comm_or_num) => { - // TODO - let allocated_ptr = AllocatedPtr::from_parts( - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, - g.global_allocator.get_or_alloc_const(cs, F::ZERO)?, + Op::Open(sec, pay, comm) => { + let comm = bound_allocations.get_ptr(comm)?; + let (preallocated_preimg, com_hash) = + &g.preallocated_commitment_slots[next_slot.consume_commitment()]; + let comm_tag = g + .global_allocator + .get_allocated_const(Tag::Expr(Comm).to_field())?; + let AllocatedVal::Number(com_hash) = com_hash else { panic!("Excepted number") }; + implies_equal( + &mut cs.namespace(|| "implies equal comm.tag"), + not_dummy, + comm.tag(), + comm_tag, ); - bound_allocations.insert(pay.clone(), allocated_ptr.clone()); - bound_allocations.insert(sec.clone(), allocated_ptr); + implies_equal( + &mut cs.namespace(|| "implies equal comm.hash "), + not_dummy, + comm.hash(), + com_hash, + ); + let sec_tag = g + .global_allocator + .get_allocated_const_cloned(Tag::Expr(Num).to_field())?; + let allocated_sec_ptr = + AllocatedPtr::from_parts(sec_tag, preallocated_preimg[0].clone()); + let allocated_pay_ptr = AllocatedPtr::from_parts( + preallocated_preimg[1].clone(), + preallocated_preimg[2].clone(), + ); + bound_allocations.insert_ptr(sec.clone(), allocated_sec_ptr); + bound_allocations.insert_ptr(pay.clone(), allocated_pay_ptr); } } } + let mut synthesize_match = |matched: &AllocatedNum, + cases: &[(F, &Block)], + def: &Option>, + bound_allocations: &mut VarMap>, + g: &mut Globals<'_, F>| + -> Result> { + // * One `Boolean` for each case + // * Maybe one `Boolean` for the default case + let selector_size = cases.len() + usize::from(def.is_some()); + let mut selector = Vec::with_capacity(selector_size); + let mut branch_slots = Vec::with_capacity(cases.len()); + for (i, (f, block)) in cases.iter().enumerate() { + // For each case, we compute `not_dummy_and_has_match: Boolean` + // and accumulate them on a `selector` vector + let not_dummy_and_has_match_bool = + not_dummy.get_value().and_then(|not_dummy| { + matched + .get_value() + .map(|matched_f| not_dummy && &matched_f == f) + }); + let not_dummy_and_has_match = Boolean::Is(AllocatedBit::alloc( + cs.namespace(|| format!("{i}.allocated_bit")), + not_dummy_and_has_match_bool, + )?); + + // If `not_dummy_and_has_match` is true, then we enforce a match + implies_equal_const( + &mut cs.namespace(|| format!("{i}.implies_equal_const")), + ¬_dummy_and_has_match, + matched, + *f, + ); + + selector.push(not_dummy_and_has_match.clone()); + + let mut branch_slot = *next_slot; + recurse( + &mut cs.namespace(|| format!("{i}")), + block, + ¬_dummy_and_has_match, + &mut branch_slot, + bound_allocations, + preallocated_outputs, + g, + )?; + branch_slots.push(branch_slot); + } + + if let Some(def) = def { + // Compute `default: Boolean`, which tells whether the default case was chosen or not + let is_default_bool = selector.iter().fold(not_dummy.get_value(), |acc, b| { + // all the booleans in `selector` have to be false up to this point + // in order for the default case to be selected + acc.and_then(|acc| b.get_value().map(|b| acc && !b)) + }); + let is_default = Boolean::Is(AllocatedBit::alloc( + cs.namespace(|| "_.allocated_bit"), + is_default_bool, + )?); + + for (i, (f, _)) in cases.iter().enumerate() { + // if the default path was taken, then there can be no tag in `cases` + // that equals the tag of the pointer being matched on + implies_unequal_const( + &mut cs.namespace(|| format!("{i}.implies_unequal_const")), + &is_default, + matched, + *f, + )?; + } + + recurse( + &mut cs.namespace(|| "_"), + def, + &is_default, + next_slot, + bound_allocations, + preallocated_outputs, + g, + )?; + + // Pushing `is_default` to `selector` to enforce summation = 1 + selector.push(is_default); + } + + // Now we need to enforce that exactly one path was taken. We do that by enforcing + // that the sum of the previously collected `Boolean`s is one. But, of course, this + // is irrelevant if we're on a virtual path and thus we use an implication gadget. + enforce_selector_with_premise( + &mut cs.namespace(|| "enforce_selector_with_premise"), + not_dummy, + &selector, + ); + + Ok(branch_slots) + }; + match &block.ctrl { Ctrl::Return(return_vars) => { for (i, return_var) in return_vars.iter().enumerate() { - let allocated_ptr = bound_allocations.get(return_var)?; + let allocated_ptr = bound_allocations.get_ptr(return_var)?; - allocated_ptr - .implies_ptr_equal( - &mut cs.namespace(|| { - format!("implies_ptr_equal {return_var} (return_var {i})") - }), - not_dummy, - &preallocated_outputs[i], - ) - .with_context(|| "couldn't constrain `implies_ptr_equal`")?; + allocated_ptr.implies_ptr_equal( + &mut cs.namespace(|| format!("implies_ptr_equal {return_var} pos {i}")), + not_dummy, + &preallocated_outputs[i], + ); } Ok(()) } - Ctrl::IfEq(x, y, eq_block, else_block) => { - let x = bound_allocations.get(x)?.hash(); - let y = bound_allocations.get(y)?.hash(); - // TODO should we check whether the tags are equal too? - let eq = alloc_equal(&mut cs.namespace(|| "if_eq.alloc_equal"), x, y)?; - let not_eq = eq.not(); - // TODO is this the most efficient way of doing if statements? - let not_dummy_and_eq = and(&mut cs.namespace(|| "if_eq.and"), not_dummy, &eq)?; - let not_dummy_and_not_eq = - and(&mut cs.namespace(|| "if_eq.and.2"), not_dummy, ¬_eq)?; - + Ctrl::If(b, true_block, false_block) => { + let b = bound_allocations.get_bool(b)?; + let b_not_dummy = and(&mut cs.namespace(|| "b and not_dummy"), b, not_dummy)?; + let not_b_not_dummy = and( + &mut cs.namespace(|| "not_b and not_dummy"), + &b.not(), + not_dummy, + )?; let mut branch_slot = *next_slot; recurse( &mut cs.namespace(|| "if_eq.true"), - eq_block, - ¬_dummy_and_eq, + true_block, + &b_not_dummy, &mut branch_slot, bound_allocations, preallocated_outputs, @@ -616,8 +1070,8 @@ impl Func { )?; recurse( &mut cs.namespace(|| "if_eq.false"), - else_block, - ¬_dummy_and_not_eq, + false_block, + ¬_b_not_dummy, next_slot, bound_allocations, preallocated_outputs, @@ -627,164 +1081,55 @@ impl Func { Ok(()) } Ctrl::MatchTag(match_var, cases, def) => { - let allocated_match_tag = bound_allocations.get(match_var)?.tag().clone(); - let mut selector = Vec::with_capacity(cases.len() + 1); - let mut branch_slots = Vec::with_capacity(cases.len()); - for (tag, block) in cases { - let allocated_has_match = alloc_equal_const( - &mut cs.namespace(|| format!("{tag}.alloc_equal_const")), - &allocated_match_tag, - tag.to_field::(), - ) - .with_context(|| "couldn't allocate equal const")?; - - let not_dummy_and_has_match = and( - &mut cs.namespace(|| format!("{tag}.and")), - not_dummy, - &allocated_has_match, - ) - .with_context(|| "failed to constrain `and`")?; - - selector.push(allocated_has_match); - - let mut branch_slot = *next_slot; - recurse( - &mut cs.namespace(|| format!("{}", tag)), - block, - ¬_dummy_and_has_match, - &mut branch_slot, - bound_allocations, - preallocated_outputs, - g, - )?; - branch_slots.push(branch_slot); - } - - match def { - Some(def) => { - let default = selector.iter().all(|b| !b.get_value().unwrap()); - let allocated_has_match = Boolean::Is(AllocatedBit::alloc( - &mut cs.namespace(|| "_.allocated_bit"), - Some(default), - )?); - - let not_dummy_and_has_match = and( - &mut cs.namespace(|| "_.and"), - not_dummy, - &allocated_has_match, - ) - .with_context(|| "failed to constrain `and`")?; - - selector.push(allocated_has_match); - - recurse( - &mut cs.namespace(|| "_"), - def, - ¬_dummy_and_has_match, - next_slot, - bound_allocations, - preallocated_outputs, - g, - )?; - } - None => (), - } + let matched = bound_allocations.get_ptr(match_var)?.tag().clone(); + let cases_vec = cases + .iter() + .map(|(tag, block)| (tag.to_field::(), block)) + .collect::>(); + let branch_slots = + synthesize_match(&matched, &cases_vec, def, bound_allocations, g)?; // The number of slots the match used is the max number of slots of each branch - *next_slot = branch_slots - .into_iter() - .fold(*next_slot, |acc, branch_slot| acc.max(branch_slot)); - - // Now we need to enforce that at exactly one path was taken. We do that by enforcing - // that the sum of the previously collected `Boolean`s is one. But, of course, this - // irrelevant if we're on a virtual path and thus we use an implication gadget. - enforce_selector_with_premise( - &mut cs.namespace(|| "enforce_selector_with_premise"), - not_dummy, - &selector, - ) - .with_context(|| " couldn't constrain `enforce_selector_with_premise`") + *next_slot = next_slot.fold_max(branch_slots); + Ok(()) } - Ctrl::MatchVal(match_var, cases, def) => { - let allocated_lit = bound_allocations.get(match_var)?.hash().clone(); - let mut selector = Vec::with_capacity(cases.len() + 1); - let mut branch_slots = Vec::with_capacity(cases.len()); - for (lit, block) in cases { - let lit_ptr = lit.to_ptr(g.store); - let lit_hash = g.store.hash_ptr(&lit_ptr)?.hash; - let allocated_has_match = alloc_equal_const( - &mut cs.namespace(|| format!("{:?}.alloc_equal_const", lit)), - &allocated_lit, - lit_hash, - ) - .with_context(|| "couldn't allocate equal const")?; - - let not_dummy_and_has_match = and( - &mut cs.namespace(|| format!("{:?}.and", lit)), - not_dummy, - &allocated_has_match, - ) - .with_context(|| "failed to constrain `and`")?; + Ctrl::MatchSymbol(match_var, cases, def) => { + let match_var_ptr = bound_allocations.get_ptr(match_var)?.clone(); + + let mut cases_vec = Vec::with_capacity(cases.len()); + for (sym, block) in cases { + let sym_ptr = g + .store + .interned_symbol(sym) + .expect("symbol must have been interned"); + let sym_hash = *g.store.hash_ptr(sym_ptr)?.value(); + cases_vec.push((sym_hash, block)); + } - selector.push(allocated_has_match); + let branch_slots = synthesize_match( + match_var_ptr.hash(), + &cases_vec, + def, + bound_allocations, + g, + )?; - let mut branch_slot = *next_slot; - recurse( - &mut cs.namespace(|| format!("{:?}", lit)), - block, - ¬_dummy_and_has_match, - &mut branch_slot, - bound_allocations, - preallocated_outputs, - g, - )?; - branch_slots.push(branch_slot); - } + // Now we enforce `match_var`'s tag - match def { - Some(def) => { - let default = selector.iter().all(|b| !b.get_value().unwrap()); - let allocated_has_match = Boolean::Is(AllocatedBit::alloc( - &mut cs.namespace(|| "_.alloc_equal_const"), - Some(default), - )?); + let sym_tag = g + .global_allocator + .get_allocated_const(Tag::Expr(Sym).to_field())?; - let not_dummy_and_has_match = and( - &mut cs.namespace(|| "_.and"), - not_dummy, - &allocated_has_match, - ) - .with_context(|| "failed to constrain `and`")?; - - selector.push(allocated_has_match); - - recurse( - &mut cs.namespace(|| "_"), - def, - ¬_dummy_and_has_match, - next_slot, - bound_allocations, - preallocated_outputs, - g, - )?; - } - None => (), - } + implies_equal( + &mut cs.namespace(|| format!("implies equal {match_var}.tag")), + not_dummy, + match_var_ptr.tag(), + sym_tag, + ); // The number of slots the match used is the max number of slots of each branch - *next_slot = branch_slots - .into_iter() - .fold(*next_slot, |acc, branch_slot| acc.max(branch_slot)); - - // Now we need to enforce that at exactly one path was taken. We do that by enforcing - // that the sum of the previously collected `Boolean`s is one. But, of course, this - // irrelevant if we're on a virtual path and thus we use an implication gadget. - enforce_selector_with_premise( - &mut cs.namespace(|| "enforce_selector_with_premise"), - not_dummy, - &selector, - ) - .with_context(|| " couldn't constrain `enforce_selector_with_premise`") + *next_slot = next_slot.fold_max(branch_slots); + Ok(()) } } } @@ -795,151 +1140,193 @@ impl Func { &self.body, &Boolean::Constant(true), &mut SlotsCounter::default(), - &mut bound_allocations, + bound_allocations, &preallocated_outputs, &mut Globals { store, - global_allocator: &mut global_allocator, - preallocated_hash2_slots, - preallocated_hash3_slots, + global_allocator, preallocated_hash4_slots, + preallocated_hash6_slots, + preallocated_hash8_slots, + preallocated_commitment_slots, + preallocated_less_than_slots, call_outputs, - call_count: 0, }, - ) + )?; + Ok(preallocated_outputs) + } + + /// Helper API for tests + pub fn synthesize_frame_aux>( + &self, + cs: &mut CS, + store: &Store, + frame: &Frame, + ) -> Result<()> { + let bound_allocations = &mut BoundAllocations::new(); + let global_allocator = self.alloc_globals(cs, store)?; + self.allocate_input(cs, store, frame, bound_allocations)?; + self.synthesize_frame(cs, store, frame, &global_allocator, bound_allocations)?; + Ok(()) } /// Computes the number of constraints that `synthesize` should create. It's /// also an explicit way to document and attest how the number of constraints /// grow. - pub fn num_constraints(&self, store: &mut Store) -> usize { + pub fn num_constraints(&self, store: &Store) -> usize { fn recurse( block: &Block, - nested: bool, globals: &mut HashSet>, - store: &mut Store, + store: &Store, ) -> usize { let mut num_constraints = 0; for op in &block.ops { match op { Op::Call(_, func, _) => { - num_constraints += recurse(&func.body, nested, globals, store); + num_constraints += recurse(&func.body, globals, store); } Op::Null(_, tag) => { + use crate::tag::ContTag::{Dummy, Error, Outermost, Terminal}; // constrain tag and hash globals.insert(FWrap(tag.to_field())); - globals.insert(FWrap(F::ZERO)); + match tag { + Tag::Cont(Outermost | Error | Dummy | Terminal) => { + // temporary shim for compatibility with Lurk Alpha + globals.insert(FWrap(store.poseidon_cache.hash8(&[F::ZERO; 8]))); + } + _ => { + globals.insert(FWrap(F::ZERO)); + } + } } Op::Lit(_, lit) => { - let lit_ptr = lit.to_ptr(store); - let lit_hash = store.hash_ptr(&lit_ptr).unwrap().hash; - globals.insert(FWrap(Tag::Expr(Sym).to_field())); - globals.insert(FWrap(lit_hash)); + let lit_ptr = lit.to_ptr_cached(store); + let lit_z_ptr = store.hash_ptr(&lit_ptr).unwrap(); + globals.insert(FWrap(lit_z_ptr.tag_field())); + globals.insert(FWrap(*lit_z_ptr.value())); } - Op::Cast(_tgt, tag, _src) => { + Op::Cast(_, tag, _) => { globals.insert(FWrap(tag.to_field())); } - Op::Add(_, _, _) => { + Op::EqTag(..) | Op::EqVal(..) => { + num_constraints += 3; + } + Op::Add(..) | Op::Sub(..) | Op::Mul(..) => { globals.insert(FWrap(Tag::Expr(Num).to_field())); num_constraints += 1; } - Op::Sub(_, _, _) => { + Op::Div(..) => { globals.insert(FWrap(Tag::Expr(Num).to_field())); - num_constraints += 1; + globals.insert(FWrap(F::ONE)); + num_constraints += 5; } - Op::Mul(_, _, _) => { + Op::Lt(..) => { globals.insert(FWrap(Tag::Expr(Num).to_field())); - num_constraints += 1; + num_constraints += 2; } - Op::Div(_, _, _) => { - // TODO + Op::Trunc(..) => { + globals.insert(FWrap(Tag::Expr(Num).to_field())); + // bit decomposition + enforce_pack + num_constraints += 389; } - Op::Emit(_) => (), - Op::Hash2(_, tag, _) => { + Op::DivRem64(..) => { + globals.insert(FWrap(Tag::Expr(Num).to_field())); + // three implies_u64, one sub and one linear + num_constraints += 197; + } + Op::Not(..) | Op::Emit(_) => (), + Op::Cons2(_, tag, _) => { // tag for the image globals.insert(FWrap(tag.to_field())); // tag and hash for 2 preimage pointers num_constraints += 4; } - Op::Hash3(_, tag, _) => { + Op::Cons3(_, tag, _) => { // tag for the image globals.insert(FWrap(tag.to_field())); // tag and hash for 3 preimage pointers num_constraints += 6; } - Op::Hash4(_, tag, _) => { + Op::Cons4(_, tag, _) => { // tag for the image globals.insert(FWrap(tag.to_field())); // tag and hash for 4 preimage pointers num_constraints += 8; } - Op::Unhash2(..) | Op::Unhash3(..) | Op::Unhash4(..) => { + Op::And(..) + | Op::Or(..) + | Op::Decons2(..) + | Op::Decons3(..) + | Op::Decons4(..) => { // one constraint for the image's hash num_constraints += 1; } Op::Hide(..) => { - // TODO - globals.insert(FWrap(F::ZERO)); + num_constraints += 4; + globals.insert(FWrap(Tag::Expr(Num).to_field())); + globals.insert(FWrap(Tag::Expr(Comm).to_field())); } Op::Open(..) => { - // TODO - globals.insert(FWrap(F::ZERO)); + num_constraints += 2; + globals.insert(FWrap(Tag::Expr(Num).to_field())); + globals.insert(FWrap(Tag::Expr(Comm).to_field())); } } } match &block.ctrl { Ctrl::Return(vars) => num_constraints + 2 * vars.len(), - Ctrl::IfEq(_, _, eq_block, else_block) => { + Ctrl::If(_, true_block, false_block) => { num_constraints - + if nested { 6 } else { 4 } - + recurse(eq_block, true, globals, store) - + recurse(else_block, true, globals, store) + + 2 + + recurse(true_block, globals, store) + + recurse(false_block, globals, store) } Ctrl::MatchTag(_, cases, def) => { - // `alloc_equal_const` adds 3 constraints for each case and - // the `and` is free for non-nested `MatchTag`s, since we - // start `not_dummy` with a constant `true` - let multiplier = if nested { 4 } else { 3 }; - - // then we add 1 constraint from `enforce_selector_with_premise` - num_constraints += multiplier * cases.len() + 1; + // We allocate one boolean per case and constrain it once + // per case. Then we add 1 constraint to enforce only one + // case was selected + num_constraints += 2 * cases.len() + 1; - // stacked ops are now nested for block in cases.values() { - num_constraints += recurse(block, true, globals, store); + num_constraints += recurse(block, globals, store); + } + if let Some(def) = def { + // constraints for the boolean, the unequalities and the default case + num_constraints += 1 + cases.len(); + num_constraints += recurse(def, globals, store); } - match def { - Some(def) => { - // constraints for the boolean and the default case - num_constraints += multiplier - 2; - num_constraints += recurse(def, true, globals, store); - } - None => (), - }; num_constraints } - Ctrl::MatchVal(_, cases, def) => { - let multiplier = if nested { 4 } else { 3 }; - num_constraints += multiplier * cases.len() + 1; + Ctrl::MatchSymbol(_, cases, def) => { + // First we enforce that the tag of the pointer being matched on + // is Sym + num_constraints += 1; + globals.insert(FWrap(Tag::Expr(Sym).to_field())); + // We allocate one boolean per case and constrain it once + // per case. Then we add 1 constraint to enforce only one + // case was selected + num_constraints += 2 * cases.len() + 1; + for block in cases.values() { - num_constraints += recurse(block, true, globals, store); + num_constraints += recurse(block, globals, store); + } + if let Some(def) = def { + // constraints for the boolean, the unequalities and the default case + num_constraints += 1 + cases.len(); + num_constraints += recurse(def, globals, store); } - match def { - Some(def) => { - num_constraints += multiplier - 2; - num_constraints += recurse(def, true, globals, store); - } - None => (), - }; num_constraints } } } let globals = &mut HashSet::default(); // fixed cost for each slot - let slot_constraints = - 289 * self.slot.hash2 + 337 * self.slot.hash3 + 388 * self.slot.hash4; - let num_constraints = recurse::(&self.body, false, globals, store); + let slot_constraints = 289 * self.slot.hash4 + + 337 * self.slot.hash6 + + 388 * self.slot.hash8 + + 265 * self.slot.commitment + + 1172 * self.slot.less_than; + let num_constraints = recurse(&self.body, globals, store); slot_constraints + num_constraints + globals.len() } } diff --git a/src/lem/eval.rs b/src/lem/eval.rs index 07c42a48aa..b482effcd5 100644 --- a/src/lem/eval.rs +++ b/src/lem/eval.rs @@ -1,39 +1,99 @@ -use crate::func; +use anyhow::Result; +use once_cell::sync::OnceCell; -use super::Func; +use crate::{ + field::LurkField, + func, + state::initial_lurk_state, + tag::ContTag::{Error, Outermost, Terminal}, +}; + +use super::{interpreter::Frame, pointers::Ptr, store::Store, Func, Tag}; + +static EVAL_STEP: OnceCell = OnceCell::new(); /// Lurk's step function -#[allow(dead_code)] -pub(crate) fn eval_step() -> Func { - let reduce = reduce(); - let apply_cont = apply_cont(); - let make_thunk = make_thunk(); - - func!(step(expr, env, cont): 3 => { - let (expr, env, cont, ctrl) = reduce(expr, env, cont); - let (expr, env, cont, ctrl) = apply_cont(expr, env, cont, ctrl); - let (expr, env, cont, _ctrl) = make_thunk(expr, env, cont, ctrl); - return (expr, env, cont) +pub fn eval_step() -> &'static Func { + EVAL_STEP.get_or_init(|| { + let reduce = reduce(); + let apply_cont = apply_cont(); + let make_thunk = make_thunk(); + + func!(step(expr, env, cont): 3 => { + let (expr, env, cont, ctrl) = reduce(expr, env, cont); + let (expr, env, cont, ctrl) = apply_cont(expr, env, cont, ctrl); + let (expr, env, cont, _ctrl) = make_thunk(expr, env, cont, ctrl); + return (expr, env, cont) + }) }) } -fn safe_uncons() -> Func { - func!(safe_uncons(xs): 2 => { - let nil: Expr::Nil; - let nilstr = Symbol(""); +pub fn evaluate_with_env_and_cont( + expr: Ptr, + env: Ptr, + cont: Ptr, + store: &mut Store, + limit: usize, +) -> Result<(Vec>, usize)> { + let stop_cond = |output: &[Ptr]| { + output[2] == Ptr::null(Tag::Cont(Terminal)) || output[2] == Ptr::null(Tag::Cont(Error)) + }; + let state = initial_lurk_state(); + let log_fmt = |i: usize, inp: &[Ptr], emit: &[Ptr], store: &Store| { + let mut out = format!( + "Frame: {i}\n\tExpr: {}\n\tEnv: {}\n\tCont: {}", + inp[0].fmt_to_string(store, state), + inp[1].fmt_to_string(store, state), + inp[2].fmt_to_string(store, state) + ); + if let Some(ptr) = emit.first() { + out.push_str(&format!("\n\tEmtd: {}", ptr.fmt_to_string(store, state))); + } + out + }; + + let input = &[expr, env, cont]; + let (frames, iterations, _) = + eval_step().call_until(input, store, stop_cond, limit, log_fmt)?; + Ok((frames, iterations)) +} + +pub fn evaluate( + expr: Ptr, + store: &mut Store, + limit: usize, +) -> Result<(Vec>, usize)> { + evaluate_with_env_and_cont( + expr, + store.intern_nil(), + Ptr::null(Tag::Cont(Outermost)), + store, + limit, + ) +} + +pub fn evaluate_simple( + expr: Ptr, + store: &mut Store, + limit: usize, +) -> Result<(Vec>, usize, Vec>)> { + let stop_cond = |output: &[Ptr]| { + output[2] == Ptr::null(Tag::Cont(Terminal)) || output[2] == Ptr::null(Tag::Cont(Error)) + }; + let input = vec![expr, store.intern_nil(), Ptr::null(Tag::Cont(Outermost))]; + eval_step().call_until_simple(input, store, stop_cond, limit) +} + +fn car_cdr() -> Func { + func!(car_cdr(xs): 2 => { + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); match xs.tag { Expr::Nil => { return (nil, nil) } Expr::Cons => { - let (car, cdr) = unhash2(xs); - return (car, cdr) - } - Expr::Str => { - if xs == nilstr { - return (nil, nilstr) - } - let (car, cdr) = unhash2(xs); + let (car, cdr) = decons2(xs); return (car, cdr) } } @@ -42,25 +102,26 @@ fn safe_uncons() -> Func { fn reduce() -> Func { // Auxiliary functions - let safe_uncons = safe_uncons(); + let car_cdr = car_cdr(); let env_to_use = func!(env_to_use(smaller_env, smaller_rec_env): 1 => { match smaller_rec_env.tag { Expr::Nil => { return (smaller_env) } }; - let env: Expr::Cons = hash2(smaller_rec_env, smaller_env); + let env: Expr::Cons = cons2(smaller_rec_env, smaller_env); return (env) }); let extract_arg = func!(extract_arg(args): 2 => { match args.tag { Expr::Nil => { let dummy = Symbol("dummy"); - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); return (dummy, nil) } Expr::Cons => { - let (arg, rest) = unhash2(args); + let (arg, rest) = decons2(args); return (arg, rest) } } @@ -71,72 +132,167 @@ fn reduce() -> Func { return (body1) } }; - let expanded_0: Expr::Cons = hash2(rest_bindings, body); - let expanded: Expr::Cons = hash2(head, expanded_0); + let expanded_0: Expr::Cons = cons2(rest_bindings, body); + let expanded: Expr::Cons = cons2(head, expanded_0); return (expanded) }); let choose_let_cont = func!(choose_let_cont(head, var, env, expanded, cont): 1 => { - match head.val { - Symbol("let") => { - let cont: Cont::Let = hash4(var, env, expanded, cont); + match symbol head { + "let" => { + let cont: Cont::Let = cons4(var, env, expanded, cont); return (cont) } - Symbol("letrec") => { - let cont: Cont::LetRec = hash4(var, env, expanded, cont); + "letrec" => { + let cont: Cont::LetRec = cons4(var, env, expanded, cont); return (cont) } } }); - let is_unop = func!(is_unop(head): 1 => { - let nil: Expr::Nil; - let t = Symbol("t"); - match head.val { - Symbol("car") - | Symbol("cdr") - | Symbol("commit") - | Symbol("num") - | Symbol("u64") - | Symbol("comm") - | Symbol("char") - | Symbol("open") - | Symbol("secret") - | Symbol("atom") - | Symbol("emit") => { - return (t) + let get_unop = func!(get_unop(head): 1 => { + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); + match symbol head { + "car" => { + let op: Op1::Car; + return (op); + } + "cdr" => { + let op: Op1::Cdr; + return (op); + } + "commit" => { + let op: Op1::Commit; + return (op); + } + "num" => { + let op: Op1::Num; + return (op); + } + "u64" => { + let op: Op1::U64; + return (op); + } + "comm" => { + let op: Op1::Comm; + return (op); + } + "char" => { + let op: Op1::Char; + return (op); + } + "open" => { + let op: Op1::Open; + return (op); + } + "secret" => { + let op: Op1::Secret; + return (op); + } + "atom" => { + let op: Op1::Atom; + return (op); + } + "emit" => { + let op: Op1::Emit; + return (op); } }; return (nil) }); - - let is_binop = func!(is_binop(head): 1 => { - let nil: Expr::Nil; - let t = Symbol("t"); - match head.val { - Symbol("cons") - | Symbol("strcons") - | Symbol("hide") - | Symbol("+") - | Symbol("-") - | Symbol("*") - // TODO: bellperson complains if we use "/" - | Symbol("div") - | Symbol("%") - | Symbol("=") - | Symbol("eq") - | Symbol("<") - | Symbol(">") - | Symbol("<=") - | Symbol(">=") => { - return (t) + let get_binop = func!(get_binop(head): 1 => { + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); + match symbol head { + "cons" => { + let op: Op2::Cons; + return (op); + } + "strcons" => { + let op: Op2::StrCons; + return (op); + } + "hide" => { + let op: Op2::Hide; + return (op); + } + "+" => { + let op: Op2::Sum; + return (op); + } + "-" => { + let op: Op2::Diff; + return (op); + } + "*" => { + let op: Op2::Product; + return (op); + } + "/" => { + let op: Op2::Quotient; + return (op); + } + "%" => { + let op: Op2::Modulo; + return (op); + } + "=" => { + let op: Op2::NumEqual; + return (op); + } + "eq" => { + let op: Op2::Equal; + return (op); + } + "<" => { + let op: Op2::Less; + return (op); + } + ">" => { + let op: Op2::Greater; + return (op); + } + "<=" => { + let op: Op2::LessEqual; + return (op); + } + ">=" => { + let op: Op2::GreaterEqual; + return (op); } }; return (nil) }); + let make_call = func!(make_call(head, rest, env, cont): 4 => { + let ret = Symbol("return"); + let foo: Expr::Nil; + match rest.tag { + Expr::Nil => { + let cont: Cont::Call0 = cons4(env, cont, foo, foo); + return (head, env, cont, ret) + } + Expr::Cons => { + let (arg, more_args) = decons2(rest); + match more_args.tag { + Expr::Nil => { + let cont: Cont::Call = cons4(arg, env, cont, foo); + return (head, env, cont, ret) + } + }; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); + let expanded_inner0: Expr::Cons = cons2(arg, nil); + let expanded_inner: Expr::Cons = cons2(head, expanded_inner0); + let expanded: Expr::Cons = cons2(expanded_inner, more_args); + return (expanded, env, cont, ret) + } + } + }); let is_potentially_fun = func!(is_potentially_fun(head): 1 => { let t = Symbol("t"); - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); match head.tag { - Expr::Fun | Expr::Cons | Expr::Sym | Expr::Thunk => { + Expr::Fun | Expr::Cons | Expr::Thunk => { return (t) } }; @@ -145,12 +301,14 @@ fn reduce() -> Func { func!(reduce(expr, env, cont): 4 => { // Useful constants - let ret: Ctrl::Return; - let apply: Ctrl::ApplyContinuation; - let errctrl: Ctrl::Error; + let ret = Symbol("return"); + let apply = Symbol("apply-continuation"); + let errctrl = Symbol("error"); let err: Cont::Error; - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let t = Symbol("t"); + let foo: Expr::Nil; match cont.tag { Cont::Terminal | Cont::Error => { @@ -164,12 +322,12 @@ fn reduce() -> Func { return (expr, env, cont, apply) } Expr::Thunk => { - let (thunk_expr, thunk_continuation) = unhash2(expr); + let (thunk_expr, thunk_continuation) = decons2(expr); return (thunk_expr, env, thunk_continuation, apply) } Expr::Sym => { - match expr.val { - Symbol("nil") | Symbol("t") => { + match symbol expr { + "nil", "t" => { return (expr, env, cont, apply) } }; @@ -180,7 +338,7 @@ fn reduce() -> Func { } }; - let (binding, smaller_env) = safe_uncons(env); + let (binding, smaller_env) = car_cdr(env); match binding.tag { Expr::Nil => { return (expr, env, err, errctrl) @@ -188,10 +346,11 @@ fn reduce() -> Func { }; let (var_or_rec_binding, val_or_more_rec_env) = - safe_uncons(binding); + car_cdr(binding); match var_or_rec_binding.tag { Expr::Sym => { - if var_or_rec_binding == expr { + let eq_val = eq_val(var_or_rec_binding, expr); + if eq_val { return (val_or_more_rec_env, env, cont, apply) } match cont.tag { @@ -199,20 +358,21 @@ fn reduce() -> Func { return (expr, smaller_env, cont, ret) } }; - let cont: Cont::Lookup = hash2(env, cont); + let cont: Cont::Lookup = cons4(env, cont, foo, foo); return (expr, smaller_env, cont, ret) } Expr::Cons => { - let (v2, val2) = safe_uncons(var_or_rec_binding); + let (v2, val2) = decons2(var_or_rec_binding); - if v2 == expr { + let eq_val = eq_val(v2, expr); + if eq_val { match val2.tag { Expr::Fun => { // if `val2` is a closure, then extend its environment - let (arg, body, closed_env) = unhash3(val2); - let extended: Expr::Cons = hash2(binding, closed_env); + let (arg, body, closed_env) = decons3(val2); + let extended: Expr::Cons = cons2(binding, closed_env); // and return the extended closure - let fun: Expr::Fun = hash3(arg, body, extended); + let fun: Expr::Fun = cons3(arg, body, extended); return (fun, env, cont, apply) } }; @@ -226,73 +386,85 @@ fn reduce() -> Func { return (expr, env_to_use, cont, ret) } }; - let cont: Cont::Lookup = hash2(env, cont); + let cont: Cont::Lookup = cons4(env, cont, foo, foo); return (expr, env_to_use, cont, ret) } - } + }; + return (expr, env, err, errctrl) } Expr::Cons => { - // No need for `safe_uncons` since the expression is already a `Cons` - let (head, rest) = unhash2(expr); - match head.val { - Symbol("lambda") => { - let (args, body) = safe_uncons(rest); - let (arg, cdr_args) = extract_arg(args); - - match arg.tag { - Expr::Sym => { - match cdr_args.tag { - Expr::Nil => { - let function: Expr::Fun = hash3(arg, body, env); + // No need for `car_cdr` since the expression is already a `Cons` + let (head, rest) = decons2(expr); + match rest.tag { + // rest's tag can only be Nil or Cons + Expr::Sym | Expr::Fun | Expr::Num | Expr::Thunk | Expr::Str + | Expr::Char | Expr::Comm | Expr::U64 | Expr::Key => { + return (expr, env, err, errctrl); + } + }; + match head.tag { + Expr::Sym => { + match symbol head { + "lambda" => { + let (args, body) = car_cdr(rest); + let (arg, cdr_args) = extract_arg(args); + + match arg.tag { + Expr::Sym => { + match cdr_args.tag { + Expr::Nil => { + let function: Expr::Fun = cons3(arg, body, env); + return (function, env, cont, apply) + } + }; + let inner: Expr::Cons = cons2(cdr_args, body); + let l: Expr::Cons = cons2(head, inner); + let inner_body: Expr::Cons = cons2(l, nil); + let function: Expr::Fun = cons3(arg, inner_body, env); return (function, env, cont, apply) } }; - let inner: Expr::Cons = hash2(cdr_args, body); - let lambda = Symbol("lambda"); - let l: Expr::Cons = hash2(lambda, inner); - let inner_body: Expr::Cons = hash2(l, nil); - let function: Expr::Fun = hash3(arg, inner_body, env); - return (function, env, cont, apply) + return (expr, env, err, errctrl) } - }; - return (expr, env, err, errctrl) - } - Symbol("quote") => { - let (quoted, end) = safe_uncons(rest); + "quote" => { + let (quoted, end) = car_cdr(rest); - match end.tag { - Expr::Nil => { - return (quoted, env, cont, apply) - } - }; - return (expr, env, err, errctrl) - } - Symbol("let") | Symbol("letrec") => { - let (bindings, body) = safe_uncons(rest); - let (body1, rest_body) = safe_uncons(body); - // Only a single body form allowed for now. - match body.tag { - Expr::Nil => { + match end.tag { + Expr::Nil => { + return (quoted, env, cont, apply) + } + }; return (expr, env, err, errctrl) } - }; - match rest_body.tag { - Expr::Nil => { - match bindings.tag { + "let", "letrec" => { + let (bindings, body) = car_cdr(rest); + let (body1, rest_body) = car_cdr(body); + // Only a single body form allowed for now. + match body.tag { Expr::Nil => { - return (body1, env, cont, ret) + return (expr, env, err, errctrl) } }; - let (binding1, rest_bindings) = safe_uncons(bindings); - let (var, vals) = safe_uncons(binding1); - match var.tag { - Expr::Sym => { - let (val, end) = safe_uncons(vals); - match end.tag { + match rest_body.tag { + Expr::Nil => { + match bindings.tag { Expr::Nil => { - let (expanded) = expand_bindings(head, body, body1, rest_bindings); - let (cont) = choose_let_cont(head, var, env, expanded, cont); - return (val, env, cont, ret) + return (body1, env, cont, ret) + } + }; + let (binding1, rest_bindings) = car_cdr(bindings); + let (var, vals) = car_cdr(binding1); + match var.tag { + Expr::Sym => { + let (val, end) = car_cdr(vals); + match end.tag { + Expr::Nil => { + let (expanded) = expand_bindings(head, body, body1, rest_bindings); + let (cont) = choose_let_cont(head, var, env, expanded, cont); + return (val, env, cont, ret) + } + }; + return (expr, env, err, errctrl) } }; return (expr, env, err, errctrl) @@ -300,112 +472,99 @@ fn reduce() -> Func { }; return (expr, env, err, errctrl) } - }; - return (expr, env, err, errctrl) - } - Symbol("begin") => { - let (arg1, more) = safe_uncons(rest); - match more.tag { - Expr::Nil => { + "begin" => { + let (arg1, more) = car_cdr(rest); + match more.tag { + Expr::Nil => { + return (arg1, env, cont, ret) + } + }; + let op: Op2::Begin; + let cont: Cont::Binop = cons4(op, env, more, cont); return (arg1, env, cont, ret) } - }; - let cont: Cont::Binop = hash4(head, env, more, cont); - return (arg1, env, cont, ret) - } - Symbol("eval") => { - match rest.tag { - Expr::Nil => { - return (expr, env, err, errctrl) - } - }; - let (arg1, more) = safe_uncons(rest); - match more.tag { - Expr::Nil => { - let cont: Cont::Unop = hash2(head, cont); + "eval" => { + match rest.tag { + Expr::Nil => { + return (expr, env, err, errctrl) + } + }; + let (arg1, more) = car_cdr(rest); + match more.tag { + Expr::Nil => { + let op: Op1::Eval; + let cont: Cont::Unop = cons4(op, cont, foo, foo); + return (arg1, env, cont, ret) + } + }; + let op: Op2::Eval; + let cont: Cont::Binop = cons4(op, env, more, cont); return (arg1, env, cont, ret) } - }; - let cont: Cont::Binop = hash4(head, env, more, cont); - return (arg1, env, cont, ret) - } - Symbol("if") => { - let (condition, more) = safe_uncons(rest); - match more.tag { - Expr::Nil => { - return (condition, env, err, errctrl) + "if" => { + let (condition, more) = car_cdr(rest); + match more.tag { + Expr::Nil => { + return (expr, env, err, errctrl) + } + }; + let cont: Cont::If = cons4(more, cont, foo, foo); + return (condition, env, cont, ret) } - }; - let cont: Cont::If = hash2(more, cont); - return (condition, env, cont, ret) - } - Symbol("current-env") => { - match rest.tag { - Expr::Nil => { - return (env, env, cont, apply) + "current-env" => { + match rest.tag { + Expr::Nil => { + return (env, env, cont, apply) + } + }; + return (expr, env, err, errctrl) } }; - return (expr, env, err, errctrl) - } - }; - // unops - let (op) = is_unop(head); - if op == t { - match rest.tag { - Expr::Nil => { - return (expr, env, err, errctrl) - } - }; - let (arg1, end) = unhash2(rest); - match end.tag { - Expr::Nil => { - let cont: Cont::Unop = hash2(head, cont); - return (arg1, env, cont, ret) - } - }; - return (expr, env, err, errctrl) - } - // binops - let (op) = is_binop(head); - if op == t { - match rest.tag { - Expr::Nil => { - return (expr, env, err, errctrl) + // unops + let (op) = get_unop(head); + let eq_val = eq_val(op, nil); + if !eq_val { + let eq_val = eq_val(rest, nil); + if !eq_val { + let (arg1, end) = decons2(rest); + let eq_val = eq_val(end, nil); + if eq_val { + let cont: Cont::Unop = cons4(op, cont, foo, foo); + return (arg1, env, cont, ret) + } + return (expr, env, err, errctrl); + } + return (expr, env, err, errctrl); } - }; - let (arg1, more) = unhash2(rest); - match more.tag { - Expr::Nil => { - return (expr, env, err, errctrl) + // binops + let (op) = get_binop(head); + let eq_val = eq_val(op, nil); + if !eq_val { + let eq_val = eq_val(rest, nil); + if !eq_val { + let (arg1, more) = decons2(rest); + let eq_val = eq_val(more, nil); + if !eq_val { + let cont: Cont::Binop = cons4(op, env, more, cont); + return (arg1, env, cont, ret); + } + return (expr, env, err, errctrl); + } + return (expr, env, err, errctrl); } - }; - let cont: Cont::Binop = hash4(head, env, more, cont); - return (arg1, env, cont, ret) - } + // just call assuming that the symbol is bound to a function + let (fun, env, cont, ret) = make_call(head, rest, env, cont); + return (fun, env, cont, ret); + } + }; // TODO coprocessors (could it be simply a `func`?) // head -> fn, rest -> args let (potentially_fun) = is_potentially_fun(head); - if potentially_fun == t { - match rest.tag { - Expr::Nil => { - let cont: Cont::Call0 = hash2(env, cont); - return (head, env, cont, ret) - } - Expr::Cons => { - let (arg, more_args) = unhash2(rest); - match more_args.tag { - Expr::Nil => { - let cont: Cont::Call = hash3(arg, env, cont); - return (head, env, cont, ret) - } - }; - let expanded_inner0: Expr::Cons = hash2(arg, nil); - let expanded_inner: Expr::Cons = hash2(head, expanded_inner0); - let expanded: Expr::Cons = hash2(expanded_inner, more_args); - return (expanded, env, cont, ret) - } - } + let eq_val = eq_val(potentially_fun, t); + if eq_val { + let (fun, env, cont, ret) = make_call(head, rest, env, cont); + return (fun, env, cont, ret); } return (expr, env, err, errctrl) } @@ -414,49 +573,91 @@ fn reduce() -> Func { } fn apply_cont() -> Func { - let safe_uncons = safe_uncons(); + let car_cdr = car_cdr(); let make_tail_continuation = func!(make_tail_continuation(env, continuation): 1 => { + let foo: Expr::Nil; match continuation.tag { Cont::Tail => { return (continuation); } }; - let tail_continuation: Cont::Tail = hash2(env, continuation); + let tail_continuation: Cont::Tail = cons4(env, continuation, foo, foo); return (tail_continuation); }); let extend_rec = func!(extend_rec(env, var, result): 1 => { - let (binding_or_env, rest) = safe_uncons(env); - let (var_or_binding, _val_or_more_bindings) = safe_uncons(binding_or_env); - let cons: Expr::Cons = hash2(var, result); + let (binding_or_env, rest) = car_cdr(env); + let (var_or_binding, _val_or_more_bindings) = car_cdr(binding_or_env); + let cons: Expr::Cons = cons2(var, result); match var_or_binding.tag { // It's a var, so we are extending a simple env with a recursive env. Expr::Sym | Expr::Nil => { - let nil: Expr::Nil; - let list: Expr::Cons = hash2(cons, nil); - let res: Expr::Cons = hash2(list, env); + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); + let list: Expr::Cons = cons2(cons, nil); + let res: Expr::Cons = cons2(list, env); return (res) } // It's a binding, so we are extending a recursive env. Expr::Cons => { - let cons2: Expr::Cons = hash2(cons, binding_or_env); - let res: Expr::Cons = hash2(cons2, rest); + let cons2: Expr::Cons = cons2(cons, binding_or_env); + let res: Expr::Cons = cons2(cons2, rest); return (res) } } }); + // Returns 0u64 if both arguments are U64, 0 (num) if the arguments are some kind of number (either U64 or Num), + // and nil otherwise + let args_num_type = func!(args_num_type(arg1, arg2): 1 => { + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); + match arg1.tag { + Expr::Num => { + match arg2.tag { + Expr::Num => { + let ret: Expr::Num; + return (ret) + } + Expr::U64 => { + let ret: Expr::Num; + return (ret) + } + }; + return (nil) + } + Expr::U64 => { + match arg2.tag { + Expr::Num => { + let ret: Expr::Num; + return (ret) + } + Expr::U64 => { + let ret: Expr::U64; + return (ret) + } + }; + return (nil) + } + }; + return (nil) + }); func!(apply_cont(result, env, cont, ctrl): 4 => { // Useful constants - let ret: Ctrl::Return; - let makethunk: Ctrl::MakeThunk; - let errctrl: Ctrl::Error; + let ret = Symbol("return"); + let makethunk = Symbol("make-thunk"); + let errctrl = Symbol("error"); let err: Cont::Error; - let nil: Expr::Nil; + let nil = Symbol("nil"); + let nil = cast(nil, Expr::Nil); let t = Symbol("t"); + let zero = Num(0); + let size_u64 = Num(18446744073709551616); + let empty_str = String(""); + let foo: Expr::Nil; - match ctrl.tag { - Ctrl::ApplyContinuation => { + match symbol ctrl { + "apply-continuation" => { match cont.tag { Cont::Terminal | Cont::Error => { return (result, env, cont, ret) @@ -466,24 +667,22 @@ fn apply_cont() -> Func { return (result, env, cont, ret) } Cont::Emit => { - emit(result); - // TODO Does this make sense? - let (cont, _rest) = unhash2(cont); + let (cont, _rest, _foo, _foo) = decons4(cont); return (result, env, cont, makethunk) } Cont::Call0 => { - let (saved_env, continuation) = unhash2(cont); + let (saved_env, continuation, _foo, _foo) = decons4(cont); match result.tag { Expr::Fun => { - let (arg, body, closed_env) = unhash3(result); - match arg.val { - Symbol("dummy") => { + let (arg, body, closed_env) = decons3(result); + match symbol arg { + "dummy" => { match body.tag { Expr::Nil => { return (result, env, err, errctrl) } }; - let (body_form, end) = safe_uncons(body); + let (body_form, end) = car_cdr(body); match end.tag { Expr::Nil => { let (cont) = make_tail_continuation(saved_env, continuation); @@ -501,20 +700,20 @@ fn apply_cont() -> Func { Cont::Call => { match result.tag { Expr::Fun => { - let (unevaled_arg, saved_env, continuation) = unhash3(cont); - let newer_cont: Cont::Call2 = hash3(result, saved_env, continuation); + let (unevaled_arg, saved_env, continuation, _foo) = decons4(cont); + let newer_cont: Cont::Call2 = cons4(result, saved_env, continuation, foo); return (unevaled_arg, env, newer_cont, ret) } }; return (result, env, err, errctrl) } Cont::Call2 => { - let (function, saved_env, continuation) = unhash3(cont); + let (function, saved_env, continuation, _foo) = decons4(cont); match function.tag { Expr::Fun => { - let (arg, body, closed_env) = unhash3(function); - match arg.val { - Symbol("dummy") => { + let (arg, body, closed_env) = decons3(function); + match symbol arg { + "dummy" => { return (result, env, err, errctrl) } }; @@ -523,11 +722,11 @@ fn apply_cont() -> Func { return (result, env, err, errctrl) } }; - let (body_form, end) = unhash2(body); + let (body_form, end) = decons2(body); match end.tag { Expr::Nil => { - let binding: Expr::Cons = hash2(arg, result); - let newer_env: Expr::Cons = hash2(binding, closed_env); + let binding: Expr::Cons = cons2(arg, result); + let newer_env: Expr::Cons = cons2(binding, closed_env); let (cont) = make_tail_continuation(saved_env, continuation); return (body_form, newer_env, cont, ret) } @@ -538,30 +737,64 @@ fn apply_cont() -> Func { return (result, env, err, errctrl) } Cont::Let => { - let (var, saved_env, body, cont) = unhash4(cont); - let binding: Expr::Cons = hash2(var, result); - let extended_env: Expr::Cons = hash2(binding, env); + let (var, saved_env, body, cont) = decons4(cont); + let binding: Expr::Cons = cons2(var, result); + let extended_env: Expr::Cons = cons2(binding, env); let (cont) = make_tail_continuation(saved_env, cont); return (body, extended_env, cont, ret) } Cont::LetRec => { - let (var, saved_env, body, cont) = unhash4(cont); + let (var, saved_env, body, cont) = decons4(cont); let (extended_env) = extend_rec(env, var, result); let (cont) = make_tail_continuation(saved_env, cont); return (body, extended_env, cont, ret) } Cont::Unop => { - let (operator, continuation) = unhash2(cont); - match operator.val { - Symbol("car") => { - let (car, _cdr) = safe_uncons(result); - return (car, env, continuation, makethunk) + let (operator, continuation, _foo, _foo) = decons4(cont); + match operator.tag { + Op1::Car => { + // Almost like car_cdr, except it returns + // an error in case it can't deconstruct it + match result.tag { + Expr::Nil => { + return (nil, env, continuation, makethunk) + } + Expr::Cons => { + let (car, _cdr) = decons2(result); + return (car, env, continuation, makethunk) + } + Expr::Str => { + let eq_val = eq_val(result, empty_str); + if eq_val { + return (nil, env, continuation, makethunk) + } + let (car, _cdr) = decons2(result); + return (car, env, continuation, makethunk) + } + }; + return(result, env, err, errctrl) } - Symbol("cdr") => { - let (_car, cdr) = safe_uncons(result); - return (cdr, env, continuation, makethunk) + Op1::Cdr => { + match result.tag { + Expr::Nil => { + return (nil, env, continuation, makethunk) + } + Expr::Cons => { + let (_car, cdr) = decons2(result); + return (cdr, env, continuation, makethunk) + } + Expr::Str => { + let eq_val = eq_val(result, empty_str); + if eq_val { + return (empty_str, env, continuation, makethunk) + } + let (_car, cdr) = decons2(result); + return (cdr, env, continuation, makethunk) + } + }; + return(result, env, err, errctrl) } - Symbol("atom") => { + Op1::Atom => { match result.tag { Expr::Cons => { return (nil, env, continuation, makethunk) @@ -569,25 +802,44 @@ fn apply_cont() -> Func { }; return (t, env, continuation, makethunk) } - Symbol("emit") => { - // TODO Does this make sense? - let emit: Cont::Emit = hash2(cont, nil); + Op1::Emit => { + emit(result); + let emit: Cont::Emit = cons4(continuation, nil, foo, foo); return (result, env, emit, makethunk) } - Symbol("open") => { - let (_secret, payload) = open(result); - return(payload, env, continuation, makethunk) + Op1::Open => { + match result.tag { + Expr::Num => { + let result = cast(result, Expr::Comm); + let (_secret, payload) = open(result); + return(payload, env, continuation, makethunk) + } + Expr::Comm => { + let (_secret, payload) = open(result); + return(payload, env, continuation, makethunk) + } + }; + return(result, env, err, errctrl) } - Symbol("secret") => { - let (secret, _payload) = open(result); - return(secret, env, continuation, makethunk) + Op1::Secret => { + match result.tag { + Expr::Num => { + let result = cast(result, Expr::Comm); + let (secret, _payload) = open(result); + return(secret, env, continuation, makethunk) + } + Expr::Comm => { + let (secret, _payload) = open(result); + return(secret, env, continuation, makethunk) + } + }; + return(result, env, err, errctrl) } - Symbol("commit") => { - let zero = Num(0); + Op1::Commit => { let comm = hide(zero, result); return(comm, env, continuation, makethunk) } - Symbol("num") => { + Op1::Num => { match result.tag { Expr::Num | Expr::Comm | Expr::Char | Expr::U64 => { let cast = cast(result, Expr::Num); @@ -596,14 +848,12 @@ fn apply_cont() -> Func { }; return(result, env, err, errctrl) } - Symbol("u64") => { + Op1::U64 => { match result.tag { Expr::Num => { - // TODO we also need to use `Mod` to truncate - // But 2^64 is out-of-range of u64, so we will - // maybe use u128 - // let limit = Num(18446744073709551616); - let cast = cast(result, Expr::U64); + // The limit is 2**64 - 1 + let trunc = truncate(result, 64); + let cast = cast(trunc, Expr::U64); return(cast, env, continuation, makethunk) } Expr::U64 => { @@ -612,71 +862,74 @@ fn apply_cont() -> Func { }; return(result, env, err, errctrl) } - Symbol("comm") => { + Op1::Comm => { match result.tag { Expr::Num | Expr::Comm => { - let cast = cast(result, Expr::Num); + let cast = cast(result, Expr::Comm); return(cast, env, continuation, makethunk) } }; return(result, env, err, errctrl) } - Symbol("char") => { + Op1::Char => { match result.tag { - Expr::Num | Expr::Char => { - // TODO we also need to use `Mod` to truncate - // let limit = Num(4294967296); - let cast = cast(result, Expr::Num); + Expr::Num => { + // The limit is 2**32 - 1 + let trunc = truncate(result, 32); + let cast = cast(trunc, Expr::Char); return(cast, env, continuation, makethunk) } + Expr::Char => { + return(result, env, continuation, makethunk) + } }; return(result, env, err, errctrl) } - Symbol("eval") => { + Op1::Eval => { return(result, nil, continuation, ret) } }; return (result, env, err, errctrl) } Cont::Binop => { - let (operator, saved_env, unevaled_args, continuation) = unhash4(cont); - let (arg2, rest) = safe_uncons(unevaled_args); - match operator.val { - Symbol("begin") => { + let (operator, saved_env, unevaled_args, continuation) = decons4(cont); + let (arg2, rest) = car_cdr(unevaled_args); + match operator.tag { + Op2::Begin => { match rest.tag { Expr::Nil => { return (arg2, saved_env, continuation, ret) } }; - let begin = Symbol("begin"); - let begin_again: Expr::Cons = hash2(begin, unevaled_args); + let begin_again: Expr::Cons = cons2(operator, unevaled_args); return (begin_again, saved_env, continuation, ctrl) } }; match rest.tag { Expr::Nil => { - let cont: Cont::Binop2 = hash3(operator, result, continuation); + let cont: Cont::Binop2 = cons4(operator, result, continuation, foo); return (arg2, saved_env, cont, ret) } }; return (result, env, err, errctrl) } Cont::Binop2 => { - let (operator, evaled_arg, continuation) = unhash3(cont); - match operator.val { - Symbol("eval") => { + let (operator, evaled_arg, continuation, _foo) = decons4(cont); + let (args_num_type) = args_num_type(evaled_arg, result); + match operator.tag { + Op2::Eval => { return (evaled_arg, result, continuation, ret) } - Symbol("cons") => { - let val: Expr::Cons = hash2(evaled_arg, result); + Op2::Cons => { + let val: Expr::Cons = cons2(evaled_arg, result); return (val, env, continuation, makethunk) } - Symbol("strcons") => { + Op2::StrCons => { match evaled_arg.tag { Expr::Char => { - match evaled_arg.tag { + match result.tag { Expr::Str => { - let val: Expr::Cons = hash2(evaled_arg, result); + let val: Expr::Str = cons2(evaled_arg, result); return (val, env, continuation, makethunk) } }; @@ -685,67 +938,171 @@ fn apply_cont() -> Func { }; return (result, env, err, errctrl) } - Symbol("hide") => { - let num = cast(evaled_arg, Expr::Num); - let hidden = hide(num, result); - return(hidden, env, continuation, makethunk) + Op2::Hide => { + match evaled_arg.tag { + Expr::Num => { + let hidden = hide(evaled_arg, result); + return(hidden, env, continuation, makethunk) + } + }; + return (result, env, err, errctrl) } - Symbol("eq") => { - // TODO should we check whether the tags are also equal? - if evaled_arg == result { + Op2::Equal => { + let eq_tag = eq_tag(evaled_arg, result); + let eq_val = eq_val(evaled_arg, result); + let eq = and(eq_tag, eq_val); + if eq { return (t, env, continuation, makethunk) } return (nil, env, continuation, makethunk) } - Symbol("+") => { - // TODO deal with U64 - let val = add(evaled_arg, result); - return (val, env, continuation, makethunk) + Op2::Sum => { + match args_num_type.tag { + Expr::Nil => { + return (result, env, err, errctrl) + } + Expr::Num => { + let val = add(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Expr::U64 => { + let val = add(evaled_arg, result); + let not_overflow = lt(val, size_u64); + if not_overflow { + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + let val = sub(val, size_u64); + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + } } - Symbol("-") => { - // TODO deal with U64 - let val = sub(evaled_arg, result); - return (val, env, continuation, makethunk) + Op2::Diff => { + match args_num_type.tag { + Expr::Nil => { + return (result, env, err, errctrl) + } + Expr::Num => { + let val = sub(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Expr::U64 => { + // Subtraction in U64 is almost the same as subtraction + // in the field. If the difference is negative, we need + // to add 2^64 to get back to U64 domain. + let val = sub(evaled_arg, result); + let is_neg = lt(val, zero); + let not_neg = not(is_neg); + if not_neg { + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + let val = add(val, size_u64); + let val = cast(val, Expr::U64); + return (val, env, continuation, makethunk) + } + } } - Symbol("*") => { - // TODO deal with U64 - let val = mul(evaled_arg, result); - return (val, env, continuation, makethunk) + Op2::Product => { + match args_num_type.tag { + Expr::Nil => { + return (result, env, err, errctrl) + } + Expr::Num => { + let val = mul(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Expr::U64 => { + let val = mul(evaled_arg, result); + // The limit is 2**64 - 1 + let trunc = truncate(val, 64); + let cast = cast(trunc, Expr::U64); + return (cast, env, continuation, makethunk) + } + } } - Symbol("div") => { - return (result, env, err, errctrl) + Op2::Quotient => { + let is_z = eq_val(result, zero); + if is_z { + return (result, env, err, errctrl) + } + match args_num_type.tag { + Expr::Nil => { + return (result, env, err, errctrl) + } + Expr::Num => { + let val = div(evaled_arg, result); + return (val, env, continuation, makethunk) + } + Expr::U64 => { + let (div, _rem) = div_rem64(evaled_arg, result); + let div = cast(div, Expr::U64); + return (div, env, continuation, makethunk) + } + } } - Symbol("%") => { - // TODO + Op2::Modulo => { + let is_z = eq_val(result, zero); + if is_z { + return (result, env, err, errctrl) + } + match args_num_type.tag { + Expr::U64 => { + let (_div, rem) = div_rem64(evaled_arg, result); + let rem = cast(rem, Expr::U64); + return (rem, env, continuation, makethunk) + } + }; return (result, env, err, errctrl) } - Symbol("=") => { - // TODO - return (result, env, err, errctrl) + Op2::NumEqual => { + match args_num_type.tag { + Expr::Nil => { + return (result, env, err, errctrl) + } + }; + let eq = eq_val(evaled_arg, result); + if eq { + return (t, env, continuation, makethunk) + } + return (nil, env, continuation, makethunk) } - Symbol("<") => { - // TODO - return (result, env, err, errctrl) + Op2::Less => { + let val = lt(evaled_arg, result); + if val { + return (t, env, continuation, makethunk) + } + return (nil, env, continuation, makethunk) } - Symbol(">") => { - // TODO - return (result, env, err, errctrl) + Op2::Greater => { + let val = lt(result, evaled_arg); + if val { + return (t, env, continuation, makethunk) + } + return (nil, env, continuation, makethunk) } - Symbol("<=") => { - // TODO - return (result, env, err, errctrl) + Op2::LessEqual => { + let val = lt(result, evaled_arg); + if val { + return (nil, env, continuation, makethunk) + } + return (t, env, continuation, makethunk) } - Symbol(">=") => { - // TODO - return (result, env, err, errctrl) + Op2::GreaterEqual => { + let val = lt(evaled_arg, result); + if val { + return (nil, env, continuation, makethunk) + } + return (t, env, continuation, makethunk) } }; return (result, env, err, errctrl) } Cont::If => { - let (unevaled_args, continuation) = unhash2(cont); - let (arg1, more) = safe_uncons(unevaled_args); - let (arg2, end) = safe_uncons(more); + let (unevaled_args, continuation, _foo, _foo) = decons4(cont); + let (arg1, more) = car_cdr(unevaled_args); + let (arg2, end) = car_cdr(more); match end.tag { Expr::Nil => { match result.tag { @@ -756,14 +1113,14 @@ fn apply_cont() -> Func { return (arg1, env, continuation, ret) } }; - return (result, env, err, errctrl) + return (arg1, env, err, errctrl) } Cont::Lookup => { - let (saved_env, continuation) = unhash2(cont); + let (saved_env, continuation, _foo, _foo) = decons4(cont); return (result, saved_env, continuation, makethunk) } Cont::Tail => { - let (saved_env, continuation) = unhash2(cont); + let (saved_env, continuation, _foo, _foo) = decons4(cont); return (result, saved_env, continuation, makethunk) } } @@ -775,13 +1132,13 @@ fn apply_cont() -> Func { fn make_thunk() -> Func { func!(make_thunk(expr, env, cont, ctrl): 4 => { - let ret: Ctrl::Return; - match ctrl.tag { - Ctrl::MakeThunk => { + let ret = Symbol("return"); + match symbol ctrl { + "make-thunk" => { match cont.tag { Cont::Tail => { - let (saved_env, saved_cont) = unhash2(cont); - let thunk: Expr::Thunk = hash2(expr, saved_cont); + let (saved_env, saved_cont, _foo, _foo) = decons4(cont); + let thunk: Expr::Thunk = cons2(expr, saved_cont); let cont: Cont::Dummy; return (thunk, saved_env, cont, ret) } @@ -790,7 +1147,7 @@ fn make_thunk() -> Func { return (expr, env, cont, ret) } }; - let thunk: Expr::Thunk = hash2(expr, cont); + let thunk: Expr::Thunk = cons2(expr, cont); let cont: Cont::Dummy; return (thunk, env, cont, ret) } @@ -802,23 +1159,29 @@ fn make_thunk() -> Func { #[cfg(test)] mod tests { use super::*; - use crate::lem::{pointers::Ptr, slot::SlotsCounter, store::Store, Tag}; - use crate::tag::ContTag::*; - use bellperson::util_cs::{test_cs::TestConstraintSystem, Comparable}; + use crate::{ + lem::{pointers::Ptr, slot::SlotsCounter, store::Store, Tag}, + state::State, + }; + use bellpepper_core::{test_cs::TestConstraintSystem, Comparable, Delta}; use blstrs::Scalar as Fr; const NUM_INPUTS: usize = 1; - const NUM_AUX: usize = 8092; - const NUM_CONSTRAINTS: usize = 10125; + const NUM_AUX: usize = 10529; + const NUM_CONSTRAINTS: usize = 12811; const NUM_SLOTS: SlotsCounter = SlotsCounter { - hash2: 16, - hash3: 4, - hash4: 2, + hash4: 14, + hash6: 3, + hash8: 4, + commitment: 1, + less_than: 1, }; - fn test_eval_and_constrain_aux(store: &mut Store, pairs: Vec<(Ptr, Ptr)>) { - let eval_step = eval_step(); - + fn test_eval_and_constrain_aux( + eval_step: &Func, + store: &mut Store, + pairs: Vec<(Ptr, Ptr)>, + ) { assert_eq!(eval_step.slot, NUM_SLOTS); let computed_num_constraints = eval_step.num_constraints::(store); @@ -834,15 +1197,24 @@ mod tests { // Stop condition: the continuation is either terminal or error let stop_cond = |output: &[Ptr]| output[2] == terminal || output[2] == error; - for (expr_in, expr_out) in pairs { - let input = vec![expr_in, nil, outermost]; - let (frames, paths) = eval_step.call_until(input, store, stop_cond).unwrap(); + let log_fmt = |_: usize, _: &[Ptr], _: &[Ptr], _: &Store| String::default(); + + let limit = 10000; + + let mut cs_prev = None; + for (i, (expr_in, expr_out)) in pairs.into_iter().enumerate() { + let input = [expr_in, nil, outermost]; + let (frames, _, paths) = eval_step + .call_until(&input, store, stop_cond, limit, log_fmt) + .unwrap(); let last_frame = frames.last().expect("eval should add at least one frame"); - assert_eq!(last_frame.output[0], expr_out); + assert_eq!(last_frame.output[0], expr_out, "pair {i}"); store.hydrate_z_cache(); for frame in frames.iter() { let mut cs = TestConstraintSystem::::new(); - eval_step.synthesize(&mut cs, store, frame).unwrap(); + eval_step + .synthesize_frame_aux(&mut cs, store, frame) + .unwrap(); assert!(cs.is_satisfied()); assert_eq!(cs.num_inputs(), NUM_INPUTS); assert_eq!(cs.aux().len(), NUM_AUX); @@ -850,7 +1222,12 @@ mod tests { let num_constraints = cs.num_constraints(); assert_eq!(computed_num_constraints, num_constraints); assert_eq!(num_constraints, NUM_CONSTRAINTS); - // TODO: assert uniformity with `Delta` from bellperson + + if let Some(cs_prev) = cs_prev { + // Check for all input expresssions that all frames are uniform. + assert_eq!(cs.delta(&cs_prev, true), Delta::Equal); + } + cs_prev = Some(cs); } all_paths.extend(paths); } @@ -860,36 +1237,71 @@ mod tests { } fn expr_in_expr_out_pairs(s: &mut Store) -> Vec<(Ptr, Ptr)> { - let sum = s.read("(+ 21 21)").unwrap(); - let sum_res = s.read("42").unwrap(); - let car = s.read("(car (cons 1 2))").unwrap(); - let car_res = s.read("1").unwrap(); - let let_ = s - .read( - "(let ((x (cons 1 2))) - (cons (car x) (cdr x)))", - ) - .unwrap(); - let let_res = s.read("(1 . 2)").unwrap(); - let lam0 = s.read("((lambda () 1))").unwrap(); - let lam0_res = s.read("1").unwrap(); - let lam = s.read("((lambda (x y) (+ x y)) 3 4)").unwrap(); - let lam_res = s.read("7").unwrap(); - let fold = s - .read( - "(letrec ((build (lambda (x) - (if (eq x 0) - nil - (cons x (build (- x 1)))))) - (sum (lambda (xs) - (if (eq xs nil) - 0 - (+ (car xs) (sum (cdr xs))))))) - (sum (build 10)))", - ) - .unwrap(); - let fold_res = s.read("55").unwrap(); + let state = State::init_lurk_state().rccell(); + let mut read = |code: &str| s.read(state.clone(), code).unwrap(); + let div = read("(/ 70u64 8u64)"); + let div_res = read("8u64"); + let rem = read("(% 70u64 8u64)"); + let rem_res = read("6u64"); + let u64_1 = read("(u64 100000000)"); + let u64_1_res = read("100000000u64"); + let u64_2 = read("(u64 1000000000000000000000000)"); + let u64_2_res = read("2003764205206896640u64"); + let mul_overflow = read("(* 1000000000000u64 100000000000000u64)"); + let mul_overflow_res = read("15908979783594147840u64"); + let char_conv = read("(char 97)"); + let char_conv_res = read("'a'"); + let char_overflow = read("(char 4294967393)"); + let char_overflow_res = read("'a'"); + let t = read("t"); + let nil = read("nil"); + let le1 = read("(<= 4 8)"); + let le2 = read("(<= 8 8)"); + let le3 = read("(<= 10 8)"); + let gt1 = read("(> 4 8)"); + let gt2 = read("(> 8 8)"); + let gt3 = read("(> 10 8)"); + let ltz = read("(< (- 0 10) 0)"); + let sum = read("(+ 21 21)"); + let sum_res = read("42"); + let car = read("(car (cons 1 2))"); + let car_res = read("1"); + let let_ = read( + "(let ((x (cons 1 2))) + (cons (car x) (cdr x)))", + ); + let let_res = read("(1 . 2)"); + let lam0 = read("((lambda () 1))"); + let lam0_res = read("1"); + let lam = read("((lambda (x y) (+ x y)) 3 4)"); + let lam_res = read("7"); + let fold = read( + "(letrec ((build (lambda (x) + (if (eq x 0) + nil + (cons x (build (- x 1)))))) + (sum (lambda (xs) + (if (eq xs nil) + 0 + (+ (car xs) (sum (cdr xs))))))) + (sum (build 4)))", + ); + let fold_res = read("10"); vec![ + (div, div_res), + (rem, rem_res), + (u64_1, u64_1_res), + (u64_2, u64_2_res), + (mul_overflow, mul_overflow_res), + (char_conv, char_conv_res), + (char_overflow, char_overflow_res), + (le1, t), + (le2, t), + (le3, nil), + (gt1, nil), + (gt2, nil), + (gt3, t), + (ltz, t), (sum, sum_res), (car, car_res), (let_, let_res), @@ -901,9 +1313,10 @@ mod tests { #[test] fn test_pairs() { - let mut store = Store::default(); - let pairs = expr_in_expr_out_pairs(&mut store); + let step_fn = eval_step(); + let store = &mut step_fn.init_store(); + let pairs = expr_in_expr_out_pairs(store); store.hydrate_z_cache(); - test_eval_and_constrain_aux(&mut store, pairs); + test_eval_and_constrain_aux(step_fn, store, pairs); } } diff --git a/src/lem/interpreter.rs b/src/lem/interpreter.rs index 8cc7a9dea7..5814e3d495 100644 --- a/src/lem/interpreter.rs +++ b/src/lem/interpreter.rs @@ -1,36 +1,104 @@ -use crate::field::{FWrap, LurkField}; use anyhow::{bail, Result}; use std::collections::VecDeque; use super::{ - path::Path, pointers::Ptr, store::Store, var_map::VarMap, Block, Ctrl, Func, Lit, Op, Tag, + path::Path, pointers::Ptr, store::Store, var_map::VarMap, Block, Ctrl, Func, Op, Tag, Var, }; -use crate::tag::ExprTag::*; +use crate::{ + field::LurkField, + num::Num as BaseNum, + state::initial_lurk_state, + tag::ExprTag::{Comm, Nil, Num, Sym}, +}; + +#[derive(Clone, Debug)] +pub enum PreimageData { + PtrVec(Vec>), + FPtr(F, Ptr), + FPair(F, F), +} + +pub enum Val { + Pointer(Ptr), + Boolean(bool), +} + +impl VarMap> { + fn get_many_ptr(&self, args: &[Var]) -> Result>> { + args.iter().map(|arg| self.get_ptr(arg)).collect() + } + + fn get_ptr(&self, var: &Var) -> Result> { + if let Val::Pointer(ptr) = self.get(var)? { + return Ok(*ptr); + } + bail!("Expected {var} to be a pointer") + } + + fn insert_ptr(&mut self, var: Var, ptr: Ptr) -> Option> { + self.insert(var, Val::Pointer(ptr)) + } -#[derive(Clone, Default)] + fn get_bool(&self, var: &Var) -> Result { + if let Val::Boolean(b) = self.get(var)? { + return Ok(*b); + } + bail!("Expected {var} to be a boolean") + } + + fn insert_bool(&mut self, var: Var, b: bool) -> Option> { + self.insert(var, Val::Boolean(b)) + } +} + +#[derive(Clone, Debug, Default)] /// `Preimages` hold the non-deterministic advices for hashes and `Func` calls. /// The hash preimages must have the same shape as the allocated slots for the /// `Func`, and the `None` values are used to fill the unused slots, which are /// later filled by dummy values. pub struct Preimages { - pub hash2_ptrs: Vec>>>, - pub hash3_ptrs: Vec>>>, - pub hash4_ptrs: Vec>>>, + pub hash4: Vec>>, + pub hash6: Vec>>, + pub hash8: Vec>>, + pub commitment: Vec>>, + pub less_than: Vec>>, pub call_outputs: VecDeque>>, } impl Preimages { pub fn new_from_func(func: &Func) -> Preimages { let slot = func.slot; - let hash2_ptrs = Vec::with_capacity(slot.hash2); - let hash3_ptrs = Vec::with_capacity(slot.hash3); - let hash4_ptrs = Vec::with_capacity(slot.hash4); + let hash4 = Vec::with_capacity(slot.hash4); + let hash6 = Vec::with_capacity(slot.hash6); + let hash8 = Vec::with_capacity(slot.hash8); + let commitment = Vec::with_capacity(slot.commitment); + let less_than = Vec::with_capacity(slot.less_than); let call_outputs = VecDeque::new(); Preimages { - hash2_ptrs, - hash3_ptrs, - hash4_ptrs, + hash4, + hash6, + hash8, + commitment, + less_than, + call_outputs, + } + } + + pub fn blank(func: &Func) -> Preimages { + let slot = func.slot; + let hash4 = vec![None; slot.hash4]; + let hash6 = vec![None; slot.hash6]; + let hash8 = vec![None; slot.hash8]; + let commitment = vec![None; slot.commitment]; + let less_than = vec![None; slot.less_than]; + let call_outputs = VecDeque::new(); + Preimages { + hash4, + hash6, + hash8, + commitment, + less_than, call_outputs, } } @@ -41,11 +109,26 @@ impl Preimages { /// running one iteration as a HashMap of variables to pointers. /// /// This information is used to generate the witness. -#[derive(Clone)] +#[derive(Clone, Debug, Default)] pub struct Frame { pub input: Vec>, pub output: Vec>, pub preimages: Preimages, + pub blank: bool, +} + +impl Frame { + pub fn blank(func: &Func) -> Frame { + let input = vec![Ptr::null(Tag::Expr(Nil)); func.input_params.len()]; + let output = vec![Ptr::null(Tag::Expr(Nil)); func.output_size]; + let preimages = Preimages::blank(func); + Frame { + input, + output, + preimages, + blank: true, + } + } } impl Block { @@ -54,17 +137,18 @@ impl Block { /// in `circuit.rs`) fn run( &self, - input: Vec>, + input: &[Ptr], store: &mut Store, - mut bindings: VarMap>, + mut bindings: VarMap>, mut preimages: Preimages, mut path: Path, + emitted: &mut Vec>, ) -> Result<(Frame, Path)> { for op in &self.ops { match op { Op::Call(out, func, inp) => { // Get the argument values - let inp_ptrs = bindings.get_many_cloned(inp)?; + let inp_ptrs = bindings.get_many_ptr(inp)?; // To save lexical order of `call_outputs` we need to push the output // of the call *before* the inner calls of the `func`. To do this, we @@ -72,13 +156,13 @@ impl Block { // of it, then extend `call_outputs` let mut inner_call_outputs = VecDeque::new(); std::mem::swap(&mut inner_call_outputs, &mut preimages.call_outputs); - let (mut frame, func_path) = func.call(inp_ptrs, store, preimages)?; + let (mut frame, func_path) = func.call(&inp_ptrs, store, preimages, emitted)?; std::mem::swap(&mut inner_call_outputs, &mut frame.preimages.call_outputs); // Extend the path and bind the output variables to the output values path.extend_from_path(&func_path); for (var, ptr) in out.iter().zip(frame.output.iter()) { - bindings.insert(var.clone(), *ptr); + bindings.insert_ptr(var.clone(), *ptr); } // Update `preimages` correctly @@ -87,79 +171,153 @@ impl Block { preimages.call_outputs.extend(inner_call_outputs); } Op::Null(tgt, tag) => { - bindings.insert(tgt.clone(), Ptr::null(*tag)); + bindings.insert_ptr(tgt.clone(), Ptr::null(*tag)); } Op::Lit(tgt, lit) => { - bindings.insert(tgt.clone(), lit.to_ptr(store)); + bindings.insert_ptr(tgt.clone(), lit.to_ptr(store)); } Op::Cast(tgt, tag, src) => { - let src_ptr = bindings.get(src)?; + let src_ptr = bindings.get_ptr(src)?; let tgt_ptr = src_ptr.cast(*tag); - bindings.insert(tgt.clone(), tgt_ptr); + bindings.insert_ptr(tgt.clone(), tgt_ptr); + } + Op::EqTag(tgt, a, b) => { + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + let c = a.tag() == b.tag(); + bindings.insert_bool(tgt.clone(), c); + } + Op::EqVal(tgt, a, b) => { + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + // In order to compare Ptrs, we *must* resolve the hashes. Otherwise, we risk failing to recognize equality of + // compound data with opaque data in either element's transitive closure. + let c = store.hash_ptr(&a)?.value() == store.hash_ptr(&b)?.value(); + bindings.insert_bool(tgt.clone(), c); + } + Op::Not(tgt, a) => { + let a = bindings.get_bool(a)?; + bindings.insert_bool(tgt.clone(), !a); + } + Op::And(tgt, a, b) => { + let a = bindings.get_bool(a)?; + let b = bindings.get_bool(b)?; + bindings.insert_bool(tgt.clone(), a && b); + } + Op::Or(tgt, a, b) => { + let a = bindings.get_bool(a)?; + let b = bindings.get_bool(b)?; + bindings.insert_bool(tgt.clone(), a || b); } Op::Add(tgt, a, b) => { - let a = bindings.get(a)?; - let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f + *g) - } - _ => bail!("Addition only works on numbers"), + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + Ptr::Atom(Tag::Expr(Num), f + g) + } else { + bail!("`Add` only works on atoms") }; - bindings.insert(tgt.clone(), c); + bindings.insert_ptr(tgt.clone(), c); } Op::Sub(tgt, a, b) => { - let a = bindings.get(a)?; - let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f - *g) - } - _ => bail!("Addition only works on numbers"), + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + Ptr::Atom(Tag::Expr(Num), f - g) + } else { + bail!("`Sub` only works on atoms") }; - bindings.insert(tgt.clone(), c); + bindings.insert_ptr(tgt.clone(), c); } Op::Mul(tgt, a, b) => { - let a = bindings.get(a)?; - let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f * *g) - } - _ => bail!("Addition only works on numbers"), + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + Ptr::Atom(Tag::Expr(Num), f * g) + } else { + bail!("`Mul` only works on atoms") }; - bindings.insert(tgt.clone(), c); + bindings.insert_ptr(tgt.clone(), c); } Op::Div(tgt, a, b) => { - let a = bindings.get(a)?; - let b = bindings.get(b)?; - let c = match (a, b) { - (Ptr::Leaf(Tag::Expr(Num), f), Ptr::Leaf(Tag::Expr(Num), g)) => { - Ptr::Leaf(Tag::Expr(Num), *f * g.invert().unwrap()) + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + if g == F::ZERO { + bail!("Can't divide by zero") } - _ => bail!("Division only works on numbers"), + Ptr::Atom(Tag::Expr(Num), f * g.invert().expect("not zero")) + } else { + bail!("`Div` only works on numbers") }; - bindings.insert(tgt.clone(), c); + bindings.insert_ptr(tgt.clone(), c); + } + Op::Lt(tgt, a, b) => { + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + let c = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + preimages.less_than.push(Some(PreimageData::FPair(f, g))); + let f = BaseNum::Scalar(f); + let g = BaseNum::Scalar(g); + f < g + } else { + bail!("`Lt` only works on atoms") + }; + bindings.insert_bool(tgt.clone(), c); + } + Op::Trunc(tgt, a, n) => { + assert!(*n <= 64); + let a = bindings.get_ptr(a)?; + let c = if let Ptr::Atom(_, f) = a { + let b = if *n < 64 { (1 << *n) - 1 } else { u64::MAX }; + Ptr::Atom(Tag::Expr(Num), F::from_u64(f.to_u64_unchecked() & b)) + } else { + bail!("`Trunc` only works a leaf") + }; + bindings.insert_ptr(tgt.clone(), c); + } + Op::DivRem64(tgt, a, b) => { + let a = bindings.get_ptr(a)?; + let b = bindings.get_ptr(b)?; + let (c1, c2) = if let (Ptr::Atom(_, f), Ptr::Atom(_, g)) = (a, b) { + if g == F::ZERO { + bail!("Can't divide by zero") + } + let f = f.to_u64_unchecked(); + let g = g.to_u64_unchecked(); + let c1 = Ptr::Atom(Tag::Expr(Num), F::from_u64(f / g)); + let c2 = Ptr::Atom(Tag::Expr(Num), F::from_u64(f % g)); + (c1, c2) + } else { + bail!("`DivRem64` only works on atoms") + }; + bindings.insert_ptr(tgt[0].clone(), c1); + bindings.insert_ptr(tgt[1].clone(), c2); } Op::Emit(a) => { - let a = bindings.get(a)?; - println!("{}", a.to_string(store)) + let a = bindings.get_ptr(a)?; + println!("{}", a.fmt_to_string(store, initial_lurk_state())); + emitted.push(a); } - Op::Hash2(img, tag, preimg) => { - let preimg_ptrs = bindings.get_many_cloned(preimg)?; + Op::Cons2(img, tag, preimg) => { + let preimg_ptrs = bindings.get_many_ptr(preimg)?; let tgt_ptr = store.intern_2_ptrs(*tag, preimg_ptrs[0], preimg_ptrs[1]); - bindings.insert(img.clone(), tgt_ptr); - preimages.hash2_ptrs.push(Some(preimg_ptrs)); + bindings.insert_ptr(img.clone(), tgt_ptr); + preimages + .hash4 + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } - Op::Hash3(img, tag, preimg) => { - let preimg_ptrs = bindings.get_many_cloned(preimg)?; + Op::Cons3(img, tag, preimg) => { + let preimg_ptrs = bindings.get_many_ptr(preimg)?; let tgt_ptr = store.intern_3_ptrs(*tag, preimg_ptrs[0], preimg_ptrs[1], preimg_ptrs[2]); - bindings.insert(img.clone(), tgt_ptr); - preimages.hash3_ptrs.push(Some(preimg_ptrs)); + bindings.insert_ptr(img.clone(), tgt_ptr); + preimages + .hash6 + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } - Op::Hash4(img, tag, preimg) => { - let preimg_ptrs = bindings.get_many_cloned(preimg)?; + Op::Cons4(img, tag, preimg) => { + let preimg_ptrs = bindings.get_many_ptr(preimg)?; let tgt_ptr = store.intern_4_ptrs( *tag, preimg_ptrs[0], @@ -167,11 +325,13 @@ impl Block { preimg_ptrs[2], preimg_ptrs[3], ); - bindings.insert(img.clone(), tgt_ptr); - preimages.hash4_ptrs.push(Some(preimg_ptrs)); + bindings.insert_ptr(img.clone(), tgt_ptr); + preimages + .hash8 + .push(Some(PreimageData::PtrVec(preimg_ptrs))); } - Op::Unhash2(preimg, img) => { - let img_ptr = bindings.get(img)?; + Op::Decons2(preimg, img) => { + let img_ptr = bindings.get_ptr(img)?; let Some(idx) = img_ptr.get_index2() else { bail!("{img} isn't a Tree2 pointer"); }; @@ -180,12 +340,14 @@ impl Block { }; let preimg_ptrs = [*a, *b]; for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { - bindings.insert(var.clone(), *ptr); + bindings.insert_ptr(var.clone(), *ptr); } - preimages.hash2_ptrs.push(Some(preimg_ptrs.to_vec())); + preimages + .hash4 + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } - Op::Unhash3(preimg, img) => { - let img_ptr = bindings.get(img)?; + Op::Decons3(preimg, img) => { + let img_ptr = bindings.get_ptr(img)?; let Some(idx) = img_ptr.get_index3() else { bail!("{img} isn't a Tree3 pointer"); }; @@ -194,12 +356,14 @@ impl Block { }; let preimg_ptrs = [*a, *b, *c]; for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { - bindings.insert(var.clone(), *ptr); + bindings.insert_ptr(var.clone(), *ptr); } - preimages.hash3_ptrs.push(Some(preimg_ptrs.to_vec())); + preimages + .hash6 + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } - Op::Unhash4(preimg, img) => { - let img_ptr = bindings.get(img)?; + Op::Decons4(preimg, img) => { + let img_ptr = bindings.get_ptr(img)?; let Some(idx) = img_ptr.get_index4() else { bail!("{img} isn't a Tree4 pointer"); }; @@ -208,102 +372,93 @@ impl Block { }; let preimg_ptrs = [*a, *b, *c, *d]; for (var, ptr) in preimg.iter().zip(preimg_ptrs.iter()) { - bindings.insert(var.clone(), *ptr); + bindings.insert_ptr(var.clone(), *ptr); } - preimages.hash4_ptrs.push(Some(preimg_ptrs.to_vec())); + preimages + .hash8 + .push(Some(PreimageData::PtrVec(preimg_ptrs.to_vec()))); } Op::Hide(tgt, sec, src) => { - let src_ptr = bindings.get(src)?; - let Ptr::Leaf(Tag::Expr(Num), secret) = bindings.get(sec)? else { + let src_ptr = bindings.get_ptr(src)?; + let Ptr::Atom(Tag::Expr(Num), secret) = bindings.get_ptr(sec)? else { bail!("{sec} is not a numeric pointer") }; - let z_ptr = store.hash_ptr(src_ptr)?; - let hash = - store - .poseidon_cache - .hash3(&[*secret, z_ptr.tag.to_field(), z_ptr.hash]); - let tgt_ptr = Ptr::comm(hash); - store.comms.insert(FWrap::(hash), (*secret, *src_ptr)); - bindings.insert(tgt.clone(), tgt_ptr); + let tgt_ptr = store.hide(secret, src_ptr)?; + preimages + .commitment + .push(Some(PreimageData::FPtr(secret, src_ptr))); + bindings.insert_ptr(tgt.clone(), tgt_ptr); + } + Op::Open(tgt_secret, tgt_ptr, comm) => { + let Ptr::Atom(Tag::Expr(Comm), hash) = bindings.get_ptr(comm)? else { + bail!("{comm} is not a comm pointer") + }; + let Some((secret, ptr)) = store.open(hash) else { + bail!("No committed data for hash {}", &hash.hex_digits()) + }; + bindings.insert_ptr(tgt_ptr.clone(), *ptr); + bindings.insert_ptr(tgt_secret.clone(), Ptr::Atom(Tag::Expr(Num), *secret)); + preimages + .commitment + .push(Some(PreimageData::FPtr(*secret, *ptr))) } - Op::Open(tgt_secret, tgt_ptr, comm_or_num) => match bindings.get(comm_or_num)? { - Ptr::Leaf(Tag::Expr(Num), hash) | Ptr::Leaf(Tag::Expr(Comm), hash) => { - let Some((secret, ptr)) = store.comms.get(&FWrap::(*hash)) else { - bail!("No committed data for hash {}", &hash.hex_digits()) - }; - bindings.insert(tgt_ptr.clone(), *ptr); - bindings.insert(tgt_secret.clone(), Ptr::Leaf(Tag::Expr(Num), *secret)); - } - _ => { - bail!("{comm_or_num} is not a num/comm pointer") - } - }, } } match &self.ctrl { Ctrl::MatchTag(match_var, cases, def) => { - let ptr = bindings.get(match_var)?; + let ptr = bindings.get_ptr(match_var)?; let tag = ptr.tag(); - match cases.get(tag) { - Some(block) => { - path.push_tag_inplace(tag); - block.run(input, store, bindings, preimages, path) - } - None => { - path.push_default_inplace(); - match def { - Some(def) => def.run(input, store, bindings, preimages, path), - None => bail!("No match for tag {}", tag), - } - } + if let Some(block) = cases.get(tag) { + path.push_tag_inplace(*tag); + block.run(input, store, bindings, preimages, path, emitted) + } else { + path.push_default_inplace(); + let Some(def) = def else { + bail!("No match for tag {}", tag) + }; + def.run(input, store, bindings, preimages, path, emitted) } } - Ctrl::MatchVal(match_var, cases, def) => { - let ptr = bindings.get(match_var)?; - let Some(lit) = Lit::from_ptr(ptr, store) else { - // If we can't find it in the store, it most certaily is not equal to any - // of the cases, which are all interned - path.push_default_inplace(); - match def { - Some(def) => return def.run(input, store, bindings, preimages, path), - None => bail!("No match for literal"), - } + Ctrl::MatchSymbol(match_var, cases, def) => { + let ptr = bindings.get_ptr(match_var)?; + if ptr.tag() != &Tag::Expr(Sym) { + bail!("{match_var} is not a symbol"); + } + let Some(sym) = store.fetch_symbol(&ptr) else { + bail!("Symbol bound to {match_var} wasn't interned"); }; - match cases.get(&lit) { - Some(block) => { - path.push_lit_inplace(&lit); - block.run(input, store, bindings, preimages, path) - } - None => { - path.push_default_inplace(); - match def { - Some(def) => def.run(input, store, bindings, preimages, path), - None => bail!("No match for literal {:?}", lit), - } - } + if let Some(block) = cases.get(&sym) { + path.push_symbol_inplace(sym); + block.run(input, store, bindings, preimages, path, emitted) + } else { + path.push_default_inplace(); + let Some(def) = def else { + bail!("No match for symbol {sym}") + }; + def.run(input, store, bindings, preimages, path, emitted) } } - Ctrl::IfEq(x, y, eq_block, else_block) => { - let x = bindings.get(x)?; - let y = bindings.get(y)?; - let b = x == y; + Ctrl::If(b, true_block, false_block) => { + let b = bindings.get_bool(b)?; path.push_bool_inplace(b); if b { - eq_block.run(input, store, bindings, preimages, path) + true_block.run(input, store, bindings, preimages, path, emitted) } else { - else_block.run(input, store, bindings, preimages, path) + false_block.run(input, store, bindings, preimages, path, emitted) } } Ctrl::Return(output_vars) => { let mut output = Vec::with_capacity(output_vars.len()); for var in output_vars.iter() { - output.push(*bindings.get(var)?) + output.push(bindings.get_ptr(var)?) } + let input = input.to_vec(); Ok(( Frame { input, output, preimages, + blank: false, }, path, )) @@ -315,37 +470,49 @@ impl Block { impl Func { pub fn call( &self, - args: Vec>, + args: &[Ptr], store: &mut Store, preimages: Preimages, + emitted: &mut Vec>, ) -> Result<(Frame, Path)> { let mut bindings = VarMap::new(); for (i, param) in self.input_params.iter().enumerate() { - bindings.insert(param.clone(), args[i]); + bindings.insert_ptr(param.clone(), args[i]); } // We must fill any unused slots with `None` values so we save // the initial size of preimages, which might not be zero - let hash2_init = preimages.hash2_ptrs.len(); - let hash3_init = preimages.hash3_ptrs.len(); - let hash4_init = preimages.hash4_ptrs.len(); + let hash4_init = preimages.hash4.len(); + let hash6_init = preimages.hash6.len(); + let hash8_init = preimages.hash8.len(); + let commitment_init = preimages.commitment.len(); + let less_than_init = preimages.less_than.len(); let mut res = self .body - .run(args, store, bindings, preimages, Path::default())?; + .run(args, store, bindings, preimages, Path::default(), emitted)?; let preimages = &mut res.0.preimages; - let hash2_used = preimages.hash2_ptrs.len() - hash2_init; - let hash3_used = preimages.hash3_ptrs.len() - hash3_init; - let hash4_used = preimages.hash4_ptrs.len() - hash4_init; - for _ in hash2_used..self.slot.hash2 { - preimages.hash2_ptrs.push(None); + let hash4_used = preimages.hash4.len() - hash4_init; + let hash6_used = preimages.hash6.len() - hash6_init; + let hash8_used = preimages.hash8.len() - hash8_init; + let commitment_used = preimages.commitment.len() - commitment_init; + let less_than_used = preimages.less_than.len() - less_than_init; + + for _ in hash4_used..self.slot.hash4 { + preimages.hash4.push(None); } - for _ in hash3_used..self.slot.hash3 { - preimages.hash3_ptrs.push(None); + for _ in hash6_used..self.slot.hash6 { + preimages.hash6.push(None); } - for _ in hash4_used..self.slot.hash4 { - preimages.hash4_ptrs.push(None); + for _ in hash8_used..self.slot.hash8 { + preimages.hash8.push(None); + } + for _ in commitment_used..self.slot.commitment { + preimages.commitment.push(None); + } + for _ in less_than_used..self.slot.less_than { + preimages.less_than.push(None); } Ok(res) @@ -353,39 +520,80 @@ impl Func { /// Calls a `Func` on an input until the stop contidion is satisfied, using the output of one /// iteration as the input of the next one. - pub fn call_until]) -> bool>( + pub fn call_until< + F: LurkField, + StopCond: Fn(&[Ptr]) -> bool, + // iteration -> input -> emitted -> store -> string + LogFmt: Fn(usize, &[Ptr], &[Ptr], &Store) -> String, + >( &self, - mut args: Vec>, + args: &[Ptr], store: &mut Store, - stop_cond: Stop, - ) -> Result<(Vec>, Vec)> { - if self.input_params.len() != self.output_size { - assert_eq!(self.input_params.len(), self.output_size) - } - if self.input_params.len() != args.len() { - assert_eq!(args.len(), self.input_params.len()) - } + stop_cond: StopCond, + limit: usize, + // TODO: make this argument optional + log_fmt: LogFmt, + ) -> Result<(Vec>, usize, Vec)> { + assert_eq!(self.input_params.len(), self.output_size); + assert_eq!(self.input_params.len(), args.len()); - // Initial path vector and frames + // Initial input, path vector and frames + let mut input = args.to_vec(); let mut frames = vec![]; let mut paths = vec![]; - loop { + let mut iterations = 0; + + tracing::info!("{}", &log_fmt(iterations, &input, &[], store)); + + for _ in 0..limit { let preimages = Preimages::new_from_func(self); - let (frame, path) = self.call(args, store, preimages)?; + let mut emitted = vec![]; + let (frame, path) = self.call(&input, store, preimages, &mut emitted)?; + input = frame.output.clone(); + iterations += 1; + tracing::info!("{}", &log_fmt(iterations, &input, &emitted, store)); if stop_cond(&frame.output) { frames.push(frame); paths.push(path); break; } - // Should frames take borrowed vectors instead, as to avoid cloning? - // Using AVec is a possibility, but to create a dynamic AVec, currently, - // requires 2 allocations since it must be created from a Vec and - // Vec -> Arc<[T]> uses `copy_from_slice`. - args = frame.output.clone(); frames.push(frame); paths.push(path); } - Ok((frames, paths)) + if iterations < limit { + // pushing a frame that can be padded + let preimages = Preimages::new_from_func(self); + let (frame, path) = self.call(&input, store, preimages, &mut vec![])?; + frames.push(frame); + paths.push(path); + } + Ok((frames, iterations, paths)) + } + + pub fn call_until_simple]) -> bool>( + &self, + args: Vec>, + store: &mut Store, + stop_cond: StopCond, + limit: usize, + ) -> Result<(Vec>, usize, Vec>)> { + assert_eq!(self.input_params.len(), self.output_size); + assert_eq!(self.input_params.len(), args.len()); + + let mut input = args; + let mut emitted = vec![]; + + let mut iterations = 0; + + for _ in 0..limit { + let (frame, _) = self.call(&input, store, Preimages::default(), &mut emitted)?; + input = frame.output.clone(); + iterations += 1; + if stop_cond(&frame.output) { + break; + } + } + Ok((input, iterations, emitted)) } } diff --git a/src/lem/macros.rs b/src/lem/macros.rs index bd3b62f93a..72265e4b65 100644 --- a/src/lem/macros.rs +++ b/src/lem/macros.rs @@ -23,7 +23,7 @@ macro_rules! lit { $crate::lem::Lit::String($lit.into()) }; ( Symbol($lit:literal) ) => { - $crate::lem::Lit::Symbol($crate::lem::Symbol::lurk_sym(&$lit)) + $crate::lem::Lit::Symbol($crate::state::lurk_sym(&$lit)) }; } @@ -35,8 +35,11 @@ macro_rules! tag { ( Cont::$tag:ident ) => { $crate::lem::Tag::Cont($crate::tag::ContTag::$tag) }; - ( Ctrl::$tag:ident ) => { - $crate::lem::Tag::Ctrl($crate::lem::CtrlTag::$tag) + ( Op1::$tag:ident ) => { + $crate::lem::Tag::Op1($crate::tag::Op1::$tag) + }; + ( Op2::$tag:ident ) => { + $crate::lem::Tag::Op2($crate::tag::Op2::$tag) }; } @@ -58,6 +61,40 @@ macro_rules! op { $crate::var!($src), ) }; + ( let $tgt:ident = eq_tag($a:ident, $b:ident) ) => { + $crate::lem::Op::EqTag( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; + ( let $tgt:ident = eq_val($a:ident, $b:ident) ) => { + $crate::lem::Op::EqVal( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; + ( let $tgt:ident = not($a:ident) ) => { + $crate::lem::Op::Not( + $crate::var!($tgt), + $crate::var!($a), + ) + }; + ( let $tgt:ident = and($a:ident, $b:ident) ) => { + $crate::lem::Op::And( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; + ( let $tgt:ident = or($a:ident, $b:ident) ) => { + $crate::lem::Op::Or( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; ( let $tgt:ident = add($a:ident, $b:ident) ) => { $crate::lem::Op::Add( $crate::var!($tgt), @@ -86,41 +123,62 @@ macro_rules! op { $crate::var!($b), ) }; + ( let $tgt:ident = lt($a:ident, $b:ident) ) => { + $crate::lem::Op::Lt( + $crate::var!($tgt), + $crate::var!($a), + $crate::var!($b), + ) + }; + ( let $tgt:ident = truncate($a:ident, $b:literal) ) => { + $crate::lem::Op::Trunc( + $crate::var!($tgt), + $crate::var!($a), + $b, + ) + }; + ( let ($tgt1:ident, $tgt2:ident) = div_rem64($a:ident, $b:ident) ) => { + $crate::lem::Op::DivRem64( + $crate::vars!($tgt1, $tgt2), + $crate::var!($a), + $crate::var!($b), + ) + }; ( emit($v:ident) ) => { $crate::lem::Op::Emit($crate::var!($v)) }; - ( let $tgt:ident : $kind:ident::$tag:ident = hash2($src1:ident, $src2:ident) ) => { - $crate::lem::Op::Hash2( + ( let $tgt:ident : $kind:ident::$tag:ident = cons2($src1:ident, $src2:ident) ) => { + $crate::lem::Op::Cons2( $crate::var!($tgt), $crate::tag!($kind::$tag), $crate::vars!($src1, $src2), ) }; - ( let $tgt:ident : $kind:ident::$tag:ident = hash3($src1:ident, $src2:ident, $src3:ident) ) => { - $crate::lem::Op::Hash3( + ( let $tgt:ident : $kind:ident::$tag:ident = cons3($src1:ident, $src2:ident, $src3:ident) ) => { + $crate::lem::Op::Cons3( $crate::var!($tgt), $crate::tag!($kind::$tag), $crate::vars!($src1, $src2, $src3), ) }; - ( let $tgt:ident : $kind:ident::$tag:ident = hash4($src1:ident, $src2:ident, $src3:ident, $src4:ident) ) => { - $crate::lem::Op::Hash4( + ( let $tgt:ident : $kind:ident::$tag:ident = cons4($src1:ident, $src2:ident, $src3:ident, $src4:ident) ) => { + $crate::lem::Op::Cons4( $crate::var!($tgt), $crate::tag!($kind::$tag), $crate::vars!($src1, $src2, $src3, $src4), ) }; - ( let ($tgt1:ident, $tgt2:ident) = unhash2($src:ident) ) => { - $crate::lem::Op::Unhash2( + ( let ($tgt1:ident, $tgt2:ident) = decons2($src:ident) ) => { + $crate::lem::Op::Decons2( $crate::vars!($tgt1, $tgt2), $crate::var!($src), ) }; - ( let ($tgt1:ident, $tgt2:ident, $tgt3:ident) = unhash3($src:ident) ) => { - $crate::lem::Op::Unhash3($crate::vars!($tgt1, $tgt2, $tgt3), $crate::var!($src)) + ( let ($tgt1:ident, $tgt2:ident, $tgt3:ident) = decons3($src:ident) ) => { + $crate::lem::Op::Decons3($crate::vars!($tgt1, $tgt2, $tgt3), $crate::var!($src)) }; - ( let ($tgt1:ident, $tgt2:ident, $tgt3:ident, $tgt4:ident) = unhash4($src:ident) ) => { - $crate::lem::Op::Unhash4( + ( let ($tgt1:ident, $tgt2:ident, $tgt3:ident, $tgt4:ident) = decons4($src:ident) ) => { + $crate::lem::Op::Decons4( $crate::vars!($tgt1, $tgt2, $tgt3, $tgt4), $crate::var!($src), ) @@ -166,19 +224,19 @@ macro_rules! ctrl { $crate::lem::Ctrl::MatchTag($crate::var!($sii), cases, default) } }; - ( match $sii:ident.val { $( $cnstr:ident($val:literal) $(| $other_cnstr:ident($other_val:literal))* => $case_ops:tt )* } $(; $($def:tt)*)? ) => { + ( match symbol $sii:ident { $( $sym:expr $(, $other_sym:expr)* => $case_ops:tt )* } $(; $($def:tt)*)? ) => { { let mut cases = indexmap::IndexMap::new(); $( if cases.insert( - $crate::lit!($cnstr($val)), + $crate::state::lurk_sym($sym), $crate::block!( $case_ops ), ).is_some() { panic!("Repeated value on `match`"); }; $( if cases.insert( - $crate::lit!($other_cnstr($other_val)), + $crate::state::lurk_sym($other_sym), $crate::block!( $case_ops ), ).is_some() { panic!("Repeated value on `match`"); @@ -186,25 +244,23 @@ macro_rules! ctrl { )* )* let default = None $( .or (Some(Box::new($crate::block!( @seq {}, $($def)* )))) )?; - $crate::lem::Ctrl::MatchVal($crate::var!($sii), cases, default) + $crate::lem::Ctrl::MatchSymbol($crate::var!($sii), cases, default) } }; - ( if $x:ident == $y:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { + ( if $x:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { { let x = $crate::var!($x); - let y = $crate::var!($y); let true_block = Box::new($crate::block!( @seq {}, $($true_block)+ )); let false_block = Box::new($crate::block!( @seq {}, $($false_block)+ )); - $crate::lem::Ctrl::IfEq(x, y, true_block, false_block) + $crate::lem::Ctrl::If(x, true_block, false_block) } }; - ( if $x:ident != $y:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { + ( if !$x:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { { let x = $crate::var!($x); - let y = $crate::var!($y); let true_block = Box::new($crate::block!( @seq {}, $($true_block)+ )); let false_block = Box::new($crate::block!( @seq {}, $($false_block)+ )); - $crate::lem::Ctrl::IfEq(x, y, false_block, true_block) + $crate::lem::Ctrl::If(x, false_block, true_block) } }; ( return ($($src:ident),*) ) => { @@ -243,6 +299,56 @@ macro_rules! block { $($tail)* ) }; + (@seq {$($limbs:expr)*}, let $tgt:ident = eq_tag($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = eq_tag($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let $tgt:ident = eq_val($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = eq_val($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let $tgt:ident = not($a:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = not($a)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let $tgt:ident = and($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = and($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let $tgt:ident = or($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = or($a, $b)) + }, + $($tail)* + ) + }; (@seq {$($limbs:expr)*}, let $tgt:ident = add($a:ident, $b:ident) ; $($tail:tt)*) => { $crate::block! ( @seq @@ -283,6 +389,36 @@ macro_rules! block { $($tail)* ) }; + (@seq {$($limbs:expr)*}, let $tgt:ident = lt($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = lt($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let $tgt:ident = truncate($a:ident, $b:literal) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let $tgt = truncate($a, $b)) + }, + $($tail)* + ) + }; + (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident) = div_rem64($a:ident, $b:ident) ; $($tail:tt)*) => { + $crate::block! ( + @seq + { + $($limbs)* + $crate::op!(let ($tgt1, $tgt2) = div_rem64($a, $b)) + }, + $($tail)* + ) + }; (@seq {$($limbs:expr)*}, emit($v:ident) ; $($tail:tt)*) => { $crate::block! ( @seq @@ -323,62 +459,62 @@ macro_rules! block { $($tail)* ) }; - (@seq {$($limbs:expr)*}, let $tgt:ident : $kind:ident::$tag:ident = hash2($src1:ident, $src2:ident) ; $($tail:tt)*) => { + (@seq {$($limbs:expr)*}, let $tgt:ident : $kind:ident::$tag:ident = cons2($src1:ident, $src2:ident) ; $($tail:tt)*) => { $crate::block! ( @seq { $($limbs)* - $crate::op!(let $tgt: $kind::$tag = hash2($src1, $src2) ) + $crate::op!(let $tgt: $kind::$tag = cons2($src1, $src2) ) }, $($tail)* ) }; - (@seq {$($limbs:expr)*}, let $tgt:ident : $kind:ident::$tag:ident = hash3($src1:ident, $src2:ident, $src3:ident) ; $($tail:tt)*) => { + (@seq {$($limbs:expr)*}, let $tgt:ident : $kind:ident::$tag:ident = cons3($src1:ident, $src2:ident, $src3:ident) ; $($tail:tt)*) => { $crate::block! ( @seq { $($limbs)* - $crate::op!(let $tgt: $kind::$tag = hash3($src1, $src2, $src3) ) + $crate::op!(let $tgt: $kind::$tag = cons3($src1, $src2, $src3) ) }, $($tail)* ) }; - (@seq {$($limbs:expr)*}, let $tgt:ident : $kind:ident::$tag:ident = hash4($src1:ident, $src2:ident, $src3:ident, $src4:ident) ; $($tail:tt)*) => { + (@seq {$($limbs:expr)*}, let $tgt:ident : $kind:ident::$tag:ident = cons4($src1:ident, $src2:ident, $src3:ident, $src4:ident) ; $($tail:tt)*) => { $crate::block! ( @seq { $($limbs)* - $crate::op!(let $tgt: $kind::$tag = hash4($src1, $src2, $src3, $src4)) + $crate::op!(let $tgt: $kind::$tag = cons4($src1, $src2, $src3, $src4)) }, $($tail)* ) }; - (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident) = unhash2($src:ident) ; $($tail:tt)*) => { + (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident) = decons2($src:ident) ; $($tail:tt)*) => { $crate::block! ( @seq { $($limbs)* - $crate::op!(let ($tgt1, $tgt2) = unhash2($src) ) + $crate::op!(let ($tgt1, $tgt2) = decons2($src) ) }, $($tail)* ) }; - (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident, $tgt3:ident) = unhash3($src:ident) ; $($tail:tt)*) => { + (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident, $tgt3:ident) = decons3($src:ident) ; $($tail:tt)*) => { $crate::block! ( @seq { $($limbs)* - $crate::op!(let ($tgt1, $tgt2, $tgt3) = unhash3($src) ) + $crate::op!(let ($tgt1, $tgt2, $tgt3) = decons3($src) ) }, $($tail)* ) }; - (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident, $tgt3:ident, $tgt4:ident) = unhash4($src:ident) ; $($tail:tt)*) => { + (@seq {$($limbs:expr)*}, let ($tgt1:ident, $tgt2:ident, $tgt3:ident, $tgt4:ident) = decons4($src:ident) ; $($tail:tt)*) => { $crate::block! ( @seq { $($limbs)* - $crate::op!(let ($tgt1, $tgt2, $tgt3, $tgt4) = unhash4($src) ) + $crate::op!(let ($tgt1, $tgt2, $tgt3, $tgt4) = decons4($src) ) }, $($tail)* ) @@ -423,31 +559,31 @@ macro_rules! block { $crate::ctrl!( match $sii.tag { $( $kind::$tag $(| $other_kind::$other_tag)* => $case_ops )* } $(; $($def)*)? ) ) }; - (@seq {$($limbs:expr)*}, match $sii:ident.val { $( $cnstr:ident($val:literal) $(| $other_cnstr:ident($other_val:literal))* => $case_ops:tt )* } $(; $($def:tt)*)?) => { + (@seq {$($limbs:expr)*}, match symbol $sii:ident { $( $sym:expr $(, $other_sym:expr)* => $case_ops:tt )* } $(; $($def:tt)*)?) => { $crate::block! ( @end { $($limbs)* }, - $crate::ctrl!( match $sii.val { $( $cnstr($val) $(| $other_cnstr($other_val))* => $case_ops )* } $(; $($def)*)? ) + $crate::ctrl!( match symbol $sii { $( $sym $(, $other_sym)* => $case_ops )* } $(; $($def)*)? ) ) }; - (@seq {$($limbs:expr)*}, if $x:ident == $y:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { + (@seq {$($limbs:expr)*}, if $x:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { $crate::block! ( @end { $($limbs)* }, - $crate::ctrl!( if $x == $y { $($true_block)+ } $($false_block)+ ) + $crate::ctrl!( if $x { $($true_block)+ } $($false_block)+ ) ) }; - (@seq {$($limbs:expr)*}, if $x:ident != $y:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { + (@seq {$($limbs:expr)*}, if !$x:ident { $($true_block:tt)+ } $($false_block:tt)+ ) => { $crate::block! ( @end { $($limbs)* }, - $crate::ctrl!( if $x != $y { $($true_block)+ } $($false_block)+ ) + $crate::ctrl!( if !$x { $($true_block)+ } $($false_block)+ ) ) }; (@seq {$($limbs:expr)*}, return ($($src:ident),*) $(;)?) => { @@ -487,9 +623,12 @@ macro_rules! func { #[cfg(test)] mod tests { - use crate::lem::{Block, Ctrl, Lit, Op, Tag, Var}; - use crate::symbol::Symbol; - use crate::tag::ExprTag::*; + use crate::{ + lem::{Block, Ctrl, Op, Tag, Var}, + state::lurk_sym, + tag::ExprTag::{Char, Num, Str}, + Symbol, + }; #[inline] fn mptr(name: &str) -> Var { @@ -502,28 +641,28 @@ mod tests { } #[inline] - fn match_val(i: Var, cases: Vec<(Lit, Block)>, def: Block) -> Ctrl { - Ctrl::MatchVal(i, indexmap::IndexMap::from_iter(cases), Some(Box::new(def))) + fn match_symbol(i: Var, cases: Vec<(Symbol, Block)>, def: Block) -> Ctrl { + Ctrl::MatchSymbol(i, indexmap::IndexMap::from_iter(cases), Some(Box::new(def))) } #[test] fn test_macros() { let lemops = [ Op::Null(mptr("foo"), Tag::Expr(Num)), - Op::Hash2(mptr("foo"), Tag::Expr(Char), [mptr("bar"), mptr("baz")]), - Op::Hash3( + Op::Cons2(mptr("foo"), Tag::Expr(Char), [mptr("bar"), mptr("baz")]), + Op::Cons3( mptr("foo"), Tag::Expr(Char), [mptr("bar"), mptr("baz"), mptr("bazz")], ), - Op::Hash4( + Op::Cons4( mptr("foo"), Tag::Expr(Char), [mptr("bar"), mptr("baz"), mptr("bazz"), mptr("baxx")], ), - Op::Unhash2([mptr("foo"), mptr("goo")], mptr("aaa")), - Op::Unhash3([mptr("foo"), mptr("goo"), mptr("moo")], mptr("aaa")), - Op::Unhash4( + Op::Decons2([mptr("foo"), mptr("goo")], mptr("aaa")), + Op::Decons3([mptr("foo"), mptr("goo"), mptr("moo")], mptr("aaa")), + Op::Decons4( [mptr("foo"), mptr("goo"), mptr("moo"), mptr("noo")], mptr("aaa"), ), @@ -532,12 +671,12 @@ mod tests { ]; let lemops_macro = vec![ op!(let foo: Expr::Num), - op!(let foo: Expr::Char = hash2(bar, baz)), - op!(let foo: Expr::Char = hash3(bar, baz, bazz)), - op!(let foo: Expr::Char = hash4(bar, baz, bazz, baxx)), - op!(let (foo, goo) = unhash2(aaa)), - op!(let (foo, goo, moo) = unhash3(aaa)), - op!(let (foo, goo, moo, noo) = unhash4(aaa)), + op!(let foo: Expr::Char = cons2(bar, baz)), + op!(let foo: Expr::Char = cons3(bar, baz, bazz)), + op!(let foo: Expr::Char = cons4(bar, baz, bazz, baxx)), + op!(let (foo, goo) = decons2(aaa)), + op!(let (foo, goo, moo) = decons3(aaa)), + op!(let (foo, goo, moo, noo) = decons4(aaa)), op!(let bar = hide(baz, bazz)), op!(let (bar, baz) = open(bazz)), ]; @@ -553,12 +692,12 @@ mod tests { }; let lem_macro_seq = block!({ let foo: Expr::Num; - let foo: Expr::Char = hash2(bar, baz); - let foo: Expr::Char = hash3(bar, baz, bazz); - let foo: Expr::Char = hash4(bar, baz, bazz, baxx); - let (foo, goo) = unhash2(aaa); - let (foo, goo, moo) = unhash3(aaa); - let (foo, goo, moo, noo) = unhash4(aaa); + let foo: Expr::Char = cons2(bar, baz); + let foo: Expr::Char = cons3(bar, baz, bazz); + let foo: Expr::Char = cons4(bar, baz, bazz, baxx); + let (foo, goo) = decons2(aaa); + let (foo, goo, moo) = decons3(aaa); + let (foo, goo, moo, noo) = decons4(aaa); let bar = hide(baz, bazz); let (bar, baz) = open(bazz); return (bar, baz, bazz); @@ -613,11 +752,11 @@ mod tests { ); let moo = ctrl!( - match www.val { - Symbol("nil") => { + match symbol www { + "nil" => { return (foo, foo, foo); // a single Ctrl will not turn into a Seq } - Symbol("cons") => { + "cons" => { let foo: Expr::Num; let goo: Expr::Char; return (foo, goo, goo); @@ -628,18 +767,18 @@ mod tests { ); assert!( - moo == match_val( + moo == match_symbol( mptr("www"), vec![ ( - Lit::Symbol(Symbol::lurk_sym("nil")), + lurk_sym("nil"), Block { ops: vec![], ctrl: Ctrl::Return(vec![mptr("foo"), mptr("foo"), mptr("foo")]), } ), ( - Lit::Symbol(Symbol::lurk_sym("cons")), + lurk_sym("cons"), Block { ops: vec![ Op::Null(mptr("foo"), Tag::Expr(Num)), diff --git a/src/lem/mod.rs b/src/lem/mod.rs index 243af64478..6b0eef2950 100644 --- a/src/lem/mod.rs +++ b/src/lem/mod.rs @@ -21,7 +21,7 @@ //! operations `Op` followed by a control `Ctrl` statement. //! //! Operations are much like `let` statements in functional languages. For -//! example, a `Op::Hash2(x, t, ys)` is to be understood as `let x = hash2(ys)`. +//! example, a `Op::Cons2(x, t, ys)` is to be understood as `let x = cons2(ys)`. //! If a second operation binds its result to the same variable as a previous //! operation, we shadow the previous value. There is no mutation, thus the //! language is referentially transparent. @@ -59,23 +59,29 @@ //! 6. We also check for variables that are not used. If intended they should //! be prefixed by "_" -mod circuit; -mod eval; -mod interpreter; +pub mod circuit; +pub mod eval; +pub mod interpreter; mod macros; mod path; -mod pointers; +pub mod pointers; mod slot; -mod store; +pub mod store; mod var_map; - -use crate::field::LurkField; -use crate::symbol::Symbol; -use crate::tag::{ContTag, ExprTag, Tag as TagTrait}; +pub mod zstore; use anyhow::{bail, Result}; use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; use std::sync::Arc; +use crate::{ + coprocessor::Coprocessor, + eval::lang::Lang, + field::LurkField, + symbol::Symbol, + tag::{ContTag, ExprTag, Op1, Op2, Tag as TagTrait}, +}; + use self::{pointers::Ptr, slot::SlotsCounter, store::Store, var_map::VarMap}; pub type AString = Arc; @@ -84,11 +90,17 @@ pub type AString = Arc; /// function body, which is a `Block` #[derive(Debug, Clone, PartialEq, Eq)] pub struct Func { - name: String, - input_params: Vec, - output_size: usize, - body: Block, - slot: SlotsCounter, + pub name: String, + pub input_params: Vec, + pub output_size: usize, + pub body: Block, + pub slot: SlotsCounter, +} + +impl> From<&Lang> for Func { + fn from(_lang: &Lang) -> Self { + eval::eval_step().clone() + } } /// LEM variables @@ -96,58 +108,69 @@ pub struct Func { pub struct Var(AString); /// LEM tags -#[derive(Copy, Debug, PartialEq, Clone, Eq, Hash)] +#[derive(Copy, Debug, PartialEq, Clone, Eq, Hash, Serialize, Deserialize)] pub enum Tag { Expr(ExprTag), Cont(ContTag), - Ctrl(CtrlTag), + Op1(Op1), + Op2(Op2), } -#[derive(Copy, Debug, PartialEq, Clone, Eq, Hash)] -pub enum CtrlTag { - Return, - MakeThunk, - ApplyContinuation, - Error, +impl TryFrom for Tag { + type Error = anyhow::Error; + + fn try_from(val: u16) -> Result { + if let Ok(tag) = ExprTag::try_from(val) { + Ok(Tag::Expr(tag)) + } else if let Ok(tag) = ContTag::try_from(val) { + Ok(Tag::Cont(tag)) + } else { + bail!("Invalid u16 for Tag: {val}") + } + } } -impl Tag { - #[inline] - pub fn to_field(self) -> F { - use Tag::*; - match self { - Expr(tag) => tag.to_field(), - Cont(tag) => tag.to_field(), - Ctrl(tag) => tag.to_field(), +impl From for u16 { + fn from(val: Tag) -> Self { + match val { + Tag::Expr(tag) => tag.into(), + Tag::Cont(tag) => tag.into(), + Tag::Op1(tag) => tag.into(), + Tag::Op2(tag) => tag.into(), } } } -impl CtrlTag { - #[inline] - fn to_field(self) -> F { - F::from(self as u64) +impl TagTrait for Tag { + fn from_field(f: &F) -> Option { + Self::try_from(f.to_u16()?).ok() + } + + fn to_field(&self) -> F { + Tag::to_field(self) } } -impl std::fmt::Display for CtrlTag { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Tag { + #[inline] + pub fn to_field(&self) -> F { match self { - Self::Return => write!(f, "return#"), - Self::ApplyContinuation => write!(f, "apply-cont#"), - Self::MakeThunk => write!(f, "make-thunk#"), - Self::Error => write!(f, "error#"), + Tag::Expr(tag) => tag.to_field(), + Tag::Cont(tag) => tag.to_field(), + Tag::Op1(tag) => tag.to_field(), + Tag::Op2(tag) => tag.to_field(), } } } impl std::fmt::Display for Tag { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use Tag::*; + use Tag::{Cont, Expr, Op1, Op2}; match self { Expr(tag) => write!(f, "expr.{}", tag), Cont(tag) => write!(f, "cont.{}", tag), - Ctrl(tag) => write!(f, "ctrl.{}", tag), + Op1(tag) => write!(f, "op1.{}", tag), + Op2(tag) => write!(f, "op2.{}", tag), } } } @@ -155,8 +178,8 @@ impl std::fmt::Display for Tag { /// LEM literals #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum Lit { - // TODO maybe it should be a LurkField instead of u64 - Num(u64), + // TODO maybe it should be a LurkField instead of u128 + Num(u128), String(String), Symbol(Symbol), } @@ -164,23 +187,36 @@ pub enum Lit { impl Lit { pub fn to_ptr(&self, store: &mut Store) -> Ptr { match self { - Self::Symbol(s) => store.intern_symbol(s.clone()), + Self::Symbol(s) => store.intern_symbol(s), Self::String(s) => store.intern_string(s), - Self::Num(num) => Ptr::num((*num).into()), + Self::Num(num) => Ptr::num(F::from_u128(*num)), + } + } + + pub fn to_ptr_cached(&self, store: &Store) -> Ptr { + match self { + Self::Symbol(s) => *store + .interned_symbol(s) + .expect("Symbol should have been cached"), + Self::String(s) => *store + .interned_string(s) + .expect("String should have been cached"), + Self::Num(num) => Ptr::num(F::from_u128(*num)), } } + pub fn from_ptr(ptr: &Ptr, store: &Store) -> Option { - use ExprTag::*; - use Tag::*; + use ExprTag::{Num, Str, Sym}; + use Tag::Expr; match ptr.tag() { Expr(Num) => match ptr { - Ptr::Leaf(_, f) => { - let num = LurkField::to_u64_unchecked(f); + Ptr::Atom(_, f) => { + let num = LurkField::to_u128_unchecked(f); Some(Self::Num(num)) } _ => unreachable!(), }, - Expr(Str) => store.fetch_string(ptr).cloned().map(Lit::String), + Expr(Str) => store.fetch_string(ptr).map(Lit::String), Expr(Sym) => store.fetch_symbol(ptr).map(Lit::Symbol), _ => None, } @@ -212,16 +248,17 @@ pub struct Block { #[non_exhaustive] #[derive(Debug, Clone, PartialEq, Eq)] pub enum Ctrl { - /// `MatchTag(x, cases)` performs a match on the tag of `x`, choosing the - /// appropriate `Block` among the ones provided in `cases` + /// `MatchTag(x, cases, def)` checks whether the tag of `x` matches some tag + /// among the ones provided in `cases`. If so, run the corresponding `Block`. + /// Run `def` otherwise MatchTag(Var, IndexMap, Option>), - /// `MatchSymbol(x, cases, def)` checks whether `x` matches some symbol among - /// the ones provided in `cases`. If so, run the corresponding `Block`. Run - /// `def` otherwise - MatchVal(Var, IndexMap, Option>), - /// `IfEq(x, y, eq_block, else_block)` runs `eq_block` if `x == y`, and - /// otherwise runs `else_block` - IfEq(Var, Var, Box, Box), + /// `MatchSymbol(x, cases, def)` requires that `x` is a symbol and checks + /// whether `x` matches some symbol among the ones provided in `cases`. If so, + /// run the corresponding `Block`. Run `def` otherwise + MatchSymbol(Var, IndexMap, Option>), + /// `If(x, true_block, false_block)` runs `true_block` if `x` is true, and + /// otherwise runs `false_block` + If(Var, Box, Box), /// `Return(rets)` sets the output to `rets` Return(Vec), } @@ -237,6 +274,16 @@ pub enum Op { Lit(Var, Lit), /// `Cast(y, t, x)` binds `y` to a pointer with tag `t` and the hash of `x` Cast(Var, Tag, Var), + /// `EqTag(y, a, b)` binds `y` to the boolean `a.tag == b.tag` + EqTag(Var, Var, Var), + /// `EqVal(y, a, b)` binds `y` to the boolean `a.val == b.val` + EqVal(Var, Var, Var), + /// `Not(y, a)` binds `y` to the negation of `a` + Not(Var, Var), + /// `And(y, a, b)` binds `y` to the conjunction of `a` and `b` + And(Var, Var, Var), + /// `Or(y, a, b)` binds `y` to the disjunction of `a` and `b` + Or(Var, Var, Var), /// `Add(y, a, b)` binds `y` to the sum of `a` and `b` Add(Var, Var, Var), /// `Sub(y, a, b)` binds `y` to the sum of `a` and `b` @@ -245,20 +292,26 @@ pub enum Op { Mul(Var, Var, Var), /// `Div(y, a, b)` binds `y` to the sum of `a` and `b` Div(Var, Var, Var), + /// `Lt(y, a, b)` binds `y` to `1` if `a < b`, or to `0` otherwise + Lt(Var, Var, Var), + /// `Trunc(y, a, n)` binds `y` to `a` truncated to `n` bits, up to 64 bits + Trunc(Var, Var, u32), + /// `DivRem64(ys, a, b)` binds `ys` to `(a / b, a % b)` as if they were u64 + DivRem64([Var; 2], Var, Var), /// `Emit(v)` simply prints out the value of `v` when interpreting the code Emit(Var), - /// `Hash2(x, t, ys)` binds `x` to a `Ptr` with tag `t` and 2 children `ys` - Hash2(Var, Tag, [Var; 2]), - /// `Hash3(x, t, ys)` binds `x` to a `Ptr` with tag `t` and 3 children `ys` - Hash3(Var, Tag, [Var; 3]), - /// `Hash4(x, t, ys)` binds `x` to a `Ptr` with tag `t` and 4 children `ys` - Hash4(Var, Tag, [Var; 4]), - /// `Unhash2([a, b], x)` binds `a` and `b` to the 2 children of `x` - Unhash2([Var; 2], Var), - /// `Unhash3([a, b, c], x)` binds `a`, `b` and `c` to the 3 children of `x` - Unhash3([Var; 3], Var), - /// `Unhash4([a, b, c, d], x)` binds `a`, `b`, `c` and `d` to the 4 children of `x` - Unhash4([Var; 4], Var), + /// `Cons2(x, t, ys)` binds `x` to a `Ptr` with tag `t` and 2 children `ys` + Cons2(Var, Tag, [Var; 2]), + /// `Cons3(x, t, ys)` binds `x` to a `Ptr` with tag `t` and 3 children `ys` + Cons3(Var, Tag, [Var; 3]), + /// `Cons4(x, t, ys)` binds `x` to a `Ptr` with tag `t` and 4 children `ys` + Cons4(Var, Tag, [Var; 4]), + /// `Decons2([a, b], x)` binds `a` and `b` to the 2 children of `x` + Decons2([Var; 2], Var), + /// `Decons3([a, b, c], x)` binds `a`, `b` and `c` to the 3 children of `x` + Decons3([Var; 3], Var), + /// `Decons4([a, b, c, d], x)` binds `a`, `b`, `c` and `d` to the 4 children of `x` + Decons4([Var; 4], Var), /// `Hide(x, s, p)` binds `x` to a (comm) `Ptr` resulting from hiding the /// payload `p` with (num) secret `s` Hide(Var, Var, Var), @@ -290,23 +343,27 @@ impl Func { /// Performs the static checks described in LEM's docstring. pub fn check(&self) -> Result<()> { - // Check if variable has already been defined. Panics - // if it is repeated (means `deconflict` is broken) use std::collections::{HashMap, HashSet}; - #[inline(always)] + + /// Check if variable has already been defined. Panics + /// if it is repeated (means `deconflict` is broken) + #[inline] fn is_unique(var: &Var, map: &mut HashMap) { - if map.insert(var.clone(), false).is_some() { - panic!("Variable {var} already defined. `deconflict` implementation broken."); - } + assert!( + map.insert(var.clone(), false).is_none(), + "Variable {var} already defined. `deconflict` implementation broken." + ); } - // Check if variable is bound and sets it as "used" - #[inline(always)] + + /// Check if variable is bound and sets it as "used" + #[inline] fn is_bound(var: &Var, map: &mut HashMap) -> Result<()> { if map.insert(var.clone(), true).is_none() { bail!("Variable {var} is unbound."); } Ok(()) } + fn recurse(block: &Block, return_size: usize, map: &mut HashMap) -> Result<()> { for op in &block.ops { match op { @@ -340,38 +397,59 @@ impl Func { is_bound(src, map)?; is_unique(tgt, map); } - Op::Add(tgt, a, b) + Op::Not(tgt, a) => { + is_bound(a, map)?; + is_unique(tgt, map); + } + Op::EqTag(tgt, a, b) + | Op::EqVal(tgt, a, b) + | Op::And(tgt, a, b) + | Op::Or(tgt, a, b) + | Op::Add(tgt, a, b) | Op::Sub(tgt, a, b) | Op::Mul(tgt, a, b) - | Op::Div(tgt, a, b) => { + | Op::Div(tgt, a, b) + | Op::Lt(tgt, a, b) => { is_bound(a, map)?; is_bound(b, map)?; is_unique(tgt, map); } + Op::Trunc(tgt, a, n) => { + if *n > 64 { + bail!("Cannot yet truncate over 64 bits") + } + is_bound(a, map)?; + is_unique(tgt, map); + } + Op::DivRem64(tgt, a, b) => { + is_bound(a, map)?; + is_bound(b, map)?; + tgt.iter().for_each(|var| is_unique(var, map)) + } Op::Emit(a) => { is_bound(a, map)?; } - Op::Hash2(img, _tag, preimg) => { + Op::Cons2(img, _tag, preimg) => { preimg.iter().try_for_each(|arg| is_bound(arg, map))?; is_unique(img, map); } - Op::Hash3(img, _tag, preimg) => { + Op::Cons3(img, _tag, preimg) => { preimg.iter().try_for_each(|arg| is_bound(arg, map))?; is_unique(img, map); } - Op::Hash4(img, _tag, preimg) => { + Op::Cons4(img, _tag, preimg) => { preimg.iter().try_for_each(|arg| is_bound(arg, map))?; is_unique(img, map); } - Op::Unhash2(preimg, img) => { + Op::Decons2(preimg, img) => { is_bound(img, map)?; preimg.iter().for_each(|var| is_unique(var, map)) } - Op::Unhash3(preimg, img) => { + Op::Decons3(preimg, img) => { is_bound(img, map)?; preimg.iter().for_each(|var| is_unique(var, map)) } - Op::Unhash4(preimg, img) => { + Op::Decons4(preimg, img) => { is_bound(img, map)?; preimg.iter().for_each(|var| is_unique(var, map)) } @@ -403,10 +481,12 @@ impl Func { let mut tags = HashSet::new(); let mut kind = None; for (tag, block) in cases { + // make sure that this `MatchTag` doesn't have weird semantics let tag_kind = match tag { Tag::Expr(..) => 0, Tag::Cont(..) => 1, - Tag::Ctrl(..) => 4, + Tag::Op1(..) => 2, + Tag::Op2(..) => 3, }; if let Some(kind) = kind { if kind != tag_kind { @@ -425,38 +505,19 @@ impl Func { None => (), } } - Ctrl::MatchVal(var, cases, def) => { + Ctrl::MatchSymbol(var, cases, def) => { is_bound(var, map)?; - let mut lits = HashSet::new(); - let mut kind = None; - for (lit, block) in cases { - let lit_kind = match lit { - Lit::Num(..) => 0, - Lit::String(..) => 1, - Lit::Symbol(..) => 2, - }; - if let Some(kind) = kind { - if kind != lit_kind { - bail!("Only values of the same kind allowed."); - } - } else { - kind = Some(lit_kind) - } - if !lits.insert(lit) { - bail!("Case {:?} already defined.", lit); - } + for block in cases.values() { recurse(block, return_size, map)?; } - match def { - Some(def) => recurse(def, return_size, map)?, - None => (), + if let Some(def) = def { + recurse(def, return_size, map)?; } } - Ctrl::IfEq(x, y, eq_block, else_block) => { + Ctrl::If(x, true_block, false_block) => { is_bound(x, map)?; - is_bound(y, map)?; - recurse(eq_block, return_size, map)?; - recurse(else_block, return_size, map)?; + recurse(true_block, return_size, map)?; + recurse(false_block, return_size, map)?; } } Ok(()) @@ -519,6 +580,12 @@ impl Func { body, ) } + + pub fn init_store(&self) -> Store { + let mut store = Store::default(); + self.body.intern_lits(&mut store); + store + } } impl Block { @@ -551,6 +618,35 @@ impl Block { let tgt = insert_one(map, uniq, &tgt); ops.push(Op::Cast(tgt, tag, src)) } + Op::EqTag(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::EqTag(tgt, a, b)) + } + Op::EqVal(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::EqVal(tgt, a, b)) + } + Op::Not(tgt, a) => { + let a = map.get_cloned(&a)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::Not(tgt, a)) + } + Op::And(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::And(tgt, a, b)) + } + Op::Or(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::Or(tgt, a, b)) + } Op::Add(tgt, a, b) => { let a = map.get_cloned(&a)?; let b = map.get_cloned(&b)?; @@ -575,39 +671,56 @@ impl Block { let tgt = insert_one(map, uniq, &tgt); ops.push(Op::Div(tgt, a, b)) } + Op::Lt(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::Lt(tgt, a, b)) + } + Op::Trunc(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let tgt = insert_one(map, uniq, &tgt); + ops.push(Op::Trunc(tgt, a, b)) + } + Op::DivRem64(tgt, a, b) => { + let a = map.get_cloned(&a)?; + let b = map.get_cloned(&b)?; + let tgt = insert_many(map, uniq, &tgt); + ops.push(Op::DivRem64(tgt.try_into().unwrap(), a, b)) + } Op::Emit(a) => { let a = map.get_cloned(&a)?; ops.push(Op::Emit(a)) } - Op::Hash2(img, tag, preimg) => { + Op::Cons2(img, tag, preimg) => { let preimg = map.get_many_cloned(&preimg)?.try_into().unwrap(); let img = insert_one(map, uniq, &img); - ops.push(Op::Hash2(img, tag, preimg)) + ops.push(Op::Cons2(img, tag, preimg)) } - Op::Hash3(img, tag, preimg) => { + Op::Cons3(img, tag, preimg) => { let preimg = map.get_many_cloned(&preimg)?.try_into().unwrap(); let img = insert_one(map, uniq, &img); - ops.push(Op::Hash3(img, tag, preimg)) + ops.push(Op::Cons3(img, tag, preimg)) } - Op::Hash4(img, tag, preimg) => { + Op::Cons4(img, tag, preimg) => { let preimg = map.get_many_cloned(&preimg)?.try_into().unwrap(); let img = insert_one(map, uniq, &img); - ops.push(Op::Hash4(img, tag, preimg)) + ops.push(Op::Cons4(img, tag, preimg)) } - Op::Unhash2(preimg, img) => { + Op::Decons2(preimg, img) => { let img = map.get_cloned(&img)?; let preimg = insert_many(map, uniq, &preimg); - ops.push(Op::Unhash2(preimg.try_into().unwrap(), img)) + ops.push(Op::Decons2(preimg.try_into().unwrap(), img)) } - Op::Unhash3(preimg, img) => { + Op::Decons3(preimg, img) => { let img = map.get_cloned(&img)?; let preimg = insert_many(map, uniq, &preimg); - ops.push(Op::Unhash3(preimg.try_into().unwrap(), img)) + ops.push(Op::Decons3(preimg.try_into().unwrap(), img)) } - Op::Unhash4(preimg, img) => { + Op::Decons4(preimg, img) => { let img = map.get_cloned(&img)?; let preimg = insert_many(map, uniq, &preimg); - ops.push(Op::Unhash4(preimg.try_into().unwrap(), img)) + ops.push(Op::Decons4(preimg.try_into().unwrap(), img)) } Op::Hide(tgt, sec, pay) => { let sec = map.get_cloned(&sec)?; @@ -637,30 +750,63 @@ impl Block { }; Ctrl::MatchTag(var, IndexMap::from_iter(new_cases), new_def) } - Ctrl::MatchVal(var, cases, def) => { + Ctrl::MatchSymbol(var, cases, def) => { let var = map.get_cloned(&var)?; let mut new_cases = Vec::with_capacity(cases.len()); - for (lit, case) in cases { + for (sym, case) in cases { let new_case = case.deconflict(&mut map.clone(), uniq)?; - new_cases.push((lit.clone(), new_case)); + new_cases.push((sym.clone(), new_case)); } let new_def = match def { Some(def) => Some(Box::new(def.deconflict(map, uniq)?)), None => None, }; - Ctrl::MatchVal(var, IndexMap::from_iter(new_cases), new_def) + Ctrl::MatchSymbol(var, IndexMap::from_iter(new_cases), new_def) } - Ctrl::IfEq(x, y, eq_block, else_block) => { + Ctrl::If(x, true_block, false_block) => { let x = map.get_cloned(&x)?; - let y = map.get_cloned(&y)?; - let eq_block = Box::new(eq_block.deconflict(&mut map.clone(), uniq)?); - let else_block = Box::new(else_block.deconflict(&mut map.clone(), uniq)?); - Ctrl::IfEq(x, y, eq_block, else_block) + let true_block = Box::new(true_block.deconflict(&mut map.clone(), uniq)?); + let false_block = Box::new(false_block.deconflict(&mut map.clone(), uniq)?); + Ctrl::If(x, true_block, false_block) } Ctrl::Return(o) => Ctrl::Return(map.get_many_cloned(&o)?), }; Ok(Block { ops, ctrl }) } + + fn intern_lits(&self, store: &mut Store) { + for op in &self.ops { + match op { + Op::Call(_, func, _) => func.body.intern_lits(store), + Op::Lit(_, lit) => { + lit.to_ptr(store); + } + _ => (), + } + } + match &self.ctrl { + Ctrl::If(.., a, b) => { + a.intern_lits(store); + b.intern_lits(store); + } + Ctrl::MatchTag(_, cases, def) => { + cases.values().for_each(|block| block.intern_lits(store)); + if let Some(def) = def { + def.intern_lits(store); + } + } + Ctrl::MatchSymbol(_, cases, def) => { + for (sym, b) in cases { + store.intern_symbol(sym); + b.intern_lits(store); + } + if let Some(def) = def { + def.intern_lits(store); + } + } + Ctrl::Return(..) => (), + } + } } impl Var { @@ -673,9 +819,11 @@ impl Var { #[cfg(test)] mod tests { use super::slot::SlotsCounter; - use super::{store::Store, *}; + use super::*; use crate::{func, lem::pointers::Ptr}; - use bellperson::util_cs::{test_cs::TestConstraintSystem, Comparable, Delta}; + use bellpepper::util_cs::Comparable; + use bellpepper_core::test_cs::TestConstraintSystem; + use bellpepper_core::Delta; use blstrs::Scalar as Fr; /// Helper function for testing circuit synthesis. @@ -685,8 +833,8 @@ mod tests { /// provided expressions. /// - `expected_slots` gives the number of expected slots for each type of hash. fn synthesize_test_helper(func: &Func, inputs: Vec>, expected_num_slots: SlotsCounter) { - use crate::tag::ContTag::*; - let store = &mut Store::default(); + use crate::tag::ContTag::{Error, Outermost, Terminal}; + let store = &mut func.init_store(); let outermost = Ptr::null(Tag::Cont(Outermost)); let terminal = Ptr::null(Tag::Cont(Terminal)); let error = Ptr::null(Tag::Cont(Error)); @@ -697,16 +845,20 @@ mod tests { let computed_num_constraints = func.num_constraints::(store); + let log_fmt = |_: usize, _: &[Ptr], _: &[Ptr], _: &Store| String::default(); + let mut cs_prev = None; - for input in inputs.into_iter() { - let input = vec![input, nil, outermost]; - let (frames, _) = func.call_until(input, store, stop_cond).unwrap(); + for input in inputs { + let input = [input, nil, outermost]; + let (frames, ..) = func + .call_until(&input, store, stop_cond, 10, log_fmt) + .unwrap(); let mut cs; - for frame in frames.clone() { + for frame in frames { cs = TestConstraintSystem::::new(); - func.synthesize(&mut cs, store, &frame).unwrap(); + func.synthesize_frame_aux(&mut cs, store, &frame).unwrap(); assert!(cs.is_satisfied()); assert_eq!(computed_num_constraints, cs.num_constraints()); if let Some(cs_prev) = cs_prev { @@ -784,16 +936,16 @@ mod tests { #[test] fn handles_non_ssa() { let func = func!(foo(expr_in, _env_in, _cont_in): 3 => { - let x: Expr::Cons = hash2(expr_in, expr_in); + let x: Expr::Cons = cons2(expr_in, expr_in); // The next line rewrites `x` and it should move on smoothly, matching // the expected number of constraints accordingly - let x: Expr::Cons = hash2(x, x); + let x: Expr::Cons = cons2(x, x); let cont_out_terminal: Cont::Terminal; return (x, x, cont_out_terminal); }); let inputs = vec![Ptr::num(Fr::from_u64(42))]; - synthesize_test_helper(&func, inputs, SlotsCounter::new((2, 0, 0))); + synthesize_test_helper(&func, inputs, SlotsCounter::new((2, 0, 0, 0, 0))); } #[test] @@ -829,16 +981,16 @@ mod tests { #[test] fn test_hash_slots() { let lem = func!(foo(expr_in, env_in, cont_in): 3 => { - let _x: Expr::Cons = hash2(expr_in, env_in); - let _y: Expr::Cons = hash3(expr_in, env_in, cont_in); - let _z: Expr::Cons = hash4(expr_in, env_in, cont_in, cont_in); + let _x: Expr::Cons = cons2(expr_in, env_in); + let _y: Expr::Cons = cons3(expr_in, env_in, cont_in); + let _z: Expr::Cons = cons4(expr_in, env_in, cont_in, cont_in); let t: Cont::Terminal; let p: Expr::Nil; match expr_in.tag { Expr::Num => { - let m: Expr::Cons = hash2(env_in, expr_in); - let n: Expr::Cons = hash3(cont_in, env_in, expr_in); - let _k: Expr::Cons = hash4(expr_in, cont_in, env_in, expr_in); + let m: Expr::Cons = cons2(env_in, expr_in); + let n: Expr::Cons = cons3(cont_in, env_in, expr_in); + let _k: Expr::Cons = cons4(expr_in, cont_in, env_in, expr_in); return (m, n, t); } Expr::Char => { @@ -854,25 +1006,25 @@ mod tests { }); let inputs = vec![Ptr::num(Fr::from_u64(42)), Ptr::char('c')]; - synthesize_test_helper(&lem, inputs, SlotsCounter::new((2, 2, 2))); + synthesize_test_helper(&lem, inputs, SlotsCounter::new((2, 2, 2, 0, 0))); } #[test] fn test_unhash_slots() { let lem = func!(foo(expr_in, env_in, cont_in): 3 => { - let _x: Expr::Cons = hash2(expr_in, env_in); - let _y: Expr::Cons = hash3(expr_in, env_in, cont_in); - let _z: Expr::Cons = hash4(expr_in, env_in, cont_in, cont_in); + let _x: Expr::Cons = cons2(expr_in, env_in); + let _y: Expr::Cons = cons3(expr_in, env_in, cont_in); + let _z: Expr::Cons = cons4(expr_in, env_in, cont_in, cont_in); let t: Cont::Terminal; let p: Expr::Nil; match expr_in.tag { Expr::Num => { - let m: Expr::Cons = hash2(env_in, expr_in); - let n: Expr::Cons = hash3(cont_in, env_in, expr_in); - let k: Expr::Cons = hash4(expr_in, cont_in, env_in, expr_in); - let (_m1, _m2) = unhash2(m); - let (_n1, _n2, _n3) = unhash3(n); - let (_k1, _k2, _k3, _k4) = unhash4(k); + let m: Expr::Cons = cons2(env_in, expr_in); + let n: Expr::Cons = cons3(cont_in, env_in, expr_in); + let k: Expr::Cons = cons4(expr_in, cont_in, env_in, expr_in); + let (_m1, _m2) = decons2(m); + let (_n1, _n2, _n3) = decons3(n); + let (_k1, _k2, _k3, _k4) = decons4(k); return (m, n, t); } Expr::Char => { @@ -888,36 +1040,36 @@ mod tests { }); let inputs = vec![Ptr::num(Fr::from_u64(42)), Ptr::char('c')]; - synthesize_test_helper(&lem, inputs, SlotsCounter::new((3, 3, 3))); + synthesize_test_helper(&lem, inputs, SlotsCounter::new((3, 3, 3, 0, 0))); } #[test] fn test_unhash_nested_slots() { let lem = func!(foo(expr_in, env_in, cont_in): 3 => { - let _x: Expr::Cons = hash2(expr_in, env_in); - let _y: Expr::Cons = hash3(expr_in, env_in, cont_in); - let _z: Expr::Cons = hash4(expr_in, env_in, cont_in, cont_in); + let _x: Expr::Cons = cons2(expr_in, env_in); + let _y: Expr::Cons = cons3(expr_in, env_in, cont_in); + let _z: Expr::Cons = cons4(expr_in, env_in, cont_in, cont_in); let t: Cont::Terminal; let p: Expr::Nil; match expr_in.tag { Expr::Num => { - let m: Expr::Cons = hash2(env_in, expr_in); - let n: Expr::Cons = hash3(cont_in, env_in, expr_in); - let k: Expr::Cons = hash4(expr_in, cont_in, env_in, expr_in); - let (_m1, _m2) = unhash2(m); - let (_n1, _n2, _n3) = unhash3(n); - let (_k1, _k2, _k3, _k4) = unhash4(k); + let m: Expr::Cons = cons2(env_in, expr_in); + let n: Expr::Cons = cons3(cont_in, env_in, expr_in); + let k: Expr::Cons = cons4(expr_in, cont_in, env_in, expr_in); + let (_m1, _m2) = decons2(m); + let (_n1, _n2, _n3) = decons3(n); + let (_k1, _k2, _k3, _k4) = decons4(k); match cont_in.tag { Cont::Outermost => { - let _a: Expr::Cons = hash2(env_in, expr_in); - let _b: Expr::Cons = hash3(cont_in, env_in, expr_in); - let _c: Expr::Cons = hash4(expr_in, cont_in, env_in, expr_in); + let _a: Expr::Cons = cons2(env_in, expr_in); + let _b: Expr::Cons = cons3(cont_in, env_in, expr_in); + let _c: Expr::Cons = cons4(expr_in, cont_in, env_in, expr_in); return (m, n, t); } Cont::Terminal => { - let _d: Expr::Cons = hash2(env_in, expr_in); - let _e: Expr::Cons = hash3(cont_in, env_in, expr_in); - let _f: Expr::Cons = hash4(expr_in, cont_in, env_in, expr_in); + let _d: Expr::Cons = cons2(env_in, expr_in); + let _e: Expr::Cons = cons3(cont_in, env_in, expr_in); + let _f: Expr::Cons = cons4(expr_in, cont_in, env_in, expr_in); return (m, n, t); } } @@ -935,6 +1087,6 @@ mod tests { }); let inputs = vec![Ptr::num(Fr::from_u64(42)), Ptr::char('c')]; - synthesize_test_helper(&lem, inputs, SlotsCounter::new((4, 4, 4))); + synthesize_test_helper(&lem, inputs, SlotsCounter::new((4, 4, 4, 0, 0))); } } diff --git a/src/lem/path.rs b/src/lem/path.rs index 061dbaeb63..cb09570880 100644 --- a/src/lem/path.rs +++ b/src/lem/path.rs @@ -1,11 +1,13 @@ use std::collections::HashSet; -use super::{Block, Ctrl, Func, Lit, Op, Tag}; +use crate::Symbol; -#[derive(Clone, PartialEq, Eq, Hash)] +use super::{Block, Ctrl, Func, Op, Tag}; + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum PathNode { Tag(Tag), - Lit(Lit), + Symbol(Symbol), Bool(bool), Default, } @@ -14,14 +16,14 @@ impl std::fmt::Display for PathNode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Tag(tag) => write!(f, "Tag({})", tag), - Self::Lit(lit) => write!(f, "{:?}", lit), + Self::Symbol(sym) => write!(f, "Symbol({})", sym), Self::Bool(b) => write!(f, "Bool({})", b), Self::Default => write!(f, "Default"), } } } -#[derive(Default, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] pub struct Path(Vec); impl std::fmt::Display for Path { @@ -44,9 +46,9 @@ impl Path { Path(path) } - pub fn push_lit(&self, lit: &Lit) -> Path { + pub fn push_symbol(&self, sym: Symbol) -> Path { let mut path = self.0.clone(); - path.push(PathNode::Lit(lit.clone())); + path.push(PathNode::Symbol(sym)); Path(path) } @@ -57,8 +59,8 @@ impl Path { } #[inline] - pub fn push_tag_inplace(&mut self, tag: &Tag) { - self.0.push(PathNode::Tag(*tag)); + pub fn push_tag_inplace(&mut self, tag: Tag) { + self.0.push(PathNode::Tag(tag)); } #[inline] @@ -67,8 +69,8 @@ impl Path { } #[inline] - pub fn push_lit_inplace(&mut self, lit: &Lit) { - self.0.push(PathNode::Lit(lit.clone())); + pub fn push_symbol_inplace(&mut self, sym: Symbol) { + self.0.push(PathNode::Symbol(sym)); } #[inline] @@ -83,10 +85,8 @@ impl Path { /// Computes the number of different paths taken given a list of paths pub fn num_paths_taken(paths: &[Self]) -> usize { - let mut all_paths: HashSet = HashSet::default(); - paths.iter().for_each(|path| { - all_paths.insert(path.clone()); - }); + let mut all_paths: HashSet<&Self> = HashSet::default(); + all_paths.extend(paths); all_paths.len() } } @@ -119,13 +119,15 @@ impl Block { .values() .fold(init, |acc, block| acc + block.num_paths()) } - Ctrl::MatchVal(_, cases, def) => { + Ctrl::MatchSymbol(_, cases, def) => { let init = def.as_ref().map_or(0, |def| def.num_paths()); cases .values() .fold(init, |acc, block| acc + block.num_paths()) } - Ctrl::IfEq(_, _, eq_block, else_block) => eq_block.num_paths() + else_block.num_paths(), + Ctrl::If(_, true_block, false_block) => { + true_block.num_paths() + false_block.num_paths() + } Ctrl::Return(..) => 1, }; num_paths diff --git a/src/lem/pointers.rs b/src/lem/pointers.rs index b93a288b83..fcdb2095df 100644 --- a/src/lem/pointers.rs +++ b/src/lem/pointers.rs @@ -1,10 +1,15 @@ -use crate::{field::*, tag::ContTag::Dummy, tag::ExprTag::*}; +use serde::{Deserialize, Serialize}; + +use crate::{ + field::*, + tag::ExprTag::{Char, Comm, Nil, Num, U64}, +}; use super::Tag; /// `Ptr` is the main piece of data LEMs operate on. We can think of a pointer /// as a building block for trees that represent Lurk data. A pointer can be a -/// leaf that contains data encoded as an element of a `LurkField` or it can have +/// atom that contains data encoded as an element of a `LurkField` or it can have /// children. For performance, the children of a pointer are stored on an /// `IndexSet` and the resulding index is carried by the pointer itself. /// @@ -13,21 +18,21 @@ use super::Tag; /// children a pointer has. However, LEMs require extra flexibility because LEM /// hashing operations can plug any tag to the resulting pointer. Thus, the /// number of children have to be made explicit as the `Ptr` enum. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum Ptr { - Leaf(Tag, F), - Tree2(Tag, usize), - Tree3(Tag, usize), - Tree4(Tag, usize), + Atom(Tag, F), + Tuple2(Tag, usize), + Tuple3(Tag, usize), + Tuple4(Tag, usize), } impl std::hash::Hash for Ptr { fn hash(&self, state: &mut H) { match self { - Ptr::Leaf(tag, f) => (0, tag, f.to_repr().as_ref()).hash(state), - Ptr::Tree2(tag, x) => (1, tag, x).hash(state), - Ptr::Tree3(tag, x) => (2, tag, x).hash(state), - Ptr::Tree4(tag, x) => (3, tag, x).hash(state), + Ptr::Atom(tag, f) => (0, tag, f.to_repr().as_ref()).hash(state), + Ptr::Tuple2(tag, x) => (1, tag, x).hash(state), + Ptr::Tuple3(tag, x) => (2, tag, x).hash(state), + Ptr::Tuple4(tag, x) => (3, tag, x).hash(state), } } } @@ -35,47 +40,75 @@ impl std::hash::Hash for Ptr { impl Ptr { pub fn tag(&self) -> &Tag { match self { - Ptr::Leaf(tag, _) => tag, - Ptr::Tree2(tag, _) => tag, - Ptr::Tree3(tag, _) => tag, - Ptr::Tree4(tag, _) => tag, + Ptr::Atom(tag, _) | Ptr::Tuple2(tag, _) | Ptr::Tuple3(tag, _) | Ptr::Tuple4(tag, _) => { + tag + } } } #[inline] pub fn num(f: F) -> Self { - Ptr::Leaf(Tag::Expr(Num), f) + Ptr::Atom(Tag::Expr(Num), f) + } + + #[inline] + pub fn num_u64(u: u64) -> Self { + Ptr::Atom(Tag::Expr(Num), F::from_u64(u)) + } + + #[inline] + pub fn u64(u: u64) -> Self { + Ptr::Atom(Tag::Expr(U64), F::from_u64(u)) } #[inline] pub fn char(c: char) -> Self { - Ptr::Leaf(Tag::Expr(Char), F::from_char(c)) + Ptr::Atom(Tag::Expr(Char), F::from_char(c)) } #[inline] pub fn comm(hash: F) -> Self { - Ptr::Leaf(Tag::Expr(Comm), hash) + Ptr::Atom(Tag::Expr(Comm), hash) } #[inline] pub fn null(tag: Tag) -> Self { - Ptr::Leaf(tag, F::ZERO) + Ptr::Atom(tag, F::ZERO) + } + + pub fn is_null(&self) -> bool { + match self { + Ptr::Atom(_, f) => f == &F::ZERO, + _ => false, + } + } + + pub fn is_nil(&self) -> bool { + self.tag() == &Tag::Expr(Nil) } #[inline] - pub fn cast(&self, tag: Tag) -> Self { + pub fn cast(self, tag: Tag) -> Self { match self { - Ptr::Leaf(_, f) => Ptr::Leaf(tag, *f), - Ptr::Tree2(_, x) => Ptr::Tree2(tag, *x), - Ptr::Tree3(_, x) => Ptr::Tree3(tag, *x), - Ptr::Tree4(_, x) => Ptr::Tree4(tag, *x), + Ptr::Atom(_, f) => Ptr::Atom(tag, f), + Ptr::Tuple2(_, x) => Ptr::Tuple2(tag, x), + Ptr::Tuple3(_, x) => Ptr::Tuple3(tag, x), + Ptr::Tuple4(_, x) => Ptr::Tuple4(tag, x), + } + } + + #[inline] + pub fn get_atom(&self) -> Option<&F> { + match self { + Ptr::Atom(_, f) => Some(f), + _ => None, } } #[inline] pub fn get_index2(&self) -> Option { match self { - Ptr::Tree2(_, x) => Some(*x), + Ptr::Tuple2(_, x) => Some(*x), _ => None, } } @@ -83,7 +116,7 @@ impl Ptr { #[inline] pub fn get_index3(&self) -> Option { match self { - Ptr::Tree3(_, x) => Some(*x), + Ptr::Tuple3(_, x) => Some(*x), _ => None, } } @@ -91,7 +124,7 @@ impl Ptr { #[inline] pub fn get_index4(&self) -> Option { match self { - Ptr::Tree4(_, x) => Some(*x), + Ptr::Tuple4(_, x) => Some(*x), _ => None, } } @@ -107,34 +140,15 @@ impl Ptr { /// An important note is that computing the respective `ZPtr` of a `Ptr` can be /// expensive because of the Poseidon hashes. That's why we operate on `Ptr`s /// when interpreting LEMs and delay the need for `ZPtr`s as much as possible. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct ZPtr { - pub tag: Tag, - pub hash: F, -} +pub type ZPtr = crate::z_data::z_ptr::ZPtr; /// `ZChildren` keeps track of the children of `ZPtr`s, in case they have any. /// This information is saved during hydration and is needed to content-address /// a store. -pub(crate) enum ZChildren { - Tree2(ZPtr, ZPtr), - Tree3(ZPtr, ZPtr, ZPtr), - Tree4(ZPtr, ZPtr, ZPtr, ZPtr), -} - -impl ZPtr { - #[inline] - pub fn dummy() -> Self { - Self { - tag: Tag::Cont(Dummy), - hash: F::ZERO, - } - } -} - -impl std::hash::Hash for ZPtr { - fn hash(&self, state: &mut H) { - self.tag.hash(state); - self.hash.to_repr().as_ref().hash(state); - } +#[derive(Debug, Serialize, Deserialize)] +pub enum ZChildren { + Atom, + Tuple2(ZPtr, ZPtr), + Tuple3(ZPtr, ZPtr, ZPtr), + Tuple4(ZPtr, ZPtr, ZPtr, ZPtr), } diff --git a/src/lem/slot.rs b/src/lem/slot.rs index 06333b5319..94ca84b44b 100644 --- a/src/lem/slot.rs +++ b/src/lem/slot.rs @@ -107,67 +107,94 @@ use super::{Block, Ctrl, Op}; #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] pub struct SlotsCounter { - pub hash2: usize, - pub hash3: usize, pub hash4: usize, + pub hash6: usize, + pub hash8: usize, + pub commitment: usize, + pub less_than: usize, } impl SlotsCounter { /// This interface is mostly for testing #[inline] - pub fn new(num_slots: (usize, usize, usize)) -> Self { + pub fn new(num_slots: (usize, usize, usize, usize, usize)) -> Self { Self { - hash2: num_slots.0, - hash3: num_slots.1, - hash4: num_slots.2, + hash4: num_slots.0, + hash6: num_slots.1, + hash8: num_slots.2, + commitment: num_slots.3, + less_than: num_slots.4, } } #[inline] - pub fn consume_hash2(&mut self) -> usize { - self.hash2 += 1; - self.hash2 - 1 + pub fn consume_hash4(&mut self) -> usize { + self.hash4 += 1; + self.hash4 - 1 } #[inline] - pub fn consume_hash3(&mut self) -> usize { - self.hash3 += 1; - self.hash3 - 1 + pub fn consume_hash6(&mut self) -> usize { + self.hash6 += 1; + self.hash6 - 1 } #[inline] - pub fn consume_hash4(&mut self) -> usize { - self.hash4 += 1; - self.hash4 - 1 + pub fn consume_hash8(&mut self) -> usize { + self.hash8 += 1; + self.hash8 - 1 + } + + #[inline] + pub fn consume_commitment(&mut self) -> usize { + self.commitment += 1; + self.commitment - 1 + } + + #[inline] + pub fn consume_less_than(&mut self) -> usize { + self.less_than += 1; + self.less_than - 1 } #[inline] pub fn max(&self, other: Self) -> Self { use std::cmp::max; Self { - hash2: max(self.hash2, other.hash2), - hash3: max(self.hash3, other.hash3), hash4: max(self.hash4, other.hash4), + hash6: max(self.hash6, other.hash6), + hash8: max(self.hash8, other.hash8), + commitment: max(self.commitment, other.commitment), + less_than: max(self.less_than, other.less_than), } } #[inline] pub fn add(&self, other: Self) -> Self { Self { - hash2: self.hash2 + other.hash2, - hash3: self.hash3 + other.hash3, hash4: self.hash4 + other.hash4, + hash6: self.hash6 + other.hash6, + hash8: self.hash8 + other.hash8, + commitment: self.commitment + other.commitment, + less_than: self.less_than + other.less_than, } } + + #[inline] + pub fn fold_max(self, vec: Vec) -> Self { + vec.into_iter().fold(self, |acc, i| acc.max(i)) + } } impl Block { pub fn count_slots(&self) -> SlotsCounter { let ops_slots = self.ops.iter().fold(SlotsCounter::default(), |acc, op| { let val = match op { - Op::Hash2(..) | Op::Unhash2(..) => SlotsCounter::new((1, 0, 0)), - Op::Hash3(..) | Op::Unhash3(..) => SlotsCounter::new((0, 1, 0)), - Op::Hash4(..) | Op::Unhash4(..) => SlotsCounter::new((0, 0, 1)), + Op::Cons2(..) | Op::Decons2(..) => SlotsCounter::new((1, 0, 0, 0, 0)), + Op::Cons3(..) | Op::Decons3(..) => SlotsCounter::new((0, 1, 0, 0, 0)), + Op::Cons4(..) | Op::Decons4(..) => SlotsCounter::new((0, 0, 1, 0, 0)), + Op::Hide(..) | Op::Open(..) => SlotsCounter::new((0, 0, 0, 1, 0)), + Op::Lt(..) => SlotsCounter::new((0, 0, 0, 0, 1)), Op::Call(_, func, _) => func.slot, _ => SlotsCounter::default(), }; @@ -182,7 +209,7 @@ impl Block { .values() .fold(init, |acc, block| acc.max(block.count_slots())) } - Ctrl::MatchVal(_, cases, def) => { + Ctrl::MatchSymbol(_, cases, def) => { let init = def .as_ref() .map_or(SlotsCounter::default(), |def| def.count_slots()); @@ -190,9 +217,9 @@ impl Block { .values() .fold(init, |acc, block| acc.max(block.count_slots())) } - Ctrl::IfEq(_, _, eq_block, else_block) => { - let eq_slots = eq_block.count_slots(); - eq_slots.max(else_block.count_slots()) + Ctrl::If(_, true_block, false_block) => { + let if_slots = true_block.count_slots(); + if_slots.max(false_block.count_slots()) } Ctrl::Return(..) => SlotsCounter::default(), }; @@ -202,17 +229,21 @@ impl Block { #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub(crate) enum SlotType { - Hash2, - Hash3, Hash4, + Hash6, + Hash8, + Commitment, + LessThan, } impl SlotType { pub(crate) fn preimg_size(&self) -> usize { match self { - Self::Hash2 => 4, - Self::Hash3 => 6, - Self::Hash4 => 8, + Self::Hash4 => 4, + Self::Hash6 => 6, + Self::Hash8 => 8, + Self::Commitment => 3, + Self::LessThan => 2, } } } @@ -220,9 +251,11 @@ impl SlotType { impl std::fmt::Display for SlotType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Hash2 => write!(f, "Hash2"), - Self::Hash3 => write!(f, "Hash3"), Self::Hash4 => write!(f, "Hash4"), + Self::Hash6 => write!(f, "Hash6"), + Self::Hash8 => write!(f, "Hash8"), + Self::Commitment => write!(f, "Commitment"), + Self::LessThan => write!(f, "LessThan"), } } } diff --git a/src/lem/store.rs b/src/lem/store.rs index b0c093b776..cbc2ccc449 100644 --- a/src/lem/store.rs +++ b/src/lem/store.rs @@ -1,33 +1,34 @@ +use anyhow::{bail, Result}; +use indexmap::IndexSet; +use nom::{sequence::preceded, Parser}; use rayon::prelude::*; -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use crate::{ + cache_map::CacheMap, field::{FWrap, LurkField}, hash::PoseidonCache, lem::Tag, - symbol::LurkSym, + parser::{syntax, Error, Span}, + state::{lurk_sym, State}, symbol::Symbol, syntax::Syntax, - tag::ExprTag::*, + tag::ExprTag::{Char, Comm, Cons, Fun, Key, Nil, Num, Str, Sym, Thunk, U64}, uint::UInt, }; -use anyhow::{bail, Result}; -use dashmap::DashMap; -use indexmap::IndexSet; -use super::pointers::{Ptr, ZChildren, ZPtr}; +use super::pointers::{Ptr, ZPtr}; /// The `Store` is a crucial part of Lurk's implementation and tries to be a /// vesatile data structure for many parts of Lurk's data pipeline. /// -/// It holds Lurk data structured as trees of `Ptr`s (or `ZPtr`s). When a `Ptr` -/// has children`, we store them in the `IndexSet`s available: `ptrs2`, `ptrs3` -/// or `ptrs4`. These data structures speed up LEM interpretation because lookups -/// by indices are fast. +/// It holds Lurk data structured as trees of `Ptr`s. When a `Ptr` has children, +/// we store them in the `IndexSet`s available: `tuple2`, `tuple3` or `tuple4`. +/// These data structures speed up LEM interpretation because lookups by indices +/// are fast. /// -/// The `Store` also provides an infra to speed up interning strings and symbols. -/// This data is saved in `str_tails_cache` and `sym_tails_cache`, which are better -/// explained in `intern_string` and `intern_symbol_path` respectively. +/// The `Store` provides an infra to speed up interning strings and symbols. This +/// data is saved in `string_ptr_cache` and `symbol_ptr_cache`. /// /// There's also a process that we call "hydration", in which we use Poseidon /// hashes to compute the (stable) hash of the children of a pointer. These hashes @@ -36,30 +37,31 @@ use super::pointers::{Ptr, ZChildren, ZPtr}; /// /// Lastly, we have a `HashMap` to hold committed data, which can be retrieved by /// the resulting commitment hash. -#[derive(Default)] +#[derive(Default, Debug)] pub struct Store { - ptrs2: IndexSet<(Ptr, Ptr)>, - ptrs3: IndexSet<(Ptr, Ptr, Ptr)>, - ptrs4: IndexSet<(Ptr, Ptr, Ptr, Ptr)>, + tuple2: IndexSet<(Ptr, Ptr)>, + tuple3: IndexSet<(Ptr, Ptr, Ptr)>, + tuple4: IndexSet<(Ptr, Ptr, Ptr, Ptr)>, - str_cache: HashMap>, - ptr_str_cache: HashMap, String>, - sym_cache: HashMap, Ptr>, - ptr_sym_cache: HashMap, Vec>, + string_ptr_cache: HashMap>, + symbol_ptr_cache: HashMap>, + + ptr_string_cache: CacheMap, String>, + ptr_symbol_cache: CacheMap, Box>, pub poseidon_cache: PoseidonCache, + dehydrated: Vec>, - z_cache: DashMap, ZPtr, ahash::RandomState>, - z_dag: DashMap, ZChildren, ahash::RandomState>, + z_cache: CacheMap, Box>>, - pub comms: HashMap, (F, Ptr)>, // hash -> (secret, src) + comms: HashMap, (F, Ptr)>, // hash -> (secret, src) } impl Store { /// Creates a `Ptr` that's a parent of two children pub fn intern_2_ptrs(&mut self, tag: Tag, a: Ptr, b: Ptr) -> Ptr { - let (idx, inserted) = self.ptrs2.insert_full((a, b)); - let ptr = Ptr::Tree2(tag, idx); + let (idx, inserted) = self.tuple2.insert_full((a, b)); + let ptr = Ptr::Tuple2(tag, idx); if inserted { // this is for `hydrate_z_cache` self.dehydrated.push(ptr); @@ -69,16 +71,17 @@ impl Store { /// Similar to `intern_2_ptrs` but doesn't add the resulting pointer to /// `dehydrated`. This function is used when converting a `ZStore` to a - /// `Store` (TODO). - #[inline] - pub fn intern_2_ptrs_not_dehydrated(&mut self, tag: Tag, a: Ptr, b: Ptr) -> Ptr { - Ptr::Tree2(tag, self.ptrs2.insert_full((a, b)).0) + /// `Store`. + pub fn intern_2_ptrs_hydrated(&mut self, tag: Tag, a: Ptr, b: Ptr, z: ZPtr) -> Ptr { + let ptr = Ptr::Tuple2(tag, self.tuple2.insert_full((a, b)).0); + self.z_cache.insert(ptr, Box::new(z)); + ptr } /// Creates a `Ptr` that's a parent of three children pub fn intern_3_ptrs(&mut self, tag: Tag, a: Ptr, b: Ptr, c: Ptr) -> Ptr { - let (idx, inserted) = self.ptrs3.insert_full((a, b, c)); - let ptr = Ptr::Tree3(tag, idx); + let (idx, inserted) = self.tuple3.insert_full((a, b, c)); + let ptr = Ptr::Tuple3(tag, idx); if inserted { // this is for `hydrate_z_cache` self.dehydrated.push(ptr); @@ -88,16 +91,18 @@ impl Store { /// Similar to `intern_3_ptrs` but doesn't add the resulting pointer to /// `dehydrated`. This function is used when converting a `ZStore` to a - /// `Store` (TODO). - #[inline] - pub fn intern_3_ptrs_not_dehydrated( + /// `Store`. + pub fn intern_3_ptrs_hydrated( &mut self, tag: Tag, a: Ptr, b: Ptr, c: Ptr, + z: ZPtr, ) -> Ptr { - Ptr::Tree3(tag, self.ptrs3.insert_full((a, b, c)).0) + let ptr = Ptr::Tuple3(tag, self.tuple3.insert_full((a, b, c)).0); + self.z_cache.insert(ptr, Box::new(z)); + ptr } /// Creates a `Ptr` that's a parent of four children @@ -109,8 +114,8 @@ impl Store { c: Ptr, d: Ptr, ) -> Ptr { - let (idx, inserted) = self.ptrs4.insert_full((a, b, c, d)); - let ptr = Ptr::Tree4(tag, idx); + let (idx, inserted) = self.tuple4.insert_full((a, b, c, d)); + let ptr = Ptr::Tuple4(tag, idx); if inserted { // this is for `hydrate_z_cache` self.dehydrated.push(ptr); @@ -120,159 +125,334 @@ impl Store { /// Similar to `intern_4_ptrs` but doesn't add the resulting pointer to /// `dehydrated`. This function is used when converting a `ZStore` to a - /// `Store` (TODO). - #[inline] - pub fn intern_4_ptrs_not_dehydrated( + /// `Store`. + pub fn intern_4_ptrs_hydrated( &mut self, tag: Tag, a: Ptr, b: Ptr, c: Ptr, d: Ptr, + z: ZPtr, ) -> Ptr { - Ptr::Tree4(tag, self.ptrs4.insert_full((a, b, c, d)).0) + let ptr = Ptr::Tuple4(tag, self.tuple4.insert_full((a, b, c, d)).0); + self.z_cache.insert(ptr, Box::new(z)); + ptr } #[inline] pub fn fetch_2_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr)> { - self.ptrs2.get_index(idx) + self.tuple2.get_index(idx) } #[inline] pub fn fetch_3_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr, Ptr)> { - self.ptrs3.get_index(idx) + self.tuple3.get_index(idx) } #[inline] pub fn fetch_4_ptrs(&self, idx: usize) -> Option<&(Ptr, Ptr, Ptr, Ptr)> { - self.ptrs4.get_index(idx) + self.tuple4.get_index(idx) } - /// Interns a string recursively pub fn intern_string(&mut self, s: &str) -> Ptr { - if s.is_empty() { - let ptr = Ptr::null(Tag::Expr(Str)); - self.ptr_str_cache.insert(ptr, "".into()); - return ptr; + if let Some(ptr) = self.string_ptr_cache.get(s) { + *ptr + } else { + let ptr = s.chars().rev().fold(Ptr::null(Tag::Expr(Str)), |acc, c| { + self.intern_2_ptrs(Tag::Expr(Str), Ptr::char(c), acc) + }); + self.string_ptr_cache.insert(s.to_string(), ptr); + self.ptr_string_cache.insert(ptr, s.to_string()); + ptr } + } - match self.str_cache.get(s) { - Some(ptr_cache) => *ptr_cache, - None => { - let tail = &s.chars().skip(1).collect::(); - let tail_ptr = self.intern_string(tail); - let head = s.chars().next().unwrap(); - let s_ptr = self.intern_2_ptrs(Tag::Expr(Str), Ptr::char(head), tail_ptr); - self.str_cache.insert(s.into(), s_ptr); - self.ptr_str_cache.insert(s_ptr, s.into()); - s_ptr + #[inline] + pub fn interned_string(&self, s: &str) -> Option<&Ptr> { + self.string_ptr_cache.get(s) + } + + pub fn fetch_string(&self, ptr: &Ptr) -> Option { + if let Some(str) = self.ptr_string_cache.get(ptr) { + Some(str.to_string()) + } else { + let mut string = String::new(); + let mut ptr = *ptr; + loop { + match ptr { + Ptr::Atom(Tag::Expr(Str), f) => { + if f == F::ZERO { + self.ptr_string_cache.insert(ptr, string.clone()); + return Some(string); + } else { + return None; + } + } + Ptr::Tuple2(Tag::Expr(Str), idx) => { + let (car, cdr) = self.fetch_2_ptrs(idx)?; + match car { + Ptr::Atom(Tag::Expr(Char), c) => { + string.push(c.to_char().expect("char pointers are well formed")); + ptr = *cdr + } + _ => return None, + } + } + _ => return None, + } } } } - #[inline] - pub fn fetch_string(&self, ptr: &Ptr) -> Option<&String> { - match ptr.tag() { - Tag::Expr(Str) => self.ptr_str_cache.get(ptr), - _ => None, + pub fn intern_symbol_path(&mut self, path: &[String]) -> Ptr { + path.iter().fold(Ptr::null(Tag::Expr(Sym)), |acc, s| { + let s_ptr = self.intern_string(s); + self.intern_2_ptrs(Tag::Expr(Sym), s_ptr, acc) + }) + } + + pub fn intern_symbol(&mut self, sym: &Symbol) -> Ptr { + if let Some(ptr) = self.symbol_ptr_cache.get(sym) { + *ptr + } else { + let path_ptr = self.intern_symbol_path(sym.path()); + let sym_ptr = if sym == &lurk_sym("nil") { + path_ptr.cast(Tag::Expr(Nil)) + } else if sym.is_keyword() { + path_ptr.cast(Tag::Expr(Key)) + } else { + path_ptr + }; + self.symbol_ptr_cache.insert(sym.clone(), sym_ptr); + self.ptr_symbol_cache.insert(sym_ptr, Box::new(sym.clone())); + sym_ptr } } - /// Interns a symbol path recursively - pub fn intern_symbol_path(&mut self, path: Vec) -> Ptr { - if path.is_empty() { - let ptr = Ptr::null(Tag::Expr(Sym)); - self.ptr_sym_cache.insert(ptr, vec![]); - return ptr; + #[inline] + pub fn interned_symbol(&self, s: &Symbol) -> Option<&Ptr> { + self.symbol_ptr_cache.get(s) + } + + pub fn fetch_symbol_path(&self, mut idx: usize) -> Option> { + let mut path = vec![]; + loop { + let (car, cdr) = self.fetch_2_ptrs(idx)?; + let string = self.fetch_string(car)?; + path.push(string); + match cdr { + Ptr::Atom(Tag::Expr(Sym), f) => { + if f == &F::ZERO { + path.reverse(); + return Some(path); + } else { + return None; + } + } + Ptr::Tuple2(Tag::Expr(Sym), idx_cdr) => idx = *idx_cdr, + _ => return None, + } } + } - match self.sym_cache.get(&path) { - Some(ptr_cache) => *ptr_cache, - None => { - let tail = &path[1..]; - let tail_ptr = self.intern_symbol_path(tail.to_vec()); - let head = &path[0]; - let head_ptr = self.intern_string(head); - let path_ptr = self.intern_2_ptrs(Tag::Expr(Sym), head_ptr, tail_ptr); - self.sym_cache.insert(path.clone(), path_ptr); - self.ptr_sym_cache.insert(path_ptr, path); - path_ptr + pub fn fetch_symbol(&self, ptr: &Ptr) -> Option { + if let Some(sym) = self.ptr_symbol_cache.get(ptr) { + Some(sym.clone()) + } else { + match ptr { + Ptr::Atom(Tag::Expr(Sym), f) => { + if f == &F::ZERO { + let sym = Symbol::root_sym(); + self.ptr_symbol_cache.insert(*ptr, Box::new(sym.clone())); + Some(sym) + } else { + None + } + } + Ptr::Atom(Tag::Expr(Key), f) => { + if f == &F::ZERO { + let key = Symbol::root_key(); + self.ptr_symbol_cache.insert(*ptr, Box::new(key.clone())); + Some(key) + } else { + None + } + } + Ptr::Tuple2(Tag::Expr(Sym | Nil), idx) => { + let path = self.fetch_symbol_path(*idx)?; + let sym = Symbol::sym_from_vec(path); + self.ptr_symbol_cache.insert(*ptr, Box::new(sym.clone())); + Some(sym) + } + Ptr::Tuple2(Tag::Expr(Key), idx) => { + let path = self.fetch_symbol_path(*idx)?; + let key = Symbol::key_from_vec(path); + self.ptr_symbol_cache.insert(*ptr, Box::new(key.clone())); + Some(key) + } + _ => None, } } } + pub fn fetch_sym(&self, ptr: &Ptr) -> Option { + if ptr.tag() == &Tag::Expr(Sym) { + self.fetch_symbol(ptr) + } else { + None + } + } + + pub fn fetch_key(&self, ptr: &Ptr) -> Option { + if ptr.tag() == &Tag::Expr(Key) { + self.fetch_symbol(ptr) + } else { + None + } + } + + pub fn hide(&mut self, secret: F, payload: Ptr) -> Result> { + let z_ptr = self.hash_ptr(&payload)?; + let hash = self + .poseidon_cache + .hash3(&[secret, z_ptr.tag_field(), *z_ptr.value()]); + self.comms.insert(FWrap::(hash), (secret, payload)); + Ok(Ptr::comm(hash)) + } + + pub fn hide_and_return_z_payload( + &mut self, + secret: F, + payload: Ptr, + ) -> Result<(F, ZPtr)> { + let z_ptr = self.hash_ptr(&payload)?; + let hash = self + .poseidon_cache + .hash3(&[secret, z_ptr.tag_field(), *z_ptr.value()]); + self.comms.insert(FWrap::(hash), (secret, payload)); + Ok((hash, z_ptr)) + } + #[inline] - pub fn fetch_sym_path(&self, ptr: &Ptr) -> Option<&Vec> { - self.ptr_sym_cache.get(ptr) + pub fn commit(&mut self, payload: Ptr) -> Result> { + self.hide(F::NON_HIDING_COMMITMENT_SECRET, payload) + } + + pub fn open(&self, hash: F) -> Option<&(F, Ptr)> { + self.comms.get(&FWrap(hash)) } #[inline] - pub fn fetch_symbol(&self, ptr: &Ptr) -> Option { - match ptr.tag() { - Tag::Expr(Sym) | Tag::Expr(Key) => Some(Symbol { - path: self.fetch_sym_path(ptr)?.to_vec(), - }), - _ => None, - } + pub fn intern_lurk_sym(&mut self, name: &str) -> Ptr { + self.intern_symbol(&lurk_sym(name)) + } + + #[inline] + pub fn intern_nil(&mut self) -> Ptr { + self.intern_lurk_sym("nil") } - pub fn intern_symbol(&mut self, s: Symbol) -> Ptr { - self.intern_symbol_path(s.path) + #[inline] + pub fn key(&mut self, name: &str) -> Ptr { + self.intern_symbol(&Symbol::key(&[name.to_string()])) } - pub fn intern_lurk_symbol(&mut self, s: LurkSym) -> Ptr { - if matches!(s, LurkSym::Nil) { - return Ptr::Leaf(Tag::Expr(Nil), F::ZERO); + pub fn car_cdr(&mut self, ptr: &Ptr) -> Result<(Ptr, Ptr)> { + match ptr.tag() { + Tag::Expr(Nil) => { + let nil = self.intern_nil(); + Ok((nil, nil)) + } + Tag::Expr(Cons) => { + let Some(idx) = ptr.get_index2() else { + bail!("malformed cons pointer") + }; + match self.fetch_2_ptrs(idx) { + Some(res) => Ok(*res), + None => bail!("car/cdr not found"), + } + } + Tag::Expr(Str) => { + if ptr.is_null() { + Ok((self.intern_nil(), Ptr::null(Tag::Expr(Str)))) + } else { + let Some(idx) = ptr.get_index2() else { + bail!("malformed str pointer") + }; + match self.fetch_2_ptrs(idx) { + Some(res) => Ok(*res), + None => bail!("car/cdr not found"), + } + } + } + _ => bail!("invalid pointer to extract car/cdr from"), } - self.intern_symbol(Symbol::lurk_sym(&format!("{s}"))) } - pub fn intern_nil(&mut self) -> Ptr { - self.intern_lurk_symbol(LurkSym::Nil) + pub fn list(&mut self, elts: Vec>) -> Ptr { + elts.into_iter().rev().fold(self.intern_nil(), |acc, elt| { + self.intern_2_ptrs(Tag::Expr(Cons), elt, acc) + }) } pub fn intern_syntax(&mut self, syn: Syntax) -> Ptr { match syn { - Syntax::Num(_, x) => Ptr::Leaf(Tag::Expr(Num), x.into_scalar()), - Syntax::UInt(_, UInt::U64(x)) => Ptr::Leaf(Tag::Expr(U64), x.into()), - Syntax::Char(_, x) => Ptr::Leaf(Tag::Expr(Char), (x as u64).into()), - Syntax::Symbol(_, x) | Syntax::Keyword(_, x) => self.intern_symbol(x), - Syntax::LurkSym(_, x) => self.intern_lurk_symbol(x), + Syntax::Num(_, x) => Ptr::Atom(Tag::Expr(Num), x.into_scalar()), + Syntax::UInt(_, UInt::U64(x)) => Ptr::Atom(Tag::Expr(U64), x.into()), + Syntax::Char(_, x) => Ptr::Atom(Tag::Expr(Char), (x as u64).into()), + Syntax::Symbol(_, symbol) => self.intern_symbol(&symbol), Syntax::String(_, x) => self.intern_string(&x), Syntax::Quote(pos, x) => { - let quote = crate::symbol::Symbol::lurk_sym("quote"); - let xs = vec![Syntax::Symbol(pos, quote), *x]; + let xs = vec![Syntax::Symbol(pos, lurk_sym("quote").into()), *x]; self.intern_syntax(Syntax::List(pos, xs)) } - Syntax::List(_, xs) => { - let mut cdr = self.intern_lurk_symbol(LurkSym::Nil); - for x in xs.into_iter().rev() { - let car = self.intern_syntax(x); - cdr = self.intern_2_ptrs(Tag::Expr(Cons), car, cdr); - } - cdr - } + Syntax::List(_, xs) => xs.into_iter().rev().fold(self.intern_nil(), |acc, x| { + let car = self.intern_syntax(x); + self.intern_2_ptrs(Tag::Expr(Cons), car, acc) + }), Syntax::Improper(_, xs, end) => { - let mut cdr = self.intern_syntax(*end); - for x in xs.into_iter().rev() { - let car = self.intern_syntax(x); - cdr = self.intern_2_ptrs(Tag::Expr(Cons), car, cdr); - } - cdr + xs.into_iter() + .rev() + .fold(self.intern_syntax(*end), |acc, x| { + let car = self.intern_syntax(x); + self.intern_2_ptrs(Tag::Expr(Cons), car, acc) + }) } } } - pub fn read(&mut self, input: &str) -> Result> { - use crate::parser::*; - use nom::sequence::preceded; - use nom::Parser; - match preceded(syntax::parse_space, syntax::parse_syntax()).parse(Span::new(input)) { - Ok((_i, x)) => Ok(self.intern_syntax(x)), + pub fn read(&mut self, state: Rc>, input: &str) -> Result> { + match preceded( + syntax::parse_space, + syntax::parse_syntax(state, false, false), + ) + .parse(Span::new(input)) + { + Ok((_, x)) => Ok(self.intern_syntax(x)), Err(e) => bail!("{}", e), } } + pub fn read_maybe_meta<'a>( + &mut self, + state: Rc>, + input: &'a str, + ) -> Result<(Span<'a>, Ptr, bool), Error> { + match preceded(syntax::parse_space, syntax::parse_maybe_meta(state, false)) + .parse(input.into()) + { + Ok((i, Some((is_meta, x)))) => Ok((i, self.intern_syntax(x), is_meta)), + Ok((_, None)) => Err(Error::NoInput), + Err(e) => Err(Error::Syntax(format!("{}", e))), + } + } + + #[inline] + pub fn read_with_default_state(&mut self, input: &str) -> Result> { + self.read(State::init_lurk_state().rccell(), input) + } + /// Recursively hashes the children of a `Ptr` in order to obtain its /// corresponding `ZPtr`. While traversing a `Ptr` tree, it consults the /// cache of `Ptr`s that have already been hydrated and also populates this @@ -281,86 +461,93 @@ impl Store { /// Warning: without cache hits, this function might blow up Rust's recursion /// depth limit. This limitation is circumvented by calling `hydrate_z_cache`. pub fn hash_ptr(&self, ptr: &Ptr) -> Result> { + use crate::tag::ContTag::{Dummy, Error, Outermost, Terminal}; match ptr { - Ptr::Leaf(tag, x) => Ok(ZPtr { - tag: *tag, - hash: *x, - }), - Ptr::Tree2(tag, idx) => match self.z_cache.get(ptr) { - Some(z_ptr) => Ok(*z_ptr), - None => { - let Some((a, b)) = self.ptrs2.get_index(*idx) else { - bail!("Index {idx} not found on ptrs2") + Ptr::Atom(tag, x) => match tag { + Tag::Cont(Outermost | Error | Dummy | Terminal) => { + // temporary shim for compatibility with Lurk Alpha + Ok(ZPtr::from_parts( + *tag, + self.poseidon_cache.hash8(&[F::ZERO; 8]), + )) + } + _ => Ok(ZPtr::from_parts(*tag, *x)), + }, + Ptr::Tuple2(tag, idx) => { + if let Some(z_ptr) = self.z_cache.get(ptr) { + Ok(*z_ptr) + } else { + let Some((a, b)) = self.fetch_2_ptrs(*idx) else { + bail!("Index {idx} not found on tuple2") }; let a = self.hash_ptr(a)?; let b = self.hash_ptr(b)?; - let z_ptr = ZPtr { - tag: *tag, - hash: self.poseidon_cache.hash4(&[ - a.tag.to_field(), - a.hash, - b.tag.to_field(), - b.hash, + let z_ptr = ZPtr::from_parts( + *tag, + self.poseidon_cache.hash4(&[ + a.tag_field(), + *a.value(), + b.tag_field(), + *b.value(), ]), - }; - self.z_dag.insert(z_ptr, ZChildren::Tree2(a, b)); - self.z_cache.insert(*ptr, z_ptr); + ); + self.z_cache.insert(*ptr, Box::new(z_ptr)); Ok(z_ptr) } - }, - Ptr::Tree3(tag, idx) => match self.z_cache.get(ptr) { - Some(z_ptr) => Ok(*z_ptr), - None => { - let Some((a, b, c)) = self.ptrs3.get_index(*idx) else { - bail!("Index {idx} not found on ptrs3") + } + Ptr::Tuple3(tag, idx) => { + if let Some(z_ptr) = self.z_cache.get(ptr) { + Ok(*z_ptr) + } else { + let Some((a, b, c)) = self.fetch_3_ptrs(*idx) else { + bail!("Index {idx} not found on tuple3") }; let a = self.hash_ptr(a)?; let b = self.hash_ptr(b)?; let c = self.hash_ptr(c)?; - let z_ptr = ZPtr { - tag: *tag, - hash: self.poseidon_cache.hash6(&[ - a.tag.to_field(), - a.hash, - b.tag.to_field(), - b.hash, - c.tag.to_field(), - c.hash, + let z_ptr = ZPtr::from_parts( + *tag, + self.poseidon_cache.hash6(&[ + a.tag_field(), + *a.value(), + b.tag_field(), + *b.value(), + c.tag_field(), + *c.value(), ]), - }; - self.z_dag.insert(z_ptr, ZChildren::Tree3(a, b, c)); - self.z_cache.insert(*ptr, z_ptr); + ); + self.z_cache.insert(*ptr, Box::new(z_ptr)); Ok(z_ptr) } - }, - Ptr::Tree4(tag, idx) => match self.z_cache.get(ptr) { - Some(z_ptr) => Ok(*z_ptr), - None => { - let Some((a, b, c, d)) = self.ptrs4.get_index(*idx) else { - bail!("Index {idx} not found on ptrs4") + } + Ptr::Tuple4(tag, idx) => { + if let Some(z_ptr) = self.z_cache.get(ptr) { + Ok(*z_ptr) + } else { + let Some((a, b, c, d)) = self.fetch_4_ptrs(*idx) else { + bail!("Index {idx} not found on tuple4") }; let a = self.hash_ptr(a)?; let b = self.hash_ptr(b)?; let c = self.hash_ptr(c)?; let d = self.hash_ptr(d)?; - let z_ptr = ZPtr { - tag: *tag, - hash: self.poseidon_cache.hash8(&[ - a.tag.to_field(), - a.hash, - b.tag.to_field(), - b.hash, - c.tag.to_field(), - c.hash, - d.tag.to_field(), - d.hash, + let z_ptr = ZPtr::from_parts( + *tag, + self.poseidon_cache.hash8(&[ + a.tag_field(), + *a.value(), + b.tag_field(), + *b.value(), + c.tag_field(), + *c.value(), + d.tag_field(), + *d.value(), ]), - }; - self.z_dag.insert(z_ptr, ZChildren::Tree4(a, b, c, d)); - self.z_cache.insert(*ptr, z_ptr); + ); + self.z_cache.insert(*ptr, Box::new(z_ptr)); Ok(z_ptr) } - }, + } } } @@ -372,54 +559,216 @@ impl Store { }); self.dehydrated = Vec::new(); } + + pub fn to_vector(&self, ptrs: &[Ptr]) -> Result> { + ptrs.iter() + .try_fold(Vec::with_capacity(2 * ptrs.len()), |mut acc, ptr| { + let z_ptr = self.hash_ptr(ptr)?; + acc.push(z_ptr.tag_field()); + acc.push(*z_ptr.value()); + Ok(acc) + }) + } } impl Ptr { - pub fn to_string(self, store: &Store) -> String { + pub fn dbg_display(self, store: &Store) -> String { if let Some(s) = store.fetch_string(&self) { - return format!("\"{}\"", s); + return format!("\"{s}\""); } if let Some(s) = store.fetch_symbol(&self) { - return format!("{}", s); + return format!("{s}"); } match self { - Ptr::Leaf(tag, f) => { + Ptr::Atom(tag, f) => { if let Some(x) = f.to_u64() { - format!("{}{}", tag, x) + format!("{tag}{x}") } else { - format!("{}{:?}", tag, f) + format!("{tag}{f:?}") } } - Ptr::Tree2(tag, x) => { + Ptr::Tuple2(tag, x) => { let (p1, p2) = store.fetch_2_ptrs(x).unwrap(); format!( "({} {} {})", tag, - (*p1).to_string(store), - (*p2).to_string(store) + (*p1).dbg_display(store), + (*p2).dbg_display(store) ) } - Ptr::Tree3(tag, x) => { + Ptr::Tuple3(tag, x) => { let (p1, p2, p3) = store.fetch_3_ptrs(x).unwrap(); format!( "({} {} {} {})", tag, - (*p1).to_string(store), - (*p2).to_string(store), - (*p3).to_string(store) + (*p1).dbg_display(store), + (*p2).dbg_display(store), + (*p3).dbg_display(store) ) } - Ptr::Tree4(tag, x) => { + Ptr::Tuple4(tag, x) => { let (p1, p2, p3, p4) = store.fetch_4_ptrs(x).unwrap(); format!( "({} {} {} {} {})", tag, - (*p1).to_string(store), - (*p2).to_string(store), - (*p3).to_string(store), - (*p4).to_string(store) + (*p1).dbg_display(store), + (*p2).dbg_display(store), + (*p3).dbg_display(store), + (*p4).dbg_display(store) ) } } } + + fn unfold_list(&self, store: &Store) -> Option<(Vec>, Option>)> { + let mut idx = self.get_index2()?; + let mut list = vec![]; + let mut last = None; + while let Some((car, cdr)) = store.fetch_2_ptrs(idx) { + list.push(*car); + match cdr.tag() { + Tag::Expr(Nil) => break, + Tag::Expr(Cons) => { + idx = cdr.get_index2()?; + } + _ => { + last = Some(*cdr); + break; + } + } + } + Some((list, last)) + } + + pub fn fmt_to_string(&self, store: &Store, state: &State) -> String { + match self.tag() { + Tag::Expr(t) => match t { + Nil => { + if let Some(sym) = store.fetch_symbol(self) { + state.fmt_to_string(&sym.into()) + } else { + "".into() + } + } + Sym => { + if let Some(sym) = store.fetch_sym(self) { + state.fmt_to_string(&sym.into()) + } else { + "".into() + } + } + Key => { + if let Some(key) = store.fetch_key(self) { + state.fmt_to_string(&key.into()) + } else { + "".into() + } + } + Str => { + if let Some(str) = store.fetch_string(self) { + format!("\"{str}\"") + } else { + "".into() + } + } + Char => match self.get_atom().map(F::to_char) { + Some(Some(c)) => format!("\'{c}\'"), + _ => "".into(), + }, + Cons => { + if let Some((list, last)) = self.unfold_list(store) { + let list = list + .iter() + .map(|p| p.fmt_to_string(store, state)) + .collect::>(); + if let Some(last) = last { + format!( + "({} . {})", + list.join(" "), + last.fmt_to_string(store, state) + ) + } else { + format!("({})", list.join(" ")) + } + } else { + "".into() + } + } + Num => match self.get_atom() { + None => "".into(), + Some(f) => { + if let Some(u) = f.to_u64() { + u.to_string() + } else { + format!("0x{}", f.hex_digits()) + } + } + }, + U64 => match self.get_atom().map(F::to_u64) { + Some(Some(u)) => format!("{u}u64"), + _ => "".into(), + }, + Fun => match self.get_index3() { + None => "".into(), + Some(idx) => { + if let Some((arg, bod, _)) = store.fetch_3_ptrs(idx) { + match bod.tag() { + Tag::Expr(Nil) => { + format!( + "", + arg.fmt_to_string(store, state), + bod.fmt_to_string(store, state) + ) + } + Tag::Expr(Cons) => { + if let Some(idx) = bod.get_index2() { + if let Some((bod, _)) = store.fetch_2_ptrs(idx) { + format!( + "", + arg.fmt_to_string(store, state), + bod.fmt_to_string(store, state) + ) + } else { + "".into() + } + } else { + "".into() + } + } + _ => "".into(), + } + } else { + "".into() + } + } + }, + Thunk => match self.get_index2() { + None => "".into(), + Some(idx) => { + if let Some((val, cont)) = store.fetch_2_ptrs(idx) { + format!( + "Thunk{{ value: {} => cont: {} }}", + val.fmt_to_string(store, state), + cont.fmt_to_string(store, state) + ) + } else { + "".into() + } + } + }, + Comm => match self.get_atom() { + Some(f) => { + if store.comms.contains_key(&FWrap(*f)) { + format!("(comm 0x{})", f.hex_digits()) + } else { + format!("", f.hex_digits()) + } + } + None => "".into(), + }, + }, + Tag::Cont(_) => "".into(), + _ => unreachable!(), + } + } } diff --git a/src/lem/var_map.rs b/src/lem/var_map.rs index b47af238e4..52d8e45da0 100644 --- a/src/lem/var_map.rs +++ b/src/lem/var_map.rs @@ -1,6 +1,6 @@ use anyhow::{bail, Result}; -use log::info; use std::collections::{hash_map::Entry, HashMap}; +use tracing::info; use super::Var; @@ -8,8 +8,8 @@ use super::Var; /// to be more ergonomic under the assumption that a LEM must always define /// variables before using them, so we don't expect to need some piece of /// information from a variable that hasn't been defined. -#[derive(Clone)] -pub(crate) struct VarMap(HashMap); +#[derive(Clone, Debug)] +pub struct VarMap(HashMap); impl VarMap { /// Creates an empty `VarMap` @@ -40,10 +40,6 @@ impl VarMap { None => bail!("Data for variable {var} not found"), } } - - pub(crate) fn get_many(&self, args: &[Var]) -> Result> { - args.iter().map(|arg| self.get(arg)).collect() - } } impl VarMap { diff --git a/src/lem/zstore.rs b/src/lem/zstore.rs new file mode 100644 index 0000000000..b74330080e --- /dev/null +++ b/src/lem/zstore.rs @@ -0,0 +1,159 @@ +use anyhow::{bail, Result}; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashMap}; + +use crate::field::{FWrap, LurkField}; + +use super::{ + pointers::{Ptr, ZChildren, ZPtr}, + store::Store, +}; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct ZStore { + dag: BTreeMap, ZChildren>, + comms: BTreeMap, (F, ZPtr)>, +} + +impl ZStore { + #[inline] + pub fn add_comm(&mut self, hash: F, secret: F, payload: ZPtr) { + self.comms.insert(FWrap(hash), (secret, payload)); + } + + #[inline] + pub fn open(&self, hash: F) -> Option<&(F, ZPtr)> { + self.comms.get(&FWrap(hash)) + } + + #[inline] + pub fn get_children(&self, z_ptr: &ZPtr) -> Option<&ZChildren> { + self.dag.get(z_ptr) + } +} + +pub fn populate_z_store( + z_store: &mut ZStore, + ptr: &Ptr, + store: &Store, +) -> Result> { + let mut cache: HashMap, ZPtr> = HashMap::default(); + let mut recurse = |ptr: &Ptr| -> Result> { + if let Some(z_ptr) = cache.get(ptr) { + Ok(*z_ptr) + } else { + let z_ptr = match ptr { + Ptr::Atom(tag, f) => { + let z_ptr = ZPtr::from_parts(*tag, *f); + z_store.dag.insert(z_ptr, ZChildren::Atom); + z_ptr + } + Ptr::Tuple2(tag, idx) => { + let Some((a, b)) = store.fetch_2_ptrs(*idx) else { + bail!("Index {idx} not found on tuple2") + }; + let a = populate_z_store(z_store, a, store)?; + let b = populate_z_store(z_store, b, store)?; + let z_ptr = ZPtr::from_parts( + *tag, + store.poseidon_cache.hash4(&[ + a.tag_field(), + *a.value(), + b.tag_field(), + *b.value(), + ]), + ); + z_store.dag.insert(z_ptr, ZChildren::Tuple2(a, b)); + z_ptr + } + Ptr::Tuple3(tag, idx) => { + let Some((a, b, c)) = store.fetch_3_ptrs(*idx) else { + bail!("Index {idx} not found on tuple3") + }; + let a = populate_z_store(z_store, a, store)?; + let b = populate_z_store(z_store, b, store)?; + let c = populate_z_store(z_store, c, store)?; + let z_ptr = ZPtr::from_parts( + *tag, + store.poseidon_cache.hash6(&[ + a.tag_field(), + *a.value(), + b.tag_field(), + *b.value(), + c.tag_field(), + *c.value(), + ]), + ); + z_store.dag.insert(z_ptr, ZChildren::Tuple3(a, b, c)); + z_ptr + } + Ptr::Tuple4(tag, idx) => { + let Some((a, b, c, d)) = store.fetch_4_ptrs(*idx) else { + bail!("Index {idx} not found on tuple4") + }; + let a = populate_z_store(z_store, a, store)?; + let b = populate_z_store(z_store, b, store)?; + let c = populate_z_store(z_store, c, store)?; + let d = populate_z_store(z_store, d, store)?; + let z_ptr = ZPtr::from_parts( + *tag, + store.poseidon_cache.hash8(&[ + a.tag_field(), + *a.value(), + b.tag_field(), + *b.value(), + c.tag_field(), + *c.value(), + d.tag_field(), + *d.value(), + ]), + ); + z_store.dag.insert(z_ptr, ZChildren::Tuple4(a, b, c, d)); + z_ptr + } + }; + cache.insert(*ptr, z_ptr); + Ok(z_ptr) + } + }; + recurse(ptr) +} + +pub fn populate_store( + store: &mut Store, + z_ptr: &ZPtr, + z_store: &ZStore, +) -> Result> { + let mut cache: HashMap, Ptr> = HashMap::default(); + let mut recurse = |z_ptr: &ZPtr| -> Result> { + if let Some(z_ptr) = cache.get(z_ptr) { + Ok(*z_ptr) + } else { + let ptr = match z_store.get_children(z_ptr) { + None => bail!("Couldn't find ZPtr"), + Some(ZChildren::Atom) => Ptr::Atom(z_ptr.tag(), *z_ptr.value()), + Some(ZChildren::Tuple2(z1, z2)) => { + let ptr1 = populate_store(store, z1, z_store)?; + let ptr2 = populate_store(store, z2, z_store)?; + store.intern_2_ptrs_hydrated(z_ptr.tag(), ptr1, ptr2, *z_ptr) + } + Some(ZChildren::Tuple3(z1, z2, z3)) => { + let ptr1 = populate_store(store, z1, z_store)?; + let ptr2 = populate_store(store, z2, z_store)?; + let ptr3 = populate_store(store, z3, z_store)?; + store.intern_3_ptrs_hydrated(z_ptr.tag(), ptr1, ptr2, ptr3, *z_ptr) + } + Some(ZChildren::Tuple4(z1, z2, z3, z4)) => { + let ptr1 = populate_store(store, z1, z_store)?; + let ptr2 = populate_store(store, z2, z_store)?; + let ptr3 = populate_store(store, z3, z_store)?; + let ptr4 = populate_store(store, z4, z_store)?; + store.intern_4_ptrs_hydrated(z_ptr.tag(), ptr1, ptr2, ptr3, ptr4, *z_ptr) + } + }; + cache.insert(*z_ptr, ptr); + Ok(ptr) + } + }; + recurse(z_ptr) +} diff --git a/src/lib.rs b/src/lib.rs index 7324b1e0b2..00d6bb135a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,29 +1,29 @@ -#![allow(clippy::single_match, clippy::type_complexity)] -#![allow(clippy::uninlined_format_args)] -#![warn(rust_2018_idioms, unreachable_pub)] - +#![deny(unreachable_pub)] #[macro_use] extern crate alloc; pub mod cache_map; +#[macro_use] pub mod circuit; pub mod cli; +pub mod config; pub mod cont; pub mod coprocessor; +pub mod error; pub mod eval; pub mod expr; pub mod field; pub mod hash; pub mod hash_witness; -//pub mod package; -pub mod error; pub mod lem; mod num; +pub mod package; pub mod parser; pub mod proof; pub mod ptr; pub mod public_parameters; pub mod repl; +pub mod state; pub mod store; pub mod symbol; pub mod syntax; diff --git a/src/main.rs b/src/main.rs index d714963b12..da441c4402 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,24 @@ use anyhow::Result; +use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; +use tracing_texray::TeXRayLayer; fn main() -> Result<()> { // this handle should be held until the end of the program, // do not replace by let _ = ... let _metrics_handle = lurk_metrics::MetricsSink::init(); - pretty_env_logger::init(); + + let subscriber = Registry::default() + .with(fmt::layer().pretty()) + .with(EnvFilter::from_default_env()) + // note: we don't `tracing_texray::examine` anywhere in lurkrs, so no spans are printed *yet* + .with(TeXRayLayer::new()); + tracing::subscriber::set_global_default(subscriber).unwrap(); + + println!( + "commit: {} {}", + env!("VERGEN_GIT_COMMIT_DATE"), + env!("VERGEN_GIT_SHA") + ); + lurk::cli::parse_and_run() } diff --git a/src/package.rs b/src/package.rs new file mode 100644 index 0000000000..beb5f9285e --- /dev/null +++ b/src/package.rs @@ -0,0 +1,92 @@ +use anyhow::{bail, Result}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +use crate::Symbol; + +pub type SymbolRef = Arc; + +#[derive(Debug)] +pub struct Package { + name: SymbolRef, + symbols: HashMap, + names: HashMap, + local: HashSet, +} + +impl Package { + #[inline] + pub fn new(name: SymbolRef) -> Self { + Self { + name, + symbols: Default::default(), + names: Default::default(), + local: Default::default(), + } + } + + #[inline] + pub const fn name(&self) -> &SymbolRef { + &self.name + } + + #[inline] + pub fn resolve(&self, symbol_name: &str) -> Option<&SymbolRef> { + self.symbols.get(symbol_name) + } + + /// Given a symbol name, returns the corresponding symbol if it's accessible + /// in the package. If it's not, make it so by creating a new symbol prefixed + /// by the package's name. + pub fn intern(&mut self, symbol_name: String) -> SymbolRef { + self.symbols + .entry(symbol_name) + .or_insert_with_key(|symbol_name| { + let symbol: SymbolRef = self.name.direct_child(symbol_name).into(); + self.names.insert(symbol.clone(), symbol_name.clone()); + self.local.insert(symbol.clone()); + symbol + }) + .clone() + } + + /// Tries to import a list of symbols so they become accessible in the package. + /// If some symbol can't be imported due to an error (i.e. it conflicts with + /// another accessible symbol), none of the symbols are effectively imported. + /// In other words, importing is an atomic operation. + pub fn import(&mut self, symbols: &[SymbolRef]) -> Result<()> { + let mut symbols_names = Vec::with_capacity(symbols.len()); + // first we look for potential errors + for symbol in symbols { + let symbol_name = symbol.name()?; + // check conflicts with accessible symbols + if let Some(symbol_resolved) = self.resolve(symbol_name) { + if symbol != symbol_resolved { + bail!("{symbol} conflicts with {symbol_resolved}, which is already accessible") + } + } + // memoize the symbols' names for efficiency + symbols_names.push(symbol_name); + } + // now we finally import as an atomic operation + for (symbol, symbol_name) in symbols.iter().zip(symbols_names) { + self.symbols.insert(symbol_name.to_string(), symbol.clone()); + self.names.insert(symbol.clone(), symbol_name.to_string()); + } + Ok(()) + } + + /// Import the local symbols of another package + pub fn use_package(&mut self, package: &Package) -> Result<()> { + self.import(&package.local.iter().cloned().collect::>()) + } + + pub fn fmt_to_string(&self, symbol: &SymbolRef) -> String { + match self.names.get(symbol) { + None => symbol.fmt_to_string(), + Some(name) => Symbol::fmt_path_component_to_string(name), + } + } +} diff --git a/src/parser.rs b/src/parser.rs index c2fb80dfa0..5174b7f43f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,5 +1,9 @@ +use std::cell::RefCell; +use std::rc::Rc; + use crate::field::LurkField; use crate::ptr::Ptr; +use crate::state::State; use crate::store::Store; use nom::sequence::preceded; use nom::Parser; @@ -32,21 +36,44 @@ pub enum Error { impl Store { pub fn read(&mut self, input: &str) -> Result, Error> { - match preceded(syntax::parse_space, syntax::parse_syntax()).parse(Span::new(input)) { + let state = State::init_lurk_state().rccell(); + match preceded( + syntax::parse_space, + syntax::parse_syntax(state, false, false), + ) + .parse(Span::new(input)) + { + Ok((_i, x)) => Ok(self.intern_syntax(x)), + Err(e) => Err(Error::Syntax(format!("{e}"))), + } + } + + pub fn read_with_state( + &mut self, + state: Rc>, + input: &str, + ) -> Result, Error> { + match preceded( + syntax::parse_space, + syntax::parse_syntax(state, false, false), + ) + .parse(Span::new(input)) + { Ok((_i, x)) => Ok(self.intern_syntax(x)), - Err(e) => Err(Error::Syntax(format!("{}", e))), + Err(e) => Err(Error::Syntax(format!("{e}"))), } } - pub fn read_maybe_meta<'a>( + pub fn read_maybe_meta_with_state<'a>( &mut self, + state: Rc>, input: Span<'a>, ) -> Result<(Span<'a>, Ptr, bool), Error> { use syntax::*; - match preceded(parse_space, parse_maybe_meta()).parse(input) { + match preceded(parse_space, parse_maybe_meta(state, false)).parse(input) { Ok((i, Some((is_meta, x)))) => Ok((i, self.intern_syntax(x), is_meta)), Ok((_, None)) => Err(Error::NoInput), - Err(e) => Err(Error::Syntax(format!("{}", e))), + Err(e) => Err(Error::Syntax(format!("{e}"))), } } } diff --git a/src/parser/base.rs b/src/parser/base.rs index 2bd9f8f0ef..172d126562 100644 --- a/src/parser/base.rs +++ b/src/parser/base.rs @@ -196,19 +196,19 @@ pub mod tests { match (expected, p.parse(Span::<'a>::new(i))) { (Some(expected), Ok((_i, x))) if x == expected => (), (Some(expected), Ok((i, x))) => { - println!("input: {:?}", i); - println!("expected: {:?}", expected); - println!("detected: {:?}", x); + println!("input: {i:?}"); + println!("expected: {expected:?}"); + println!("detected: {x:?}"); unreachable!("unexpected parse result") } (Some(..), Err(e)) => { - println!("{}", e); + println!("{e}"); unreachable!("unexpected parse result") } (None, Ok((i, x))) => { - println!("input: {:?}", i); + println!("input: {i:?}"); println!("expected parse error"); - println!("detected: {:?}", x); + println!("detected: {x:?}"); unreachable!("unexpected parse result") } (None, Err(_e)) => (), diff --git a/src/parser/error.rs b/src/parser/error.rs index f440b268cc..8e83481594 100644 --- a/src/parser/error.rs +++ b/src/parser/error.rs @@ -5,7 +5,7 @@ use nom::{error::ErrorKind, AsBytes, Err, IResult, InputLength}; use crate::parser::{base, Span}; -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum ParseErrorKind { InvalidBase16EscapeSequence(String, Option), InvalidBaseEncoding(base::LitBase), @@ -15,18 +15,19 @@ pub enum ParseErrorKind { ParseIntErr(ParseIntError), InvalidChar(String), Nom(ErrorKind), + InterningError(String), } impl fmt::Display for ParseErrorKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::InvalidBase16EscapeSequence(seq, _) => { - write!(f, "Unknown base 16 string escape sequence {}.", seq) + write!(f, "Unknown base 16 string escape sequence {seq}.") } Self::ParseIntErr(e) => { - write!(f, "Error parsing number: {}", e) + write!(f, "Error parsing number: {e}") } - e => write!(f, "internal parser error {:?}", e), + e => write!(f, "internal parser error {e:?}"), } } } @@ -37,7 +38,7 @@ impl ParseErrorKind { } } -#[derive(PartialEq, Debug, Clone)] +#[derive(PartialEq, Eq, Debug, Clone)] pub struct ParseError { pub input: I, pub expected: Option<&'static str>, @@ -97,7 +98,7 @@ impl<'a, F: LurkField> fmt::Display for ParseError, F> { writeln!(&mut res, "^")?; if let Some(exp) = self.expected { - writeln!(&mut res, "Expected {}", exp)?; + writeln!(&mut res, "Expected {exp}")?; } let mut errs = self.errors.iter().filter(|x| !x.is_nom_err()).peekable(); @@ -108,12 +109,12 @@ impl<'a, F: LurkField> fmt::Display for ParseError, F> { Some(_) => { writeln!(&mut res, "Reported errors:")?; for kind in errs { - writeln!(&mut res, "- {}", kind)?; + writeln!(&mut res, "- {kind}")?; } } } - write!(f, "{}", res) + write!(f, "{res}") } } diff --git a/src/parser/position.rs b/src/parser/position.rs index cb4e7512e0..1aa8f688eb 100644 --- a/src/parser/position.rs +++ b/src/parser/position.rs @@ -47,8 +47,8 @@ impl Pos { upto_column: usize, ) -> String { let mut res = String::new(); - let gutter = format!("{}", upto_line).len(); - let pad = format!("{: >gutter$}", from_line, gutter = gutter).len() + 3 + from_column; + let gutter = format!("{upto_line}").len(); + let pad = format!("{from_line: >gutter$}").len() + 3 + from_column; res.push_str(&format!("{}â–¼\n", " ".to_owned().repeat(pad))); for (line_number, line) in input.lines().enumerate() { if ((line_number + 1) >= from_line) && ((line_number + 1) <= upto_line) { @@ -60,7 +60,7 @@ impl Pos { )); } } - let pad = format!("{: >gutter$}", upto_line, gutter = gutter).len() + 3 + upto_column; + let pad = format!("{upto_line: >gutter$}").len() + 3 + upto_column; res.push_str(&format!("{}â–²", " ".to_owned().repeat(pad))); res } diff --git a/src/parser/string.rs b/src/parser/string.rs index dfb041d5d7..dba8f83643 100644 --- a/src/parser/string.rs +++ b/src/parser/string.rs @@ -32,7 +32,7 @@ pub fn parse_unicode<'a, F: LurkField>() -> impl Fn(Span<'a>) -> ParseResult<'a, char('}'), ), )(from)?; - let hex: String = hex.fragment().to_string(); + let hex: String = (*hex.fragment()).to_string(); let (i, x) = ParseError::res(u32::from_str_radix(&hex, 16), i, |x| { ParseErrorKind::InvalidBase16EscapeSequence(hex.clone(), Some(x)) })?; @@ -212,19 +212,19 @@ pub mod tests { match (expected, p.parse(Span::new(i))) { (Some(expected), Ok((_i, x))) if x == expected => (), (Some(expected), Ok((i, x))) => { - println!("input: {:?}", i); - println!("expected: {:?}", expected); - println!("detected: {:?}", x); + println!("input: {i:?}"); + println!("expected: {expected:?}"); + println!("detected: {x:?}"); unreachable!("unexpected parse result") } (Some(..), Err(e)) => { - println!("{}", e); + println!("{e}"); unreachable!("unexpected parse result") } (None, Ok((i, x))) => { - println!("input: {:?}", i); + println!("input: {i:?}"); println!("expected parse error"); - println!("detected: {:?}", x); + println!("detected: {x:?}"); unreachable!("unexpected parse result") } (None, Err(_e)) => (), diff --git a/src/parser/syntax.rs b/src/parser/syntax.rs index 0cc12004ad..fb0fbd4d15 100644 --- a/src/parser/syntax.rs +++ b/src/parser/syntax.rs @@ -1,4 +1,5 @@ -use crate::field::LurkField; +use std::{cell::RefCell, rc::Rc}; + use nom::{ branch::alt, bytes::complete::{tag, take_till}, @@ -8,17 +9,20 @@ use nom::{ multi::{many0, many_till, separated_list1}, sequence::{delimited, preceded, terminated}, }; +use nom_locate::LocatedSpan; use crate::{ + field::LurkField, num::Num, + package::SymbolRef, parser::{ base, error::{ParseError, ParseErrorKind}, position::Pos, string, ParseResult, Span, }, + state::{meta_package_symbol, State}, symbol, - symbol::Symbol, syntax::Syntax, uint::UInt, }; @@ -45,12 +49,12 @@ pub fn parse_symbol_limb( ) -> impl Fn(Span<'_>) -> ParseResult<'_, F, String> { move |from: Span<'_>| { let (i, s) = alt(( + string::parse_string_inner1(symbol::SYM_SEPARATOR, false, escape), delimited( tag("|"), string::parse_string_inner1('|', true, "|"), tag("|"), ), - string::parse_string_inner1(symbol::SYM_SEPARATOR, false, escape), value(String::from(""), peek(tag("."))), ))(from)?; Ok((i, s)) @@ -62,12 +66,12 @@ pub fn parse_symbol_limb_raw( ) -> impl Fn(Span<'_>) -> ParseResult<'_, F, String> { move |from: Span<'_>| { let (i, s) = alt(( + string::parse_string_inner1(' ', false, escape), delimited( tag("|"), string::parse_string_inner1('|', true, "|"), tag("|"), ), - string::parse_string_inner1(' ', false, escape), value(String::from(""), peek(tag("."))), ))(from)?; Ok((i, s)) @@ -85,61 +89,116 @@ pub fn parse_symbol_limbs() -> impl Fn(Span<'_>) -> ParseResult<'_ } } -pub fn parse_absolute_symbol() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Symbol> { +fn intern_path<'a, F: LurkField>( + state: Rc>, + upto: LocatedSpan<&'a str>, + path: &[String], + keyword: Option, + create_unknown_packages: bool, +) -> ParseResult<'a, F, SymbolRef> { + use nom::Err::Failure; + match keyword { + Some(keyword) => state + .borrow_mut() + .intern_path(path, keyword, create_unknown_packages), + None => state + .borrow_mut() + .intern_relative_path(path, create_unknown_packages), + } + .map(|symbol| (upto, symbol)) + .map_err(|e| { + Failure(ParseError::new( + upto, + ParseErrorKind::InterningError(format!("{e}")), + )) + }) +} + +pub fn parse_absolute_symbol( + state: Rc>, + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, SymbolRef> { move |from: Span<'_>| { let (i, is_key) = alt(( value(false, char(symbol::SYM_MARKER)), value(true, char(symbol::KEYWORD_MARKER)), ))(from)?; let (upto, path) = parse_symbol_limbs()(i)?; - if is_key { - Ok((upto, Symbol::new(&["keyword"]).extend(&path))) - } else { - Ok((upto, Symbol { path })) - } + intern_path( + state.clone(), + upto, + &path, + Some(is_key), + create_unknown_packages, + ) } } pub fn parse_relative_symbol( - parent: Symbol, -) -> impl Fn(Span<'_>) -> ParseResult<'_, F, Symbol> { + state: Rc>, + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, SymbolRef> { move |from: Span<'_>| { let (i, _) = peek(none_of(",~#(){}[]1234567890."))(from)?; let (upto, path) = parse_symbol_limbs()(i)?; - Ok((upto, parent.extend(&path))) + intern_path(state.clone(), upto, &path, None, create_unknown_packages) } } -pub fn parse_raw_symbol() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Symbol> { +pub fn parse_raw_symbol( + state: Rc>, + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, SymbolRef> { move |from: Span<'_>| { let (i, _) = tag("~(")(from)?; - let (i, path) = many0(preceded(parse_space, parse_symbol_limb_raw("|()")))(i)?; + let (i, mut path) = many0(preceded(parse_space, parse_symbol_limb_raw("|()")))(i)?; let (upto, _) = many_till(parse_space, tag(")"))(i)?; - Ok((upto, Symbol { path })) + path.reverse(); + intern_path( + state.clone(), + upto, + &path, + Some(false), + create_unknown_packages, + ) } } -// raw: ~(foo bar baz) = .|foo|.|bar|.|baz| -// absolute: .foo.bar.baz (escaped limbs: .|foo|.|bar|.|baz|) -// keyword: :foo.bar = .keyword.foo.bar -// relative: foo.bar +pub fn parse_raw_keyword( + state: Rc>, + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, SymbolRef> { + move |from: Span<'_>| { + let (i, _) = tag("~:(")(from)?; + let (i, mut path) = many0(preceded(parse_space, parse_symbol_limb_raw("|()")))(i)?; + let (upto, _) = many_till(parse_space, tag(")"))(i)?; + path.reverse(); + intern_path( + state.clone(), + upto, + &path, + Some(true), + create_unknown_packages, + ) + } +} -pub fn parse_symbol() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { +/// relative: foo.bar +/// absolute: .foo.bar.baz, :foo.bar (escaped limbs: .|foo|.|bar|.|baz|) +/// raw: ~(foo bar baz) = .baz.bar.foo +/// raw keyword: ~:(foo bar) = :bar.foo +pub fn parse_symbol( + state: Rc>, + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { move |from: Span<'_>| { let (upto, sym) = alt(( - parse_raw_symbol(), - parse_absolute_symbol(), - // temporary root argument until packages are reimplemented - parse_relative_symbol(Symbol::root()), + parse_relative_symbol(state.clone(), create_unknown_packages), + parse_absolute_symbol(state.clone(), create_unknown_packages), + parse_raw_symbol(state.clone(), create_unknown_packages), + parse_raw_keyword(state.clone(), create_unknown_packages), ))(from)?; - let pos = Pos::from_upto(from, upto); - if let Some(val) = Symbol::lurk_syms().get(&Symbol::lurk_sym(&format!("{}", sym))) { - Ok((upto, Syntax::LurkSym(pos, *val))) - } else if sym.is_keyword() { - Ok((upto, Syntax::Keyword(pos, sym))) - } else { - Ok((upto, Syntax::Symbol(pos, sym))) - } + Ok((upto, Syntax::Symbol(Pos::from_upto(from, upto), sym))) } } @@ -171,7 +230,7 @@ fn f_from_le_bytes(bs: &[u8]) -> F { let mut res = F::ZERO; let mut bs = bs.iter().rev().peekable(); while let Some(b) = bs.next() { - let b: F = (*b as u64).into(); + let b: F = u64::from(*b).into(); if bs.peek().is_none() { res.add_assign(b) } else { @@ -262,14 +321,48 @@ pub fn parse_char() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syn } } -pub fn parse_list() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { +pub fn parse_list( + state: Rc>, + meta: bool, + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { move |from: Span<'_>| { let (i, _) = tag("(")(from)?; - //let (i, _) = parse_space(i)?; - let (i, xs) = many0(preceded(parse_space, parse_syntax()))(i)?; + let (i, xs) = if meta { + // parse the head symbol in the meta package + let saved_package = state.borrow().get_current_package_name().clone(); + state + .borrow_mut() + .set_current_package(meta_package_symbol().into()) + .expect("meta package is available"); + let (i, h) = preceded( + parse_space, + parse_symbol(state.clone(), create_unknown_packages), + )(i)?; + // then recover the previous package + state + .borrow_mut() + .set_current_package(saved_package) + .expect("previous package is available"); + let (i, t) = many0(preceded( + parse_space, + parse_syntax(state.clone(), false, create_unknown_packages), + ))(i)?; + let mut xs = vec![h]; + xs.extend(t); + (i, xs) + } else { + many0(preceded( + parse_space, + parse_syntax(state.clone(), false, create_unknown_packages), + ))(i)? + }; let (i, end) = opt(preceded( preceded(parse_space, tag(".")), - preceded(parse_space, parse_syntax()), + preceded( + parse_space, + parse_syntax(state.clone(), false, create_unknown_packages), + ), ))(i)?; let (i, _) = parse_space(i)?; let (upto, _) = tag(")")(i)?; @@ -282,14 +375,17 @@ pub fn parse_list() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syn } } -pub fn parse_quote() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { +pub fn parse_quote( + state: Rc>, + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { move |from: Span<'_>| { let (i, c) = opt(parse_char())(from)?; if let Some(c) = c { Ok((i, c)) } else { let (i, _) = tag("'")(from)?; - let (upto, s) = parse_syntax()(i)?; + let (upto, s) = parse_syntax(state.clone(), false, create_unknown_packages)(i)?; let pos = Pos::from_upto(from, upto); Ok((upto, Syntax::Quote(pos, Box::new(s)))) } @@ -297,21 +393,34 @@ pub fn parse_quote() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Sy } // top-level syntax parser -pub fn parse_syntax() -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { +pub fn parse_syntax( + state: Rc>, + meta: bool, + // this parameter triggers a less strict mode for testing purposes + create_unknown_packages: bool, +) -> impl Fn(Span<'_>) -> ParseResult<'_, F, Syntax> { move |from: Span<'_>| { alt(( - parse_hash_char(), + context( + "list", + parse_list(state.clone(), meta, create_unknown_packages), + ), parse_uint(), parse_num(), - context("symbol", parse_symbol()), + context( + "symbol", + parse_symbol(state.clone(), create_unknown_packages), + ), parse_string(), - context("quote", parse_quote()), - context("list", parse_list()), + context("quote", parse_quote(state.clone(), create_unknown_packages)), + parse_hash_char(), ))(from) } } pub fn parse_maybe_meta( + state: Rc>, + create_unknown_packages: bool, ) -> impl Fn(Span<'_>) -> ParseResult<'_, F, Option<(bool, Syntax)>> { move |from: Span<'_>| { let (_, is_eof) = opt(nom::combinator::eof)(from)?; @@ -319,26 +428,20 @@ pub fn parse_maybe_meta( return Ok((from, None)); } let (next, meta) = opt(char('!'))(from)?; - if meta.is_some() { - let (end, syntax) = parse_syntax()(next)?; - Ok((end, Some((true, syntax)))) - } else { - let (end, syntax) = parse_syntax()(from)?; - Ok((end, Some((false, syntax)))) - } + let meta = meta.is_some(); + let (end, syntax) = parse_syntax(state.clone(), meta, create_unknown_packages)(next)?; + Ok((end, Some((meta, syntax)))) } } #[cfg(test)] pub mod tests { - use crate::symbol::LurkSym; use blstrs::Scalar; use nom::Parser; #[cfg(not(target_arch = "wasm32"))] use proptest::prelude::*; use super::*; - #[allow(unused_imports)] use crate::{char, keyword, list, num, str, symbol, uint}; fn test<'a, P, R>(mut p: P, i: &'a str, expected: Option) -> bool @@ -348,17 +451,7 @@ pub mod tests { { match (expected, p.parse(Span::<'a>::new(i))) { (Some(expected), Ok((_, x))) if x == expected => true, - (Some(_), Ok(..)) => { - // println!("input: {:?}", i); - // println!("expected: {} {:?}", expected.clone(), expected); - // println!("detected: {} {:?}", x.clone(), x); - false - } - (Some(..), Err(_)) => { - // println!("{}", e); - false - } - (None, Ok(..)) => { + (Some(_) | None, Ok(..)) | (Some(..), Err(_)) => { // println!("input: {:?}", i); // println!("expected parse error"); // println!("detected: {:?}", x); @@ -382,169 +475,257 @@ pub mod tests { #[test] fn unit_parse_symbol() { - assert!(test(parse_raw_symbol(), "", None)); - assert!(test(parse_absolute_symbol(), "", None)); - assert!(test(parse_relative_symbol(Symbol::root()), "", None)); - assert!(test(parse_symbol(), "", None)); - assert!(test(parse_symbol(), "~()", Some(symbol!([])))); - assert!(test(parse_symbol(), ".", None)); - assert!(test(parse_symbol(), "..", Some(symbol!([""])))); - assert!(test(parse_symbol(), "foo", Some(symbol!(["foo"])))); - assert!(test(parse_symbol(), "|foo|", Some(symbol!(["foo"])))); - assert!(test( - parse_symbol(), + let state_ = State::default().rccell(); + let state = || state_.clone(); + assert!(test(parse_raw_symbol(state(), true), "", None)); + assert!(test(parse_absolute_symbol(state(), true), "", None)); + assert!(test(parse_relative_symbol(state(), true), "", None)); + assert!(test(parse_relative_symbol(state(), true), "", None)); + assert!(test(parse_symbol(state(), true), "", None)); + assert!(test(parse_symbol(state(), true), "~()", Some(symbol!([])))); + assert!(test(parse_symbol(state(), true), ".", None)); + assert!(test(parse_symbol(state(), true), "..", Some(symbol!([""])))); + assert!(test( + parse_symbol(state(), true), + "foo", + Some(symbol!(["foo"])) + )); + assert!(test( + parse_symbol(state(), true), + "|foo|", + Some(symbol!(["foo"])) + )); + assert!(test( + parse_symbol(state(), true), "|Hi, bye|", Some(symbol!(["Hi, bye"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), "|foo|.|bar|", Some(symbol!(["foo", "bar"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ".|foo|.|bar|", Some(symbol!(["foo", "bar"])) )); - assert!(test(parse_symbol(), ".foo", Some(symbol!(["foo"])))); - assert!(test(parse_symbol(), "..foo", Some(symbol!(["", "foo"])))); - assert!(test(parse_symbol(), "foo.", Some(symbol!(["foo"])))); - assert!(test(parse_symbol(), "foo..", Some(symbol!(["foo", ""])))); - assert!(test(parse_symbol(), ".foo..", Some(symbol!(["foo", ""])))); - assert!(test(parse_symbol(), ".foo..", Some(symbol!(["foo", ""])))); assert!(test( - parse_symbol(), + parse_symbol(state(), true), + ".foo", + Some(symbol!(["foo"])) + )); + assert!(test( + parse_symbol(state(), true), + "..foo", + Some(symbol!(["", "foo"])) + )); + assert!(test( + parse_symbol(state(), true), + "foo.", + Some(symbol!(["foo"])) + )); + assert!(test( + parse_symbol(state(), true), + "foo..", + Some(symbol!(["foo", ""])) + )); + assert!(test( + parse_symbol(state(), true), + ".foo..", + Some(symbol!(["foo", ""])) + )); + assert!(test( + parse_symbol(state(), true), + ".foo..", + Some(symbol!(["foo", ""])) + )); + assert!(test( + parse_symbol(state(), true), ".foo.bar", Some(symbol!(["foo", "bar"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ".foo?.bar?", Some(symbol!(["foo?", "bar?"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ".fooλ.barλ", Some(symbol!(["fooλ", "barλ"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ".foo\\n.bar\\n", Some(symbol!(["foo\n", "bar\n"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ".foo\\u{00}.bar\\u{00}", Some(symbol!(["foo\u{00}", "bar\u{00}"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ".foo\\.bar", Some(symbol!(["foo.bar"])) )); assert!(test( - parse_symbol(), - "nil", - Some(Syntax::LurkSym(Pos::No, LurkSym::Nil)) + parse_symbol(state(), true), + "~(asdf )", + Some(symbol!(["asdf"])) + )); + assert!(test( + parse_symbol(state(), true), + "~( asdf )", + Some(symbol!(["asdf"])) + )); + assert!(test( + parse_symbol(state(), true), + "~( asdf)", + Some(symbol!(["asdf"])) )); - assert!(test(parse_symbol(), "~(asdf )", Some(symbol!(["asdf"])))); - assert!(test(parse_symbol(), "~( asdf )", Some(symbol!(["asdf"])))); - assert!(test(parse_symbol(), "~( asdf)", Some(symbol!(["asdf"])))); assert!(test( - parse_symbol(), + parse_symbol(state(), true), "~(asdf.fdsa)", Some(symbol!(["asdf.fdsa"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), "~(asdf.fdsa arst)", - Some(symbol!(["asdf.fdsa", "arst"])) + Some(symbol!(["arst", "asdf.fdsa"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), "~(asdf.fdsa arst |wfp qwf|)", - Some(symbol!(["asdf.fdsa", "arst", "wfp qwf"])) + Some(symbol!(["wfp qwf", "arst", "asdf.fdsa"])) )); } #[test] fn unit_parse_keyword() { - assert!(test(parse_symbol(), "", None)); - assert!(test(parse_symbol(), ":", None)); - assert!(test(parse_symbol(), ":.", Some(keyword!([""])))); - assert!(test(parse_symbol(), ":foo", Some(keyword!(["foo"])))); - assert!(test(parse_symbol(), ":foo.", Some(keyword!(["foo"])))); - assert!(test(parse_symbol(), ":foo..", Some(keyword!(["foo", ""])))); - assert!(test( - parse_symbol(), + let state_ = State::default().rccell(); + let state = || state_.clone(); + assert!(test(parse_symbol(state(), true), "", None)); + assert!(test(parse_symbol(state(), true), ":", None)); + assert!(test( + parse_symbol(state(), true), + "~:()", + Some(keyword!([])) + )); + assert!(test( + parse_symbol(state(), true), + ":.", + Some(keyword!([""])) + )); + assert!(test( + parse_symbol(state(), true), + ":foo", + Some(keyword!(["foo"])) + )); + assert!(test( + parse_symbol(state(), true), + ":foo.", + Some(keyword!(["foo"])) + )); + assert!(test( + parse_symbol(state(), true), + ":foo..", + Some(keyword!(["foo", ""])) + )); + assert!(test( + parse_symbol(state(), true), ":foo.bar", Some(keyword!(["foo", "bar"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ":foo?.bar?", Some(keyword!(["foo?", "bar?"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ":fooλ.barλ", Some(keyword!(["fooλ", "barλ"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ":foo\\n.bar\\n", Some(keyword!(["foo\n", "bar\n"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ":foo\\u{00}.bar\\u{00}", Some(keyword!(["foo\u{00}", "bar\u{00}"])) )); assert!(test( - parse_symbol(), + parse_symbol(state(), true), ":foo\\.bar", Some(keyword!(["foo.bar"])) )); + assert!(test( + parse_symbol(state(), true), + "~:(x y z)", + Some(keyword!(["z", "y", "x"])) + )); } #[test] fn unit_parse_list() { - assert!(test(parse_list(), "()", Some(list!([])))); - assert!(test(parse_list(), "(1 2)", Some(list!([num!(1), num!(2)])),)); - assert!(test(parse_list(), "(1)", Some(list!([num!(1)])),)); - assert!(test(parse_list(), "(a)", Some(list!([symbol!(["a"])])),)); + let state_ = State::default().rccell(); + let state = || state_.clone(); + assert!(test( + parse_list(state(), false, true), + "()", + Some(list!([])) + )); + assert!(test( + parse_list(state(), false, true), + "(1 2)", + Some(list!([num!(1), num!(2)])), + )); + assert!(test( + parse_list(state(), false, true), + "(1)", + Some(list!([num!(1)])), + )); + assert!(test( + parse_list(state(), false, true), + "(a)", + Some(list!([symbol!(["a"])])), + )); assert!(test( - parse_list(), + parse_list(state(), false, true), "(a b)", Some(list!([symbol!(["a"]), symbol!(["b"])])), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(.a .b)", Some(list!([symbol!(["a"]), symbol!(["b"])])), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(.foo.bar .foo.bar)", Some(list!([symbol!(["foo", "bar"]), symbol!(["foo", "bar"])])), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(a . b)", Some(list!([symbol!(["a"])], symbol!(["b"]))), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(.a . .b)", Some(list!([symbol!(["a"])], symbol!(["b"]))), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(a b . c)", Some(list!([symbol!(["a"]), symbol!(["b"])], symbol!(["c"]))), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(a . (b . c))", Some(list!( [symbol!(["a"])], @@ -552,23 +733,22 @@ pub mod tests { )) )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(a b c)", Some(list!([symbol!(["a"]), symbol!(["b"]), symbol!(["c"])])), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "('a' 'b' 'c')", Some(list!([char!('a'), char!('b'), char!('c')])), )); - assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(a. b. c.)", Some(list!([symbol!(["a"]), symbol!(["b"]), symbol!(["c"])])), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(a.. b.. c..)", Some(list!([ symbol!(["a", ""]), @@ -587,34 +767,60 @@ pub mod tests { assert!(test(parse_char(), "'('", None)); assert!(test(parse_char(), "'\\('", Some(char!('(')))); } + #[test] fn unit_parse_hash_char() { + let state_ = State::default().rccell(); + let state = || state_.clone(); assert!(test(parse_hash_char(), "#\\a", Some(char!('a')))); assert!(test(parse_hash_char(), "#\\b", Some(char!('b')))); assert!(test(parse_hash_char(), r"#\b", Some(char!('b')))); assert!(test(parse_hash_char(), "#\\u{8f}", Some(char!('\u{8f}')))); - assert!(test(parse_syntax(), "#\\a", Some(char!('a')))); - assert!(test(parse_syntax(), "#\\b", Some(char!('b')))); - assert!(test(parse_syntax(), r"#\b", Some(char!('b')))); - assert!(test(parse_syntax(), r"#\u{8f}", Some(char!('\u{8f}')))); + assert!(test( + parse_syntax(state(), false, false), + "#\\a", + Some(char!('a')) + )); + assert!(test( + parse_syntax(state(), false, false), + "#\\b", + Some(char!('b')) + )); + assert!(test( + parse_syntax(state(), false, false), + r"#\b", + Some(char!('b')) + )); + assert!(test( + parse_syntax(state(), false, false), + r"#\u{8f}", + Some(char!('\u{8f}')) + )); } #[test] fn unit_parse_quote() { + let state_ = State::default().rccell(); + let state = || state_.clone(); assert!(test( - parse_quote(), - "'a", + parse_quote(state(), true), + "'.a", Some(Syntax::Quote(Pos::No, Box::new(symbol!(["a"])))) )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), + "':a", + Some(Syntax::Quote(Pos::No, Box::new(keyword!(["a"])))) + )); + assert!(test( + parse_syntax(state(), false, true), "'a", Some(Syntax::Quote(Pos::No, Box::new(symbol!(["a"])))) )); - assert!(test(parse_quote(), "'a'", Some(char!('a')))); - assert!(test(parse_quote(), "'a'", Some(char!('a')))); + assert!(test(parse_quote(state(), true), "'a'", Some(char!('a')))); + assert!(test(parse_quote(state(), true), "'a'", Some(char!('a')))); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "'(a b)", Some(Syntax::Quote( Pos::No, @@ -622,13 +828,12 @@ pub mod tests { )) )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "('a)", Some(list!([Syntax::Quote(Pos::No, Box::new(symbol!(['a'])))])) )); - assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "('a' 'b' 'c')", Some(list!([char!('a'), char!('b'), char!('c')])), )); @@ -693,60 +898,68 @@ pub mod tests { .into_iter() .rev() .collect(); + let state_ = State::default().rccell(); + let state = || state_.clone(); + assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(0x6e2e5055dcf61486b03bb80ed2b3f1a35c30e122defebae824fae4ed32408e87)", Some(list!([num!(Num::Scalar(f_from_le_bytes(&vec)))])), )); - - assert!(test(parse_syntax(), ".\\.", Some(symbol!(["."])))); - assert!(test(parse_syntax(), ".\\'", Some(symbol!(["'"])))); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), + ".\\.", + Some(symbol!(["."])) + )); + assert!(test( + parse_syntax(state(), false, true), + ".\\'", + Some(symbol!(["'"])) + )); + assert!(test( + parse_syntax(state(), false, true), ".\\'\\u{8e}\\u{fffc}\\u{201b}", Some(symbol!(["'\u{8e}\u{fffc}\u{201b}"])), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(lambda (🚀) 🚀)", Some(list!([ - Syntax::LurkSym(Pos::No, LurkSym::Lambda), + symbol!(["lambda"]), list!([symbol!(["🚀"])]), symbol!(["🚀"]) ])), )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "11242421860377074631u64", Some(uint!(11242421860377074631)) )); - assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), ":\u{ae}\u{60500}\u{87}..)", Some(keyword!(["®\u{60500}\u{87}", ""])) )); assert!(test( - parse_syntax(), - "(.keyword 11242421860377074631u64 . :\u{ae}\u{60500}\u{87}..)", + parse_syntax(state(), false, true), + "(~:() 11242421860377074631u64 . :\u{ae}\u{60500}\u{87}..)", Some(list!( [keyword!([]), uint!(11242421860377074631)], keyword!(["®\u{60500}\u{87}", ""]) )) )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "((\"\"))", Some(list!([list!([Syntax::String(Pos::No, "".to_string())])])) )); - assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "((=))", - Some(list!([list!([Syntax::LurkSym(Pos::No, LurkSym::OpEql)])])) + Some(list!([list!([symbol!(["="])])])) )); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "('.. . a)", Some(list!( [Syntax::Quote(Pos::No, Box::new(symbol!([""])))], @@ -754,11 +967,11 @@ pub mod tests { )) )); assert_eq!( - "(.. . a)", + "(.. . .a)", format!("{}", list!(Scalar, [symbol!([""])], symbol!(["a"]))) ); assert_eq!( - "('.. . a)", + "('.. . .a)", format!( "{}", list!( @@ -768,30 +981,35 @@ pub mod tests { ) ) ); - assert!(test(parse_syntax(), "'\\('", Some(char!('(')))); + assert!(test( + parse_syntax(state(), false, true), + "'\\('", + Some(char!('(')) + )); assert_eq!("'\\('", format!("{}", char!(Scalar, '('))); assert_eq!( - "(' ' . a)", + "(' ' . .a)", format!("{}", list!(Scalar, [char!(' ')], symbol!(["a"]))) ); assert!(test( - parse_syntax(), + parse_syntax(state(), false, true), "(' ' . a)", Some(list!([char!(' ')], symbol!(["a"]))) )); - assert!(test(parse_syntax(), "(cons # \"\")", None)); - assert!(test(parse_syntax(), "#", None)); + assert!(test( + parse_syntax(state(), false, true), + "(cons # \"\")", + None + )); + assert!(test(parse_syntax(state(), false, true), "#", None)); } #[test] fn test_minus_zero_symbol() { let x: Syntax = symbol!(["-0"]); - let text = format!("{}", x); - let (_, res) = parse_syntax()(Span::new(&text)).expect("valid parse"); - // eprintln!("------------------"); - // eprintln!("{}", text); - // eprintln!("{} {:?}", x, x); - // eprintln!("{} {:?}", res, res); + let text = format!("{x}"); + let (_, res) = parse_syntax(State::default().rccell(), false, true)(Span::new(&text)) + .expect("valid parse"); assert_eq!(x, res) } @@ -799,10 +1017,7 @@ pub mod tests { #[test] fn prop_syntax(x in any::>()) { let text = format!("{}", x); - let (_, res) = parse_syntax()(Span::new(&text)).expect("valid parse"); - // eprintln!("------------------"); - // eprintln!("x {} {:?}", x, x); - // eprintln!("res {} {:?}", res, res); + let (_, res) = parse_syntax(State::default().rccell(), false, true)(Span::new(&text)).expect("valid parse"); assert_eq!(x, res) } } diff --git a/src/proof/groth16.rs b/src/proof/groth16.rs index 4b65a8bfd4..c5dbb34a25 100644 --- a/src/proof/groth16.rs +++ b/src/proof/groth16.rs @@ -1,15 +1,13 @@ +use bellpepper_core::SynthesisError; #[cfg(not(target_arch = "wasm32"))] use bellperson::groth16::aggregate::setup_fake_srs; -use bellperson::{ - groth16::{ - self, - aggregate::{ - aggregate_proofs_and_instances, verify_aggregate_proof_and_aggregate_instances, - AggregateProofAndInstance, AggregateVersion, GenericSRS, VerifierSRS, - }, - verify_proof, +use bellperson::groth16::{ + self, + aggregate::{ + aggregate_proofs_and_instances, verify_aggregate_proof_and_aggregate_instances, + AggregateProofAndInstance, AggregateVersion, GenericSRS, VerifierSRS, }, - SynthesisError, + verify_proof, }; use blstrs::{Bls12, Scalar}; #[cfg(not(target_arch = "wasm32"))] @@ -25,9 +23,9 @@ use std::sync::Arc; use crate::circuit::MultiFrame; use crate::coprocessor::Coprocessor; use crate::error::ProofError; -use crate::eval::{lang::Lang, Evaluator, Witness, IO}; +use crate::eval::{lang::Lang, Meta, IO}; use crate::field::LurkField; -use crate::proof::{Provable, Prover, PublicParameters}; +use crate::proof::{supernova::FoldingConfig, Provable, Prover, PublicParameters}; use crate::ptr::Ptr; use crate::store::Store; @@ -77,7 +75,7 @@ fn load_srs() -> Result, io::Error> { } /// A struct representing a proof using the Groth16 proving system with the specified engine. -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Proof where ::Gt: blstrs::Compress + Serialize, @@ -105,8 +103,10 @@ impl> Groth16Prover { reduction_count: usize, lang: Arc>, ) -> Result, SynthesisError> { - let multiframe: MultiFrame<'_, Scalar, IO, Witness, C> = - MultiFrame::blank(reduction_count, lang); + let multiframe: MultiFrame<'_, Scalar, C> = MultiFrame::blank( + Arc::new(FoldingConfig::new_ivc(lang, reduction_count)), + Meta::Lurk, + ); // WARNING: These parameters are totally bogus. Real Groth16 parameters need to be // generated by a trusted setup. We create them *deterministically* from a seeded RNG @@ -120,7 +120,7 @@ impl> Groth16Prover { /// Generates a Groth16 proof using the given multi_frame, parameters, and random number generator. pub fn prove( &self, - multi_frame: MultiFrame<'_, Scalar, IO, Witness, C>, + multi_frame: MultiFrame<'_, Scalar, C>, params: &groth16::Parameters, mut rng: R, ) -> Result, SynthesisError> { @@ -129,7 +129,6 @@ impl> Groth16Prover { /// Generates an outer Groth16 proof using the given parameters, SRS, expression, environment, /// store, limit, and random number generator. - #[allow(clippy::too_many_arguments)] pub fn outer_prove( &self, params: &groth16::Parameters, @@ -141,11 +140,11 @@ impl> Groth16Prover { mut rng: R, lang: Arc>, ) -> Result<(Proof, IO, IO), ProofError> { - let padding_predicate = |count| self.needs_frame_padding(count); - let frames = Evaluator::generate_frames(expr, env, store, limit, padding_predicate, &lang)?; - store.hydrate_scalar_cache(); - - let multiframes = MultiFrame::from_frames(self.reduction_count(), &frames, store, &lang); + let frames = self.get_evaluation_frames(expr, env, store, limit, lang.clone())?; + let reduction_count = self.reduction_count(); + let folding_config = Arc::new(FoldingConfig::new_ivc(lang, reduction_count)); + let multiframes = + MultiFrame::from_frames(reduction_count, &frames, store, folding_config.clone()); let mut proofs = Vec::with_capacity(multiframes.len()); let mut statements = Vec::with_capacity(multiframes.len()); @@ -157,7 +156,7 @@ impl> Groth16Prover { let mut multiframe_proofs = Vec::with_capacity(multiframes_count); let last_multiframe = multiframes.last().unwrap().clone(); - for multiframe in multiframes.into_iter() { + for multiframe in multiframes { statements.push(multiframe.public_inputs()); let proof = self.prove(multiframe.clone(), params, &mut rng).unwrap(); @@ -170,7 +169,8 @@ impl> Groth16Prover { self.reduction_count(), last_multiframe.frames.and_then(|x| x.last().cloned()), store, - lang, + folding_config, + Meta::Lurk, ); let dummy_proof = self @@ -213,7 +213,7 @@ impl> Groth16Prover { /// Verifies a single Groth16 proof using the given multi_frame, prepared verifier key, and proof. pub fn verify_groth16_proof( // multiframe need not have inner frames populated for verification purposes. - multiframe: &MultiFrame<'_, Scalar, IO, Witness, C>, + multiframe: &MultiFrame<'_, Scalar, C>, pvk: &groth16::PreparedVerifyingKey, proof: &groth16::Proof, ) -> Result { @@ -246,6 +246,7 @@ impl> Groth16Prover { /// A prover struct for the Groth16 proving system. /// Implements the crate::Prover trait. +#[derive(Debug)] pub struct Groth16Prover, F: LurkField> { reduction_count: usize, lang: Lang, @@ -258,7 +259,7 @@ pub struct PublicParams(pub groth16::Parameters) impl PublicParameters for PublicParams {} -impl<'a, 'b, C: Coprocessor> Prover<'a, 'b, Scalar, C> for Groth16Prover { +impl<'a, C: Coprocessor> Prover<'a, Scalar, C> for Groth16Prover { type PublicParams = PublicParams; fn new(reduction_count: usize, lang: Lang) -> Self { @@ -278,15 +279,7 @@ impl<'a, 'b, C: Coprocessor> Prover<'a, 'b, Scalar, C> for Groth16Prover } } -impl> - MultiFrame< - '_, - ::Fr, - IO<::Fr>, - Witness<::Fr>, - C, - > -{ +impl> MultiFrame<'_, ::Fr, C> { /// Verify a Groth16 Lurk proof. pub fn verify_groth16_proof( self, @@ -300,10 +293,7 @@ impl> #[allow(dead_code)] fn verify_sequential_groth16_proofs>( - multiframe_proofs: &[( - MultiFrame<'_, Scalar, IO, Witness, C>, - groth16::Proof, - )], + multiframe_proofs: &[(MultiFrame<'_, Scalar, C>, groth16::Proof)], vk: &groth16::VerifyingKey, ) -> Result { let pvk = groth16::prepare_verifying_key(vk); @@ -329,13 +319,12 @@ fn verify_sequential_groth16_proofs>( mod tests { use super::*; use crate::circuit::ToInputs; - use crate::eval::{empty_sym_env, lang::Coproc, Frame}; + use crate::eval::{empty_sym_env, lang::Coproc, Evaluator, Frame}; + use crate::lurk_sym_ptr; use crate::proof::{verify_sequential_css, SequentialCS}; - use bellperson::{ - groth16::aggregate::verify_aggregate_proof_and_aggregate_instances, - util_cs::{metric_cs::MetricCS, Comparable, Delta}, - Circuit, - }; + use bellpepper::util_cs::{metric_cs::MetricCS, Comparable}; + use bellpepper_core::{Circuit, Delta}; + use bellperson::groth16::aggregate::verify_aggregate_proof_and_aggregate_instances; use blstrs::Scalar as Fr; use rand::rngs::OsRng; @@ -403,8 +392,13 @@ mod tests { Evaluator::generate_frames(expr, e, s, limit, padding_predicate, lang).unwrap(); s.hydrate_scalar_cache(); + let folding_config = Arc::new(FoldingConfig::new_ivc( + lang_rc.clone(), + DEFAULT_REDUCTION_COUNT, + )); + let multi_frames = - MultiFrame::from_frames(DEFAULT_REDUCTION_COUNT, &frames, s, &lang_rc); + MultiFrame::from_frames(DEFAULT_REDUCTION_COUNT, &frames, s, folding_config); let cs = groth_prover.outer_synthesize(&multi_frames).unwrap(); @@ -462,12 +456,13 @@ mod tests { } fn check_cs_deltas>( - constraint_systems: &SequentialCS<'_, Fr, IO, Witness, C>, + constraint_systems: &SequentialCS<'_, Fr, C>, limit: usize, lang: Arc>, ) { let mut cs_blank = MetricCS::::new(); - let blank_frame = MultiFrame::::blank(DEFAULT_REDUCTION_COUNT, lang); + let folding_config = Arc::new(FoldingConfig::new_ivc(lang, DEFAULT_REDUCTION_COUNT)); + let blank_frame = MultiFrame::::blank(folding_config, Meta::Lurk); blank_frame .synthesize(&mut cs_blank) .expect("failed to synthesize"); @@ -514,7 +509,7 @@ mod tests { fn outer_prove_eq() { outer_prove_aux( "(eq 5 5)", - |store| store.t(), + |store| lurk_sym_ptr!(store, t), 3, true, // Always check Groth16 in at least one test. true, @@ -528,7 +523,7 @@ mod tests { fn outer_prove_num_equal() { outer_prove_aux( "(= 5 5)", - |store| store.t(), + |store| lurk_sym_ptr!(store, t), 3, DEFAULT_CHECK_GROTH16, true, @@ -537,7 +532,7 @@ mod tests { ); outer_prove_aux( "(= 5 6)", - |store| store.nil(), + |store| lurk_sym_ptr!(store, nil), 3, DEFAULT_CHECK_GROTH16, true, @@ -647,8 +642,8 @@ mod tests { let fun = evaled.expr; - let cdr = s.lurk_sym("cdr"); - let quote = s.lurk_sym("quote"); + let cdr = lurk_sym_ptr!(s, cdr); + let quote = lurk_sym_ptr!(s, quote); let zero = s.num(0); let five = s.num(5); diff --git a/src/proof/mod.rs b/src/proof/mod.rs index 2fb4216220..e5710e4117 100644 --- a/src/proof/mod.rs +++ b/src/proof/mod.rs @@ -10,16 +10,22 @@ pub mod groth16; /// An adapter to a Nova proving system implementation. pub mod nova; -use bellperson::{util_cs::test_cs::TestConstraintSystem, Circuit, SynthesisError}; +/// An adapter to a SuperNova proving system implementation. +pub mod supernova; use crate::circuit::MultiFrame; use crate::coprocessor::Coprocessor; -use crate::eval::{lang::Lang, Witness, IO}; +use crate::error::ProofError; +use crate::eval::{lang::Lang, Evaluator, Frame, Witness, IO}; use crate::field::LurkField; +use crate::ptr::Ptr; +use crate::store::Store; +use bellpepper_core::{test_cs::TestConstraintSystem, Circuit, SynthesisError}; +use std::sync::Arc; + /// Represents a sequential Constraint System for a given proof. -pub(crate) type SequentialCS<'a, F, IO, Witness, C> = - Vec<(MultiFrame<'a, F, IO, Witness, C>, TestConstraintSystem)>; +pub(crate) type SequentialCS<'a, F, C> = Vec<(MultiFrame<'a, F, C>, TestConstraintSystem)>; /// A trait for provable structures over a field `F`. pub trait Provable { @@ -33,25 +39,25 @@ pub trait Provable { /// Verifies a sequence of constraint systems (CSs) for sequentiality & validity. pub fn verify_sequential_css>( - css: &SequentialCS<'_, F, IO, Witness, C>, + css: &SequentialCS<'_, F, C>, ) -> Result { - let mut previous_frame: Option<&MultiFrame<'_, F, IO, Witness, C>> = None; + let mut previous_frame: Option<&MultiFrame<'_, F, C>> = None; for (i, (multiframe, cs)) in css.iter().enumerate() { if let Some(prev) = previous_frame { if !prev.precedes(multiframe) { - dbg!(i, "not preceeding frame"); + tracing::debug!("frame {}: not preceeding frame", i); return Ok(false); } } if !cs.is_satisfied() { - dbg!(i, "cs not satisfied"); + tracing::debug!("frame {}: cs not satisfied", i); return Ok(false); } let public_inputs = multiframe.public_inputs(); if !cs.verify(&public_inputs) { - dbg!(i, "cs not verified"); + tracing::debug!("frame {}: cs not verified", i); return Ok(false); } previous_frame = Some(multiframe); @@ -62,7 +68,7 @@ pub fn verify_sequential_css>( pub trait PublicParameters {} /// A trait for a prover that works with a field `F`. -pub trait Prover<'a, 'b, F: LurkField, C: Coprocessor> { +pub trait Prover<'a, F: LurkField, C: Coprocessor> { /// The associated public parameters type for the prover. type PublicParams: PublicParameters; @@ -91,7 +97,7 @@ pub trait Prover<'a, 'b, F: LurkField, C: Coprocessor> { let full_multiframe_count = raw_iterations / cfc; let unfull_multiframe_frame_count = raw_iterations % cfc; let raw_multiframe_count = - full_multiframe_count + (unfull_multiframe_frame_count != 0) as usize; + full_multiframe_count + usize::from(unfull_multiframe_frame_count != 0); raw_multiframe_count + self.multiframe_padding_count(raw_multiframe_count) } @@ -108,8 +114,8 @@ pub trait Prover<'a, 'b, F: LurkField, C: Coprocessor> { /// Synthesizes the outer circuit for the prover given a slice of multiframes. fn outer_synthesize( &self, - multiframes: &'a [MultiFrame<'_, F, IO, Witness, C>], - ) -> Result, Witness, C>, SynthesisError> { + multiframes: &'a [MultiFrame<'_, F, C>], + ) -> Result, SynthesisError> { // Note: This loop terminates and returns an error on the first occurrence of `SynthesisError`. multiframes .iter() @@ -123,4 +129,24 @@ pub trait Prover<'a, 'b, F: LurkField, C: Coprocessor> { }) .collect::>() } + /// Evaluates and generates the `Frame`s of the computation given the expression, environment, and store + fn get_evaluation_frames( + &self, + expr: Ptr, + env: Ptr, + store: &mut Store, + limit: usize, + lang: Arc>, + ) -> Result, Witness, F, C>>, ProofError> { + let padding_predicate = |count| self.needs_frame_padding(count); + + let frames = Evaluator::generate_frames(expr, env, store, limit, padding_predicate, &lang)?; + + store.hydrate_scalar_cache(); + + Ok(frames) + } } + +/// Supertrait for `Prover` that also supports NIVC. +pub trait NIVCProver<'a, F: LurkField, C: Coprocessor>: Prover<'a, F, C> {} diff --git a/src/proof/nova.rs b/src/proof/nova.rs index 291d87b91f..1c49d0de40 100644 --- a/src/proof/nova.rs +++ b/src/proof/nova.rs @@ -1,8 +1,9 @@ #![allow(non_snake_case)] +use std::sync::Mutex; -use std::marker::PhantomData; - -use bellperson::{gadgets::num::AllocatedNum, ConstraintSystem, SynthesisError}; +use abomonation::Abomonation; +use bellpepper::util_cs::witness_cs::WitnessCS; +use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; use ff::Field; use nova::{ errors::NovaError, @@ -11,11 +12,13 @@ use nova::{ traits::{ circuit::{StepCircuit, TrivialTestCircuit}, commitment::CommitmentEngineTrait, + snark::RelaxedR1CSSNARKTrait, Group, }, CompressedSNARK, ProverKey, RecursiveSNARK, VerifierKey, }; use pasta_curves::{pallas, vesta}; +use rayon::prelude::*; use serde::{Deserialize, Serialize}; use std::sync::Arc; @@ -26,11 +29,13 @@ use crate::circuit::{ }, CircuitFrame, MultiFrame, }; +use crate::config::CONFIG; + use crate::coprocessor::Coprocessor; use crate::error::ProofError; -use crate::eval::{lang::Lang, Evaluator, Frame, Witness, IO}; +use crate::eval::{lang::Lang, Frame, Meta, Witness, IO}; use crate::field::LurkField; -use crate::proof::{Prover, PublicParameters}; +use crate::proof::{supernova::FoldingConfig, Prover, PublicParameters}; use crate::ptr::Ptr; use crate::store::Store; @@ -55,9 +60,9 @@ pub trait CurveCycleEquipped: LurkField { /// (properties are unwieldy to encode) in the form of this CommitmentKeyExtTrait. /// The type of the commitment key used for points of the first curve in the cycle. - type CK1: CommitmentKeyExtTrait::CE>; + type CK1: CommitmentKeyExtTrait; /// The type of the commitment key used for points of the second curve in the cycle. - type CK2: CommitmentKeyExtTrait::CE>; + type CK2: CommitmentKeyExtTrait; /// The commitment engine type for the first curve in the cycle. type CE1: CommitmentEngineTrait; /// The commitment engine type for the second curve in the cycle. @@ -102,14 +107,18 @@ pub type EE1 = nova::provider::ipa_pc::EvaluationEngine>; pub type EE2 = nova::provider::ipa_pc::EvaluationEngine>; /// Type alias for the Relaxed R1CS Spartan SNARK using G1 group elements, EE1. +// NOTE: this is not a SNARK that uses computational commitments, +// that SNARK would be found at nova::spartan::ppsnark::RelaxedR1CSSNARK, pub type SS1 = nova::spartan::snark::RelaxedR1CSSNARK, EE1>; /// Type alias for the Relaxed R1CS Spartan SNARK using G2 group elements, EE2. +// NOTE: this is not a SNARK that uses computational commitments, +// that SNARK would be found at nova::spartan::ppsnark::RelaxedR1CSSNARK, pub type SS2 = nova::spartan::snark::RelaxedR1CSSNARK, EE2>; /// Type alias for a MultiFrame with S1 field elements. /// This uses the <::G1 as Group>::Scalar type for the G1 scalar field elements /// to reflect it this should not be used outside the Nova context -pub type C1<'a, F, C> = MultiFrame<'a, as Group>::Scalar, IO, Witness, C>; +pub type C1<'a, F, C> = MultiFrame<'a, F, C>; /// Type alias for a Trivial Test Circuit with G2 scalar field elements. pub type C2 = TrivialTestCircuit< as Group>::Scalar>; @@ -117,20 +126,54 @@ pub type C2 = TrivialTestCircuit< as Group>::Scalar>; pub type NovaPublicParams<'a, F, C> = nova::PublicParams, G2, C1<'a, F, C>, C2>; /// A struct that contains public parameters for the Nova proving system. -#[derive(Serialize, Deserialize)] +#[derive(Clone, Serialize, Deserialize)] #[serde(bound = "")] -pub struct PublicParams<'a, F: CurveCycleEquipped, C: Coprocessor> +pub struct PublicParams<'a, F, C: Coprocessor> where F: CurveCycleEquipped, + // technical bounds that would disappear once associated_type_bounds stabilizes + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, { pp: NovaPublicParams<'a, F, C>, pk: ProverKey, G2, C1<'a, F, C>, C2, SS1, SS2>, vk: VerifierKey, G2, C1<'a, F, C>, C2, SS1, SS2>, } +impl<'c, F: CurveCycleEquipped, C: Coprocessor> Abomonation for PublicParams<'c, F, C> +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ + unsafe fn entomb(&self, bytes: &mut W) -> std::io::Result<()> { + self.pp.entomb(bytes)?; + self.pk.entomb(bytes)?; + self.vk.entomb(bytes)?; + Ok(()) + } + + unsafe fn exhume<'b>(&mut self, mut bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { + let temp = bytes; + bytes = self.pp.exhume(temp)?; + let temp = bytes; + bytes = self.pk.exhume(temp)?; + let temp = bytes; + bytes = self.vk.exhume(temp)?; + Some(bytes) + } + + fn extent(&self) -> usize { + self.pp.extent() + self.pk.extent() + self.vk.extent() + } +} + /// An enum representing the two types of proofs that can be generated and verified. #[derive(Serialize, Deserialize)] -pub enum Proof<'a, F: CurveCycleEquipped, C: Coprocessor> { +pub enum Proof<'a, F: CurveCycleEquipped, C: Coprocessor> +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ /// A proof for the intermediate steps of a recursive computation Recursive(Box, G2, C1<'a, F, C>, C2>>), /// A proof for the final step of a recursive computation @@ -141,18 +184,31 @@ pub enum Proof<'a, F: CurveCycleEquipped, C: Coprocessor> { pub fn public_params<'a, F: CurveCycleEquipped, C: Coprocessor>( num_iters_per_step: usize, lang: Arc>, -) -> PublicParams<'a, F, C> { +) -> PublicParams<'a, F, C> +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ let (circuit_primary, circuit_secondary) = C1::circuits(num_iters_per_step, lang); - let pp = nova::PublicParams::setup(circuit_primary, circuit_secondary); + let commitment_size_hint1 = as RelaxedR1CSSNARKTrait>>::commitment_key_floor(); + let commitment_size_hint2 = as RelaxedR1CSSNARKTrait>>::commitment_key_floor(); + + let pp = nova::PublicParams::new( + &circuit_primary, + &circuit_secondary, + Some(commitment_size_hint1), + Some(commitment_size_hint2), + ); let (pk, vk) = CompressedSNARK::setup(&pp).unwrap(); PublicParams { pp, pk, vk } } impl<'a, F: CurveCycleEquipped, C: Coprocessor> C1<'a, F, C> { fn circuits(count: usize, lang: Arc>) -> (C1<'a, F, C>, C2) { + let folding_config = Arc::new(FoldingConfig::new_ivc(lang, count)); ( - MultiFrame::blank(count, lang), + MultiFrame::blank(folding_config, Meta::Lurk), TrivialTestCircuit::default(), ) } @@ -164,18 +220,25 @@ pub struct NovaProver> { // `reduction_count` specifies the number of small-step reductions are performed in each recursive step. reduction_count: usize, lang: Lang, - _p: PhantomData<(F, C)>, } -impl<'a, F: CurveCycleEquipped, C: Coprocessor> PublicParameters for PublicParams<'a, F, C> {} +impl<'a, F: CurveCycleEquipped, C: Coprocessor> PublicParameters for PublicParams<'a, F, C> +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ +} -impl<'a, F: CurveCycleEquipped, C: Coprocessor + 'a> Prover<'a, '_, F, C> for NovaProver { +impl<'a, F: CurveCycleEquipped, C: Coprocessor + 'a> Prover<'a, F, C> for NovaProver +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ type PublicParams = PublicParams<'a, F, C>; fn new(reduction_count: usize, lang: Lang) -> Self { NovaProver:: { reduction_count, lang, - _p: Default::default(), } } fn reduction_count(&self) -> usize { @@ -187,36 +250,24 @@ impl<'a, F: CurveCycleEquipped, C: Coprocessor + 'a> Prover<'a, '_, F, C> for } } -impl> NovaProver { - /// Evaluates and generates the frames of the computation given the expression, environment, and store - pub fn get_evaluation_frames( - &self, - expr: Ptr, - env: Ptr, - store: &mut Store, - limit: usize, - lang: &Lang, - ) -> Result, Witness, C>>, ProofError> { - let padding_predicate = |count| self.needs_frame_padding(count); - - let frames = Evaluator::generate_frames(expr, env, store, limit, padding_predicate, lang)?; - - store.hydrate_scalar_cache(); - - Ok(frames) - } - +impl> NovaProver +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ /// Proves the computation given the public parameters, frames, and store. pub fn prove<'a>( &'a self, pp: &'a PublicParams<'_, F, C>, - frames: &[Frame, Witness, C>], - store: &'a mut Store, + frames: &[Frame, Witness, F, C>], + store: &'a Store, lang: Arc>, ) -> Result<(Proof<'_, F, C>, Vec, Vec, usize), ProofError> { let z0 = frames[0].input.to_vector(store)?; let zi = frames.last().unwrap().output.to_vector(store)?; - let circuits = MultiFrame::from_frames(self.reduction_count(), frames, store, &lang); + let folding_config = Arc::new(FoldingConfig::new_ivc(lang.clone(), self.reduction_count())); + let circuits = MultiFrame::from_frames(self.reduction_count, frames, store, folding_config); + let num_steps = circuits.len(); let proof = Proof::prove_recursively(pp, store, &circuits, self.reduction_count, z0.clone(), lang)?; @@ -234,18 +285,49 @@ impl> NovaProver { limit: usize, lang: Arc>, ) -> Result<(Proof<'_, F, C>, Vec, Vec, usize), ProofError> { - let frames = self.get_evaluation_frames(expr, env, store, limit, &lang)?; + let frames = self.get_evaluation_frames(expr, env, store, limit, lang.clone())?; self.prove(pp, &frames, store, lang) } } -impl<'a, F: LurkField, C: Coprocessor> StepCircuit - for MultiFrame<'a, F, IO, Witness, C> -{ +impl<'a, F: LurkField, C: Coprocessor> MultiFrame<'a, F, C> { + fn compute_witness(&self, s: &Store) -> WitnessCS { + let mut wcs = WitnessCS::new(); + + let input = self.input.unwrap(); + + use crate::tag::Tag; + let expr = s.hash_expr(&input.expr).unwrap(); + let env = s.hash_expr(&input.env).unwrap(); + let cont = s.hash_cont(&input.cont).unwrap(); + + let z_scalar = vec![ + expr.tag().to_field(), + *expr.value(), + env.tag().to_field(), + *env.value(), + cont.tag().to_field(), + *cont.value(), + ]; + + let mut bogus_cs = WitnessCS::::new(); + let z: Vec> = z_scalar + .iter() + .map(|x| AllocatedNum::alloc(&mut bogus_cs, || Ok(*x)).unwrap()) + .collect::>(); + + let _ = self.clone().synthesize(&mut wcs, z.as_slice()); + + wcs + } +} + +impl<'a, F: LurkField, C: Coprocessor> StepCircuit for MultiFrame<'a, F, C> { fn arity(&self) -> usize { 6 } + #[tracing::instrument(skip_all, name = "::synthesize")] fn synthesize( &self, cs: &mut CS, @@ -256,26 +338,79 @@ impl<'a, F: LurkField, C: Coprocessor> StepCircuit { assert_eq!(self.arity(), z.len()); + if cs.is_witness_generator() { + if let Some(w) = &self.cached_witness { + let aux = w.aux_slice(); + let end = aux.len() - 6; + let inputs = &w.inputs_slice()[1..]; + + cs.extend_aux(aux); + cs.extend_inputs(inputs); + + let scalars = &aux[end..]; + + let allocated = { + let mut bogus_cs = WitnessCS::new(); + + scalars + .iter() + .map(|scalar| AllocatedNum::alloc(&mut bogus_cs, || Ok(*scalar)).unwrap()) + .collect::>() + }; + + return Ok(allocated); + } + }; let input_expr = AllocatedPtr::by_index(0, z); let input_env = AllocatedPtr::by_index(1, z); let input_cont = AllocatedContPtr::by_index(2, z); let count = self.count; - let (new_expr, new_env, new_cont) = match self.frames.as_ref() { - Some(frames) => { - let s = self.store.expect("store missing"); - let g = GlobalAllocations::new(&mut cs.namespace(|| "global_allocations"), s)?; - self.synthesize_frames(cs, s, input_expr, input_env, input_cont, frames, &g) - } - None => { - assert!(self.store.is_none()); - let s = Store::default(); - let blank_frame = CircuitFrame::blank(); - let frames = vec![blank_frame; count]; - - let g = GlobalAllocations::new(&mut cs.namespace(|| "global_allocations"), &s)?; - self.synthesize_frames(cs, &s, input_expr, input_env, input_cont, &frames, &g) + let (new_expr, new_env, new_cont) = match self.meta { + Meta::Lurk => match self.frames.as_ref() { + Some(frames) => { + let s = self.store.expect("store missing"); + let g = GlobalAllocations::new(&mut cs.namespace(|| "global_allocations"), s)?; + + self.synthesize_frames(cs, s, input_expr, input_env, input_cont, frames, &g) + } + None => { + assert!(self.store.is_none()); + let s = Store::default(); + let blank_frame = CircuitFrame::blank(); + let frames = vec![blank_frame; count]; + + let g = GlobalAllocations::new(&mut cs.namespace(|| "global_allocations"), &s)?; + + self.synthesize_frames(cs, &s, input_expr, input_env, input_cont, &frames, &g) + } + }, + Meta::Coprocessor(z_ptr) => { + let c = self + .folding_config + .lang() + .get_coprocessor_from_zptr(&z_ptr) + .expect("coprocessor not found for a frame that requires one"); + match self.frames.as_ref() { + Some(frames) => { + assert_eq!(1, frames.len()); + let s = self.store.expect("store missing"); + let g = + GlobalAllocations::new(&mut cs.namespace(|| "global_allocations"), s)?; + + c.synthesize_step_circuit(cs, s, &g, &input_expr, &input_env, &input_cont)? + } + None => { + assert!(self.store.is_none()); + let s = Store::default(); + + let g = + GlobalAllocations::new(&mut cs.namespace(|| "global_allocations"), &s)?; + + c.synthesize_step_circuit(cs, &s, &g, &input_expr, &input_env, &input_cont)? + } + } } }; @@ -288,20 +423,15 @@ impl<'a, F: LurkField, C: Coprocessor> StepCircuit new_cont.hash().clone(), ]) } - - fn output(&self, z: &[F]) -> Vec { - // sanity check - assert_eq!(z, self.input.unwrap().to_vector(self.get_store()).unwrap()); - assert_eq!( - self.frames.as_ref().unwrap().last().unwrap().output, - self.output - ); - self.output.unwrap().to_vector(self.get_store()).unwrap() - } } -impl<'a: 'b, 'b, F: CurveCycleEquipped, C: Coprocessor> Proof<'a, F, C> { +impl<'a: 'b, 'b, F: CurveCycleEquipped, C: Coprocessor> Proof<'a, F, C> +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ /// Proves the computation recursively, generating a recursive SNARK proof. + #[tracing::instrument(skip_all, name = "Proof::prove_recursively")] pub fn prove_recursively( pp: &'a PublicParams<'_, F, C>, store: &'a Store, @@ -321,58 +451,117 @@ impl<'a: 'b, 'b, F: CurveCycleEquipped, C: Coprocessor> Proof<'a, F, C> { num_iters_per_step ); let (_circuit_primary, circuit_secondary): ( - MultiFrame<'_, F, IO, Witness, C>, + MultiFrame<'_, F, C>, TrivialTestCircuit< as Group>::Scalar>, ) = C1::<'a>::circuits(num_iters_per_step, lang); + + tracing::debug!("circuits.len: {}", circuits.len()); + // produce a recursive SNARK let mut recursive_snark: Option, G2, C1<'a, F, C>, C2>> = None; - for circuit_primary in circuits.iter() { - assert_eq!( - num_iters_per_step, - circuit_primary.frames.as_ref().unwrap().len() - ); - if debug { - // For debugging purposes, synthesize the circuit and check that the constraint system is satisfied. - use bellperson::util_cs::test_cs::TestConstraintSystem; - let mut cs = TestConstraintSystem::< as Group>::Scalar>::new(); - - let zi = circuit_primary.frames.as_ref().unwrap()[0] - .input - .unwrap() - .to_vector(store)?; - let zi_allocated: Vec<_> = zi - .iter() - .enumerate() - .map(|(i, x)| { - AllocatedNum::alloc(cs.namespace(|| format!("z{i}_1")), || Ok(*x)) - }) - .collect::>()?; - - circuit_primary.synthesize(&mut cs, zi_allocated.as_slice())?; - - assert!(cs.is_satisfied()); + // the shadowing here is voluntary + let recursive_snark = if CONFIG.parallelism.recursive_steps.is_parallel() { + let cc = circuits + .iter() + .map(|c| Mutex::new(c.clone())) + .collect::>(); + + crossbeam::thread::scope(|s| { + s.spawn(|_| { + // Skip the very first circuit's witness, so `prove_step` can begin immediately. + // That circuit's witness will not be cached and will just be computed on-demand. + cc.par_iter().skip(1).for_each(|mf| { + let witness = { + let mf1 = mf.lock().unwrap(); + mf1.compute_witness(store) + }; + let mut mf2 = mf.lock().unwrap(); + + mf2.cached_witness = Some(witness); + }); + }); + + for circuit_primary in cc.iter() { + let circuit_primary = circuit_primary.lock().unwrap(); + assert_eq!( + num_iters_per_step, + circuit_primary.frames.as_ref().unwrap().len() + ); + + let mut r_snark = recursive_snark.unwrap_or_else(|| { + RecursiveSNARK::new( + &pp.pp, + &circuit_primary, + &circuit_secondary, + z0_primary.clone(), + z0_secondary.clone(), + ) + }); + r_snark + .prove_step( + &pp.pp, + &circuit_primary, + &circuit_secondary, + z0_primary.clone(), + z0_secondary.clone(), + ) + .expect("failure to prove Nova step"); + recursive_snark = Some(r_snark); + } + recursive_snark + }) + .unwrap() + } else { + for circuit_primary in circuits.iter() { + assert_eq!( + num_iters_per_step, + circuit_primary.frames.as_ref().unwrap().len() + ); + if debug { + // For debugging purposes, synthesize the circuit and check that the constraint system is satisfied. + use bellpepper_core::test_cs::TestConstraintSystem; + let mut cs = TestConstraintSystem::< as Group>::Scalar>::new(); + + let zi = circuit_primary.frames.as_ref().unwrap()[0] + .input + .unwrap() + .to_vector(store)?; + let zi_allocated: Vec<_> = zi + .iter() + .enumerate() + .map(|(i, x)| { + AllocatedNum::alloc(cs.namespace(|| format!("z{i}_1")), || Ok(*x)) + }) + .collect::>()?; + + circuit_primary.synthesize(&mut cs, zi_allocated.as_slice())?; + + assert!(cs.is_satisfied()); + } + + let mut r_snark = recursive_snark.unwrap_or_else(|| { + RecursiveSNARK::new( + &pp.pp, + circuit_primary, + &circuit_secondary, + z0_primary.clone(), + z0_secondary.clone(), + ) + }); + r_snark + .prove_step( + &pp.pp, + circuit_primary, + &circuit_secondary, + z0_primary.clone(), + z0_secondary.clone(), + ) + .expect("failure to prove Nova step"); + recursive_snark = Some(r_snark); } - let mut r_snark = recursive_snark.unwrap_or_else(|| { - RecursiveSNARK::new( - &pp.pp, - circuit_primary, - &circuit_secondary, - z0_primary.clone(), - z0_secondary.clone(), - ) - }); - r_snark - .prove_step( - &pp.pp, - circuit_primary, - &circuit_secondary, - z0_primary.clone(), - z0_secondary.clone(), - ) - .expect("failure to prove Nova step"); - recursive_snark = Some(r_snark); - } + recursive_snark + }; Ok(Self::Recursive(Box::new(recursive_snark.unwrap()))) } @@ -423,7 +612,13 @@ impl<'a: 'b, 'b, F: CurveCycleEquipped, C: Coprocessor> Proof<'a, F, C> { #[cfg(test)] pub mod tests { + use std::cell::RefCell; + use std::rc::Rc; + use tracing_test::traced_test; + + use crate::lurk_sym_ptr; use crate::num::Num; + use crate::state::{user_sym, State}; use super::*; use crate::eval::empty_sym_env; @@ -432,15 +627,30 @@ pub mod tests { use crate::ptr::ContPtr; use crate::tag::{Op, Op1, Op2}; - use bellperson::{ - util_cs::{metric_cs::MetricCS, test_cs::TestConstraintSystem, Comparable, Delta}, - Circuit, - }; + use bellpepper::util_cs::witness_cs::WitnessCS; + use bellpepper::util_cs::{metric_cs::MetricCS, Comparable}; + use bellpepper_core::test_cs::TestConstraintSystem; + use bellpepper_core::{Circuit, Delta}; use pallas::Scalar as Fr; const DEFAULT_REDUCTION_COUNT: usize = 5; const REDUCTION_COUNTS_TO_TEST: [usize; 3] = [1, 2, 5]; - /// fake docs + + // Returns index of first mismatch, along with the mismatched elements if they exist. + fn mismatch(a: &[T], b: &[T]) -> Option<(usize, (Option, Option))> { + let min_len = a.len().min(b.len()); + for i in 0..min_len { + if a[i] != b[i] { + return Some((i, (Some(a[i]), Some(b[i])))); + } + } + match (a.get(min_len), b.get(min_len)) { + (Some(&a_elem), None) => Some((min_len, (Some(a_elem), None))), + (None, Some(&b_elem)) => Some((min_len, (None, Some(b_elem)))), + _ => None, + } + } + pub fn test_aux>( s: &mut Store, expr: &str, @@ -534,7 +744,7 @@ pub mod tests { let res = proof.verify(&pp, num_steps, &z0, &zi); if res.is_err() { - dbg!(&res); + tracing::debug!("{:?}", &res); } assert!(res.unwrap()); @@ -545,38 +755,58 @@ pub mod tests { } let frames = nova_prover - .get_evaluation_frames(expr, e, s, limit, &lang) + .get_evaluation_frames(expr, e, s, limit, lang.clone()) .unwrap(); + let folding_config = Arc::new(FoldingConfig::new_ivc(lang, nova_prover.reduction_count())); - let multiframes = MultiFrame::from_frames(nova_prover.reduction_count(), &frames, s, &lang); + let multiframes = MultiFrame::from_frames( + nova_prover.reduction_count(), + &frames, + s, + folding_config.clone(), + ); let len = multiframes.len(); let adjusted_iterations = nova_prover.expected_total_iterations(expected_iterations); - let mut previous_frame: Option, Witness, C>> = None; + let mut previous_frame: Option> = None; let mut cs_blank = MetricCS::::new(); - let blank = MultiFrame::, Witness, C>::blank(reduction_count, lang); + let blank = MultiFrame::::blank(folding_config, Meta::Lurk); blank .synthesize(&mut cs_blank) .expect("failed to synthesize blank"); for (_i, multiframe) in multiframes.iter().enumerate() { let mut cs = TestConstraintSystem::new(); + let mut wcs = WitnessCS::new(); + + tracing::debug!("synthesizing test cs"); multiframe.clone().synthesize(&mut cs).unwrap(); + tracing::debug!("synthesizing witness cs"); + multiframe.clone().synthesize(&mut wcs).unwrap(); if let Some(prev) = previous_frame { assert!(prev.precedes(multiframe)); }; - // dbg!(i); + // tracing::debug!("frame {}" i); let unsat = cs.which_is_unsatisfied(); if unsat.is_some() { // For some reason, this isn't getting printed from within the implementation as expected. // Since we always want to know this information, if the condition occurs, just print it here. - dbg!(unsat); + tracing::debug!("{:?}", unsat); } assert!(cs.is_satisfied()); assert!(cs.verify(&multiframe.public_inputs())); + tracing::debug!("cs is satisfied!"); + let cs_inputs = cs.scalar_inputs(); + let cs_aux = cs.scalar_aux(); + + let wcs_inputs = wcs.scalar_inputs(); + let wcs_aux = wcs.scalar_aux(); + + assert_eq!(None, mismatch(&cs_inputs, &wcs_inputs)); + assert_eq!(None, mismatch(&cs_aux, &wcs_aux)); previous_frame = Some(multiframe.clone()); @@ -589,7 +819,7 @@ pub mod tests { if let Some(expected_emitted) = expected_emitted { let emitted_vec: Vec<_> = frames .iter() - .flat_map(|frame| frame.output.maybe_emitted_expression(s)) + .filter_map(|frame| frame.output.maybe_emitted_expression(s)) .collect(); assert_eq!(expected_emitted, &emitted_vec); } @@ -677,7 +907,7 @@ pub mod tests { #[ignore] fn test_prove_eq() { let s = &mut Store::::default(); - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); nova_test_full_aux::>( s, @@ -698,7 +928,7 @@ pub mod tests { #[ignore] fn test_prove_num_equal() { let s = &mut Store::::default(); - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -711,7 +941,7 @@ pub mod tests { None, ); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -728,7 +958,7 @@ pub mod tests { #[test] fn test_prove_invalid_num_equal() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let error = s.get_cont_error(); test_aux::>( s, @@ -757,8 +987,8 @@ pub mod tests { #[test] fn test_prove_equal() { let s = &mut Store::::default(); - let nil = s.nil(); - let t = s.t(); + let nil = lurk_sym_ptr!(s, nil); + let t = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>( @@ -915,7 +1145,7 @@ pub mod tests { fn test_prove_unop_regression_aux(chunk_count: usize) { let s = &mut Store::::default(); - let expected = s.lurk_sym("t"); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); nova_test_full_aux::>( s, @@ -1164,23 +1394,23 @@ pub mod tests { if !op.supports_arity(0) { let expr = format!("({name})"); - dbg!(&expr); + tracing::debug!("{:?}", &expr); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } if !op.supports_arity(1) { let expr = format!("({name} 123)"); - dbg!(&expr); + tracing::debug!("{:?}", &expr); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } if !op.supports_arity(2) { let expr = format!("({name} 123 456)"); - dbg!(&expr); + tracing::debug!("{:?}", &expr); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } if !op.supports_arity(3) { let expr = format!("({name} 123 456 789)"); - dbg!(&expr); + tracing::debug!("{:?}", &expr); let iterations = if op.supports_arity(2) { 2 } else { 1 }; test_aux::>(s, &expr, None, None, Some(error), None, iterations, None); } @@ -1276,7 +1506,7 @@ pub mod tests { #[test] fn test_prove_error_invalid_type_and_not_cons() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let error = s.get_cont_error(); test_aux::>( s, @@ -1315,7 +1545,7 @@ pub mod tests { #[test] fn test_prove_current_env_simple() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -1453,7 +1683,7 @@ pub mod tests { #[test] fn test_prove_let_body_nil() { let s = &mut Store::::default(); - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -1584,7 +1814,7 @@ pub mod tests { #[ignore] fn test_prove_comparison() { let s = &mut Store::::default(); - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -1826,7 +2056,7 @@ pub mod tests { #[ignore] fn test_prove_no_mutual_recursion() { let s = &mut Store::::default(); - let expected = s.t(); + let expected = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -1974,10 +2204,10 @@ pub mod tests { fn test_prove_zero_arg_lambda3() { let s = &mut Store::::default(); let expected = { - let arg = s.sym("x"); + let arg = s.user_sym("x"); let num = s.num(123); let body = s.list(&[num]); - let env = s.nil(); + let env = lurk_sym_ptr!(s, nil); s.intern_fun(arg, body, env) }; let terminal = s.get_cont_terminal(); @@ -2381,7 +2611,7 @@ pub mod tests { #[test] fn test_prove_begin_empty() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -2450,7 +2680,7 @@ pub mod tests { #[test] fn test_prove_str_car_empty() { let s = &mut Store::::default(); - let expected_nil = s.nil(); + let expected_nil = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -2524,7 +2754,7 @@ pub mod tests { #[test] fn test_prove_car_nil() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -2541,7 +2771,7 @@ pub mod tests { #[test] fn test_prove_cdr_nil() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, @@ -2663,7 +2893,7 @@ pub mod tests { fn test_prove_hide_open_sym() { let s = &mut Store::::default(); let expr = "(open (hide 123 'x))"; - let x = s.sym("x"); + let x = s.user_sym("x"); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(x), None, Some(terminal), None, 5, None); } @@ -2672,7 +2902,7 @@ pub mod tests { fn test_prove_commit_open_sym() { let s = &mut Store::::default(); let expr = "(open (commit 'x))"; - let x = s.sym("x"); + let x = s.user_sym("x"); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(x), None, Some(terminal), None, 4, None); } @@ -2886,7 +3116,7 @@ pub mod tests { fn test_prove_terminal_sym() { let s = &mut Store::::default(); let expr = "(quote x)"; - let x = s.sym("x"); + let x = s.user_sym("x"); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(x), None, Some(terminal), None, 1, None); } @@ -2923,7 +3153,7 @@ pub mod tests { let a_pple = s.read(r#" (#\a . "pple") "#).unwrap(); let pple = s.read(r#" "pple" "#).unwrap(); let empty = s.intern_string(""); - let nil = s.nil(); + let nil = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); let error = s.get_cont_error(); @@ -3025,7 +3255,11 @@ pub mod tests { fn relational_aux(s: &mut Store, op: &str, a: &str, b: &str, res: bool) { let expr = &format!("({op} {a} {b})"); - let expected = if res { s.t() } else { s.nil() }; + let expected = if res { + lurk_sym_ptr!(s, t) + } else { + lurk_sym_ptr!(s, nil) + }; let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(expected), None, Some(terminal), None, 3, None); @@ -3153,7 +3387,7 @@ pub mod tests { let expr = "(let ((most-positive (/ (- 0 1) 2)) (most-negative (+ 1 most-positive))) (< most-negative most-positive))"; - let t = s.t(); + let t = lurk_sym_ptr!(s, t); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(t), None, Some(terminal), None, 19, None); @@ -3180,8 +3414,8 @@ pub mod tests { let expr2 = "(eq :asdf :asdf)"; let expr3 = "(eq :asdf 'asdf)"; let res = s.key("asdf"); - let res2 = s.get_t(); - let res3 = s.get_nil(); + let res2 = lurk_sym_ptr!(s, t); + let res3 = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); @@ -3437,8 +3671,8 @@ pub mod tests { let expr9 = "(<= 0u64 0u64)"; let expr10 = "(>= 0u64 0u64)"; - let t = s.t(); - let nil = s.nil(); + let t = lurk_sym_ptr!(s, t); + let nil = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(t), None, Some(terminal), None, 3, None); @@ -3480,8 +3714,8 @@ pub mod tests { let expr = "(= 1 1u64)"; let expr2 = "(= 1 2u64)"; - let t = s.t(); - let nil = s.nil(); + let t = lurk_sym_ptr!(s, t); + let nil = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>(s, expr, Some(t), None, Some(terminal), None, 3, None); @@ -3534,8 +3768,8 @@ pub mod tests { #[test] fn test_prove_call_literal_fun() { let s = &mut Store::::default(); - let empty_env = s.get_nil(); - let arg = s.sym("x"); + let empty_env = lurk_sym_ptr!(s, nil); + let arg = s.user_sym("x"); let body = s.read("((+ x 1))").unwrap(); let fun = s.intern_fun(arg, body, empty_env); let input = s.num(9); @@ -3599,7 +3833,7 @@ pub mod tests { #[test] #[ignore] - fn test_eval_non_symbol_binding_error() { + fn test_prove_non_symbol_binding_error() { let s = &mut Store::::default(); let error = s.get_cont_error(); @@ -3625,80 +3859,82 @@ pub mod tests { let s = &mut Store::::default(); let error = s.get_cont_error(); - let hash_num = |s: &mut Store, name| { - let sym = s.lurk_sym(name); + let hash_num = |s: &mut Store, state: Rc>, name| { + let sym = s.read_with_state(state, name).unwrap(); let z_ptr = s.hash_expr(&sym).unwrap(); let hash = *z_ptr.value(); Num::Scalar(hash) }; + + let state = State::init_lurk_state().rccell(); { // binop - let expr = format!("({} 1 1)", hash_num(s, "+")); + let expr = format!("({} 1 1)", hash_num(s, state.clone(), "+")); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } { // unop - let expr = format!("({} '(1 . 2))", hash_num(s, "car")); + let expr = format!("({} '(1 . 2))", hash_num(s, state.clone(), "car")); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } { // let_or_letrec - let expr = format!("({} ((a 1)) a)", hash_num(s, "let")); + let expr = format!("({} ((a 1)) a)", hash_num(s, state.clone(), "let")); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } { // current-env - let expr = format!("({})", hash_num(s, "current-env")); + let expr = format!("({})", hash_num(s, state.clone(), "current-env")); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } { // lambda - let expr = format!("({} (x) 123)", hash_num(s, "lambda")); + let expr = format!("({} (x) 123)", hash_num(s, state.clone(), "lambda")); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } { // quote - let expr = format!("({} asdf)", hash_num(s, "quote")); + let expr = format!("({} asdf)", hash_num(s, state.clone(), "quote")); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } { // if - let expr = format!("({} t 123 456)", hash_num(s, "if")); + let expr = format!("({} t 123 456)", hash_num(s, state, "if")); test_aux::>(s, &expr, None, None, Some(error), None, 1, None); } } #[test] + #[traced_test] fn test_dumb_lang() { use crate::coprocessor::test::DumbCoprocessor; use crate::eval::tests::coproc::DumbCoproc; - use crate::symbol::Symbol; let s = &mut Store::::new(); let mut lang = Lang::>::new(); - let name = Symbol::new(&["cproc", "dumb"]); + let name = user_sym("cproc-dumb"); let dumb = DumbCoprocessor::new(); let coproc = DumbCoproc::DC(dumb); lang.add_coprocessor(name, coproc, s); // 9^2 + 8 = 89 - let expr = "(.cproc.dumb 9 8)"; + let expr = "(cproc-dumb 9 8)"; // The dumb coprocessor cannot be shadowed. - let expr2 = "(let ((.cproc.dumb (lambda (a b) (* a b)))) - (.cproc.dumb 9 8))"; + let expr2 = "(let ((cproc-dumb (lambda (a b) (* a b)))) + (cproc-dumb 9 8))"; - let expr3 = "(.cproc.dumb 9 8 123)"; - let expr4 = "(.cproc.dumb 9)"; + let expr3 = "(cproc-dumb 9 8 123)"; + let expr4 = "(cproc-dumb 9)"; let res = s.num(89); let error = s.get_cont_error(); let lang = Arc::new(lang); - test_aux(s, expr, Some(res), None, None, None, 1, Some(lang.clone())); - test_aux(s, expr2, Some(res), None, None, None, 3, Some(lang.clone())); + test_aux(s, expr, Some(res), None, None, None, 2, Some(lang.clone())); + test_aux(s, expr2, Some(res), None, None, None, 5, Some(lang.clone())); test_aux( s, expr3, @@ -3716,7 +3952,7 @@ pub mod tests { #[test] fn test_prove_lambda_body_nil() { let s = &mut Store::::default(); - let expected = s.nil(); + let expected = lurk_sym_ptr!(s, nil); let terminal = s.get_cont_terminal(); test_aux::>( s, diff --git a/src/proof/supernova.rs b/src/proof/supernova.rs new file mode 100644 index 0000000000..0bcceb9318 --- /dev/null +++ b/src/proof/supernova.rs @@ -0,0 +1,595 @@ +#![allow(non_snake_case)] +use std::marker::PhantomData; +use std::ops::Index; + +use abomonation::Abomonation; +use tracing::{debug, info}; + +use bellpepper_core::{num::AllocatedNum, ConstraintSystem, SynthesisError}; +use nova::{ + self, + supernova::{self, error::SuperNovaError, NonUniformCircuit, RecursiveSNARK, RunningClaim}, + traits::{ + circuit_supernova::{StepCircuit, TrivialSecondaryCircuit}, + Group, + }, +}; + +use ff::{Field, PrimeField}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use crate::circuit::MultiFrame; + +use crate::coprocessor::Coprocessor; + +use crate::error::ProofError; +use crate::eval::{lang::Lang, Frame, Meta, Witness, IO}; +use crate::field::LurkField; +use crate::proof::nova::{CurveCycleEquipped, G1, G2}; +use crate::proof::{Provable, Prover, PublicParameters}; +use crate::ptr::Ptr; +use crate::store::Store; + +/// Type alias for SuperNova Public Parameters with the curve cycle types defined above. +pub type SuperNovaPublicParams = supernova::PublicParams, G2>; + +/// A struct that contains public parameters for the Nova proving system. +#[derive(Clone, Serialize, Deserialize)] +#[serde(bound = "")] +pub struct PublicParams> +where + F: CurveCycleEquipped, + // technical bounds that would disappear once associated_type_bounds stabilizes + < as Group>::Scalar as PrimeField>::Repr: Abomonation, + < as Group>::Scalar as PrimeField>::Repr: Abomonation, +{ + pp: SuperNovaPublicParams, + // SuperNova does not yet have a `CompressedSNARK`. + // see https://github.com/lurk-lab/arecibo/issues/27 + // pk: ProverKey, G2, C1<'a, F, C>, C2, SS1, SS2>, + // vk: VerifierKey, G2, C1<'a, F, C>, C2, SS1, SS2>, + _p: PhantomData, +} + +impl> Abomonation for PublicParams +where + < as Group>::Scalar as PrimeField>::Repr: Abomonation, + < as Group>::Scalar as PrimeField>::Repr: Abomonation, +{ + unsafe fn entomb(&self, bytes: &mut W) -> std::io::Result<()> { + self.pp.entomb(bytes)?; + // self.pk.entomb(bytes)?; + // self.vk.entomb(bytes)?; + Ok(()) + } + + unsafe fn exhume<'b>(&mut self, mut bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { + let temp = bytes; + bytes = self.pp.exhume(temp)?; + // let temp = bytes; + // bytes = self.pk.exhume(temp)?; + // let temp = bytes; + // bytes = self.vk.exhume(temp)?; + Some(bytes) + } + + fn extent(&self) -> usize { + self.pp.extent() // + self.pk.extent() + self.vk.extent() + } +} + +/// An enum representing the two types of proofs that can be generated and verified. +#[derive(Serialize, Deserialize)] +pub enum Proof> +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ + /// A proof for the intermediate steps of a recursive computation + Recursive(Box, G2>>), + /// A proof for the final step of a recursive computation + // Compressed(Box, G2, C1<'a, F, C>, C2, SS1, SS2>>), + Compressed(PhantomData), +} + +impl> Proof +where + < as Group>::Scalar as PrimeField>::Repr: Abomonation, + < as Group>::Scalar as PrimeField>::Repr: Abomonation, + ::Repr: Abomonation, + <<::G2 as Group>::Scalar as PrimeField>::Repr: Abomonation, +{ + /// Proves the computation recursively, generating a recursive SNARK proof. + #[tracing::instrument(skip_all, name = "Proof::prove_recursively")] + pub fn prove_recursively( + _pp: Option<&PublicParams>, + _store: &Store, + nivc_steps: &NIVCSteps<'_, G1, C>, + reduction_count: usize, + z0: Vec, + lang: Arc>, + ) -> Result { + // Is this assertion strictly necessary? + assert!(nivc_steps.num_steps() != 0); + // NOTE: The `Meta::Lurk` in the blank step is used as a default. It might be worth more explicitly supporting + // an undifferentiated 'stem cell' blank `NonUniformCircuit`, for clarity. + let folding_config = Arc::new(FoldingConfig::new_nivc(lang, reduction_count)); + let blank_step = NIVCStep::blank(folding_config, Meta::Lurk); + + info!("setting up running claims"); + let running_claims = blank_step.setup_running_claims(); + info!("running claim setup complete"); + let mut recursive_snark_option: Option, G2>> = None; + + let z0_primary = z0; + let z0_secondary = Self::z0_secondary(); + + let mut last_running_claim = &running_claims[nivc_steps.steps[0].circuit_index()]; + + for (i, step) in nivc_steps.steps.iter().enumerate() { + info!("prove_recursively, step {i}"); + let augmented_circuit_index = step.circuit_index(); + let program_counter = F::from(augmented_circuit_index as u64); + + let mut recursive_snark = recursive_snark_option.clone().unwrap_or_else(|| { + info!("iter_base_step {i}"); + RecursiveSNARK::iter_base_step( + &running_claims[augmented_circuit_index], + step, + running_claims.digest(), + Some(program_counter), + augmented_circuit_index, + step.num_circuits(), + &z0_primary, + &z0_secondary, + ) + .unwrap() + }); + + info!("prove_step {i}"); + + recursive_snark + .prove_step( + &running_claims[augmented_circuit_index], + step, + &z0_primary, + &z0_secondary, + ) + .unwrap(); + info!("verify step {i}"); + recursive_snark + .verify( + &running_claims[augmented_circuit_index], + &z0_primary, + &z0_secondary, + ) + .unwrap(); + recursive_snark_option = Some(recursive_snark); + + last_running_claim = &running_claims[augmented_circuit_index]; + } + + // TODO: return `last_running_claim` somehow, so it can be used to verify. + let _ = last_running_claim; + + // This probably should be made unnecessary. + Ok(Self::Recursive(Box::new( + recursive_snark_option.expect("RecursiveSNARK missing"), + ))) + } + + /// Verifies the proof given the claim, which (for now), contains the public parameters. + pub fn verify( + &self, + claim: &RunningClaim< + G1, + G2, + NIVCStep<'_, F, C>, + TrivialSecondaryCircuit< as Group>::Scalar>, + >, + _pp: Option<&PublicParams>, + _num_steps: usize, + z0: &[F], + zi: &[F], + ) -> Result { + let (z0_primary, _zi_primary) = (z0, zi); + let z0_secondary = Self::z0_secondary(); + + match self { + Self::Recursive(p) => p.verify(claim, z0_primary, &z0_secondary), + Self::Compressed(_) => unimplemented!(), + }?; + Ok(true) + } + + fn z0_secondary() -> Vec<::Scalar> { + vec![ as Group>::Scalar::ZERO] + } +} + +// /// Generates the public parameters for the Nova proving system. +// pub fn public_params<'a, F: CurveCycleEquipped, C: Coprocessor>( +// num_iters_per_step: usize, +// lang: Arc>, +// ) -> PublicParams<'a, F, C> +// where +// < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +// < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +// { +// let (circuit_primary, circuit_secondary) = C1::circuits(num_iters_per_step, lang); + +// let commitment_size_hint1 = as RelaxedR1CSSNARKTrait>>::commitment_key_floor(); +// let commitment_size_hint2 = as RelaxedR1CSSNARKTrait>>::commitment_key_floor(); + +// let pp = nova::PublicParams::setup( +// &circuit_primary, +// &circuit_secondary, +// Some(commitment_size_hint1), +// Some(commitment_size_hint2), +// ); +// let (pk, vk) = CompressedSNARK::setup(&pp).unwrap(); +// PublicParams { pp, pk, vk } +// } + +/// A struct for the Nova prover that operates on field elements of type `F`. +#[derive(Debug)] +pub struct SuperNovaProver> { + // `reduction_count` specifies the number of small-step reductions are performed in each recursive step of the primary Lurk circuit. + reduction_count: usize, + lang: Lang, +} + +impl> PublicParameters for PublicParams +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ +} + +impl<'a, F: CurveCycleEquipped, C: Coprocessor + 'a> Prover<'a, F, C> for SuperNovaProver +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ + type PublicParams = PublicParams; + fn new(reduction_count: usize, lang: Lang) -> Self { + SuperNovaProver:: { + reduction_count, + lang, + } + } + fn reduction_count(&self) -> usize { + self.reduction_count + } + + fn lang(&self) -> &Lang { + &self.lang + } +} + +impl> SuperNovaProver +where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ + /// Proves the computation given the public parameters, frames, and store. + pub fn prove<'a>( + &'a self, + pp: Option<&PublicParams>, + frames: &[Frame, Witness, F, C>], + store: &'a mut Store, + lang: Arc>, + ) -> Result<(Proof, Vec, Vec, usize), ProofError> { + let z0 = frames[0].input.to_vector(store)?; + let zi = frames.last().unwrap().output.to_vector(store)?; + let folding_config = Arc::new(FoldingConfig::new_nivc(lang.clone(), self.reduction_count)); + + let nivc_steps = + NIVCSteps::from_frames(self.reduction_count(), frames, store, folding_config); + + let num_steps = nivc_steps.num_steps(); + let proof = Proof::prove_recursively( + pp, + store, + &nivc_steps, + self.reduction_count, + z0.clone(), + lang, + )?; + + Ok((proof, z0, zi, num_steps)) + } + + /// Evaluates and proves the computation given the public parameters, expression, environment, and store. + pub fn evaluate_and_prove<'a>( + &'a self, + pp: Option<&PublicParams>, + expr: Ptr, + env: Ptr, + store: &'a mut Store, + limit: usize, + lang: Arc>, + ) -> Result<(Proof, Vec, Vec, usize), ProofError> { + let frames = self.get_evaluation_frames(expr, env, store, limit, lang.clone())?; + info!("got {} evaluation frames", frames.len()); + self.prove(pp, &frames, store, lang) + } +} + +#[derive(Clone, Debug)] +/// Folding configuration specifies `Lang` and can be either `IVC` or `NIVC`. +// NOTE: This is somewhat trivial now, but will likely become more elaborate as NIVC configuration becomes more flexible. +pub enum FoldingConfig> { + // TODO: maybe (lang, reduction_count) should be a common struct. + /// IVC: a single circuit implementing the `Lang`'s reduction will be used for every folding step + IVC(Arc>, usize), + /// NIVC: each folding step will use one of a fixed set of circuits which together implement the `Lang`'s reduction. + NIVC(Arc>, usize), +} + +impl> FoldingConfig { + /// Create a new IVC config for `lang`. + pub fn new_ivc(lang: Arc>, reduction_count: usize) -> Self { + Self::IVC(lang, reduction_count) + } + + /// Create a new NIVC config for `lang`. + pub fn new_nivc(lang: Arc>, reduction_count: usize) -> Self { + Self::NIVC(lang, reduction_count) + } + + /// Return the circuit index assigned in this `FoldingConfig` to circuits tagged with this `meta`. + pub fn circuit_index(&self, meta: &Meta) -> usize { + match self { + Self::IVC(_, _) => 0, + Self::NIVC(lang, _) => match meta { + Meta::Lurk => 0, + Meta::Coprocessor(z_ptr) => lang.get_index(z_ptr).unwrap() + 1, + }, + } + } + + /// Return the total number of NIVC circuits potentially required when folding programs described by this `FoldingConfig`. + pub fn num_circuits(&self) -> usize { + match self { + Self::IVC(_, _) => 1, + Self::NIVC(lang, _) => 1 + lang.coprocessor_count(), + } + } + + /// Return a reference to the contained `Lang`. + pub fn lang(&self) -> &Arc> { + match self { + Self::IVC(lang, _) | Self::NIVC(lang, _) => lang, + } + } + /// Return contained reduction count. + pub fn reduction_count(&self) -> usize { + match self { + Self::IVC(_, rc) | Self::NIVC(_, rc) => *rc, + } + } +} + +impl<'a, F: LurkField, C: Coprocessor> MultiFrame<'a, F, C> { + /// Return the circuit index assigned to this `MultiFrame`'s inner computation, as labeled by its `Meta`, and determined by its `FoldingConfig`. + pub fn circuit_index(&self) -> usize { + debug!( + "getting circuit_index for {:?}: {}", + &self.meta, + self.folding_config.circuit_index(&self.meta) + ); + self.folding_config.circuit_index(&self.meta) + } +} + +#[derive(Clone, Debug)] +/// One step of an NIVC computation +pub struct NIVCStep<'a, F: LurkField, C: Coprocessor> { + multiframe: MultiFrame<'a, F, C>, + next: Option>, + _p: PhantomData, +} + +impl<'a, 'b, F: LurkField, C: Coprocessor> NIVCStep<'a, F, C> +where + 'b: 'a, +{ + fn new(multiframe: MultiFrame<'b, F, C>) -> Self { + Self { + multiframe, + next: None, + _p: Default::default(), + } + } + + fn blank(folding_config: Arc>, meta: Meta) -> Self { + let multiframe = MultiFrame::blank(folding_config, meta); + Self::new(multiframe) + } + + fn lang(&self) -> Arc> { + self.multiframe.folding_config.lang().clone() + } + + fn meta(&self) -> Meta { + self.multiframe.meta + } + + fn folding_config(&self) -> Arc> { + self.multiframe.folding_config.clone() + } +} + +/// Implement `supernova::StepCircuit` for `MultiFrame`. This is the universal Lurk circuit that will be included as the +/// first circuit (index 0) of every Lurk NIVC circuit set. +impl> StepCircuit for NIVCStep<'_, F, C> { + fn arity(&self) -> usize { + MultiFrame::<'_, F, C>::public_input_size() / 2 + } + + fn circuit_index(&self) -> usize { + self.multiframe.circuit_index() + } + + fn synthesize( + &self, + cs: &mut CS, + pc: std::option::Option<&AllocatedNum>, + z: &[AllocatedNum], + ) -> Result<(std::option::Option>, Vec>), SynthesisError> + where + CS: ConstraintSystem, + { + if let Some(pc) = pc { + if pc.get_value() == Some(F::ZERO) { + debug!("synthesizing StepCircuit for Lurk"); + } else { + debug!( + "synthesizing StepCircuit for Coprocessor with pc: {:?}", + pc.get_value() + ); + } + } + let output = as nova::traits::circuit::StepCircuit>::synthesize( + &self.multiframe, + cs, + z, + )?; + + let next_pc = AllocatedNum::alloc_infallible(&mut cs.namespace(|| "next_pc"), || { + self.next + .as_ref() + // This is missing in the case of a final `MultiFrame`. The Lurk circuit is defined to always have index + // 0, so it is a good default in this case. + .map_or(F::ZERO, |x| F::from(x.circuit_index() as u64)) + }); + debug!("synthesizing with next_pc: {:?}", next_pc.get_value()); + + Ok((Some(next_pc), output)) + } +} + +/// All steps of an NIVC computation +pub struct NIVCSteps<'a, G: Group, C: Coprocessor> +where + G::Scalar: LurkField, +{ + steps: Vec>, +} +impl<'a, G1, F, C1> Index for NIVCSteps<'a, G1, C1> +where + C1: Coprocessor + 'a, + G1: Group + 'a, + F: LurkField, +{ + type Output = NIVCStep<'a, G1::Scalar, C1>; + + fn index(&self, idx: usize) -> &>::Output { + &self.steps[idx] + } +} + +impl<'a, G1, F, C1> NIVCSteps<'a, G1, C1> +where + C1: Coprocessor, + G1: Group, + F: LurkField, +{ + /// Number of NIVC steps contained. + pub fn num_steps(&self) -> usize { + self.steps.len() + } + /// Separate frames according to NIVC circuit requirements. + pub fn from_frames( + count: usize, + frames: &[Frame, Witness, F, C1>], + store: &'a Store, + folding_config: Arc>, + ) -> Self { + let mut steps = Vec::new(); + let mut last_meta = frames[0].meta; + let mut consecutive_frames = Vec::new(); + + for frame in frames { + if frame.meta == last_meta { + let padding_frame = frame.clone(); + consecutive_frames.push(padding_frame); + } else { + if last_meta == Meta::Lurk { + consecutive_frames.push(frame.clone()); + } + let new_steps = MultiFrame::from_frames( + if last_meta == Meta::Lurk { count } else { 1 }, + &consecutive_frames, + store, + folding_config.clone(), + ) + .into_iter() + .map(NIVCStep::<'_, F, C1>::new); + + steps.extend(new_steps); + consecutive_frames.truncate(0); + consecutive_frames.push(frame.clone()); + last_meta = frame.meta; + } + } + + // TODO: refactor + if !consecutive_frames.is_empty() { + let new_steps = + MultiFrame::from_frames(count, &consecutive_frames, store, folding_config) + .into_iter() + .map(NIVCStep::<'_, F, C1>::new); + + steps.extend(new_steps); + } + + if !steps.is_empty() { + let penultimate = steps.len() - 1; + for i in 0..(penultimate - 1) { + steps[i].next = Some(steps[i + 1].multiframe.clone()); + } + } + Self { steps } + } +} + +impl<'a, F, C1> NonUniformCircuit, G2, NIVCStep<'a, F, C1>> for NIVCStep<'a, F, C1> +where + < as Group>::Scalar as PrimeField>::Repr: Abomonation, + < as Group>::Scalar as PrimeField>::Repr: Abomonation, + ::Repr: Abomonation, + C1: Coprocessor, + F: CurveCycleEquipped + LurkField, + <<::G2 as Group>::Scalar as PrimeField>::Repr: Abomonation, +{ + fn num_circuits(&self) -> usize { + assert!(self.meta().is_lurk()); + self.lang().coprocessor_count() + 1 + } + + fn primary_circuit(&self, circuit_index: usize) -> Self { + debug!( + "getting primary_circuit for index {circuit_index} and Meta: {:?}", + self.meta() + ); + if circuit_index == 0 { + debug!("using Lurk circuit"); + return self.clone(); + }; + if let Some(z_ptr) = self.lang().get_coprocessor_z_ptr(circuit_index - 1) { + let meta = Meta::Coprocessor(*z_ptr); + debug!( + "using coprocessor {} with meta: {:?}", + circuit_index - 1, + meta + ); + Self::blank(self.folding_config(), meta) + } else { + debug!("unsupported primary circuit index: {circuit_index}"); + panic!("unsupported primary circuit index") + } + } +} diff --git a/src/ptr.rs b/src/ptr.rs index 581aa7d3a9..c8231fb840 100644 --- a/src/ptr.rs +++ b/src/ptr.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use std::marker::PhantomData; use crate::field::LurkField; -use crate::tag::{ContTag, ExprTag}; +use crate::tag::{ContTag, ExprTag, Tag}; /// The internal untagged raw Store pointer #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -53,9 +53,9 @@ impl RawPtr { /// A `Store` pointer #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Ptr { +pub struct GPtr { /// An expression tag - pub tag: ExprTag, + pub tag: T, /// The underlying pointer, which can be null, opaque, or an index pub raw: RawPtr, /// PhantomData is needed to consume the `F: LurkField` parameter, since @@ -65,65 +65,25 @@ pub struct Ptr { } #[allow(clippy::derived_hash_with_manual_eq)] -impl Hash for Ptr { +impl Hash for GPtr { fn hash(&self, state: &mut H) { self.tag.hash(state); self.raw.hash(state); } } -impl Ptr { - // TODO: Make these methods and the similar ones defined on expression consistent, probably including a shared trait. - - // NOTE: Although this could be a type predicate now, when NIL becomes a symbol, it won't be possible. - /// check if a Ptr is `Nil` pointer - pub const fn is_nil(&self) -> bool { - matches!(self.tag, ExprTag::Nil) - // FIXME: check value also, probably - } - - /// check if a Ptr is a `Cons` pointer - pub const fn is_cons(&self) -> bool { - matches!(self.tag, ExprTag::Cons) - } - - // TODO: Is this still needed? - /// check if a Ptr is atomic pointer - pub const fn is_atom(&self) -> bool { - !self.is_cons() - } - - // check if a Ptr is a list pointer - pub const fn is_list(&self) -> bool { - matches!(self.tag, ExprTag::Nil | ExprTag::Cons) - } +pub type Ptr = GPtr; +pub type ContPtr = GPtr; +impl GPtr { /// check if a Ptr is an opaque pointer pub const fn is_opaque(&self) -> bool { self.raw.is_opaque() } - // TODO: Is this still needed? - pub const fn as_cons(self) -> Option { - if self.is_cons() { - Some(self) - } else { - None - } - } - - // TODO: Is this still needed? - pub const fn as_list(self) -> Option { - if self.is_list() { - Some(self) - } else { - None - } - } - /// Construct a Ptr from an index - pub fn index(tag: ExprTag, idx: usize) -> Self { - Ptr { + pub fn index(tag: T, idx: usize) -> Self { + GPtr { tag, raw: RawPtr::Index(idx), _f: Default::default(), @@ -131,8 +91,8 @@ impl Ptr { } /// Construct a Ptr from an opaque index - pub fn opaque(tag: ExprTag, idx: usize) -> Self { - Ptr { + pub fn opaque(tag: T, idx: usize) -> Self { + GPtr { tag, raw: RawPtr::Opaque(idx), _f: Default::default(), @@ -140,8 +100,8 @@ impl Ptr { } /// Construct a null Ptr - pub fn null(tag: ExprTag) -> Self { - Ptr { + pub fn null(tag: T) -> Self { + GPtr { tag, raw: RawPtr::Null, _f: Default::default(), @@ -159,58 +119,69 @@ impl From for Ptr { } } -/// A pointer to a continuation. Logically this is the same a Ptr and should -/// probably be combined with it in a future refactor -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct ContPtr { - pub tag: ContTag, - pub raw: RawPtr, - pub _f: PhantomData, -} +impl GPtr { + #[inline] + pub fn cast(self, tag: ExprTag) -> Self { + GPtr { + tag, + raw: self.raw, + _f: self._f, + } + } -#[allow(clippy::derived_hash_with_manual_eq)] -impl Hash for ContPtr { - fn hash(&self, state: &mut H) { - self.tag.hash(state); - self.raw.hash(state); + // TODO: Is this still needed? + pub const fn as_cons(self) -> Option { + if self.is_cons() { + Some(self) + } else { + None + } } -} -impl ContPtr { - pub fn new(tag: ContTag, raw: RawPtr) -> Self { - Self { - tag, - raw, - _f: Default::default(), + // TODO: Is this still needed? + pub const fn as_list(self) -> Option { + if self.is_list() { + Some(self) + } else { + None } } - pub const fn is_error(&self) -> bool { - matches!(self.tag, ContTag::Error) + + // NOTE: Although this could be a type predicate now, when NIL becomes a symbol, it won't be possible. + /// check if a Ptr is `Nil` pointer + pub const fn is_nil(&self) -> bool { + matches!(self.tag, ExprTag::Nil) + // FIXME: check value also, probably } - pub fn index(tag: ContTag, idx: usize) -> Self { - ContPtr { - tag, - raw: RawPtr::Index(idx), - _f: Default::default(), - } + /// check if a Ptr is a `Cons` pointer + pub const fn is_cons(&self) -> bool { + matches!(self.tag, ExprTag::Cons) } - pub fn opaque(tag: ContTag, idx: usize) -> Self { - ContPtr { - tag, - raw: RawPtr::Index(idx), - _f: Default::default(), - } + // TODO: Is this still needed? + /// check if a Ptr is atomic pointer + pub const fn is_atom(&self) -> bool { + !self.is_cons() } - pub fn null(tag: ContTag) -> Self { - ContPtr { + // check if a Ptr is a list pointer + pub const fn is_list(&self) -> bool { + matches!(self.tag, ExprTag::Nil | ExprTag::Cons) + } +} + +impl ContPtr { + pub fn new(tag: ContTag, raw: RawPtr) -> Self { + Self { tag, - raw: RawPtr::Null, + raw, _f: Default::default(), } } + pub const fn is_error(&self) -> bool { + matches!(self.tag, ContTag::Error) + } } pub trait TypePredicates { diff --git a/src/public_parameters/disk_cache.rs b/src/public_parameters/disk_cache.rs index bca79b912e..c11790fb78 100644 --- a/src/public_parameters/disk_cache.rs +++ b/src/public_parameters/disk_cache.rs @@ -1,23 +1,30 @@ use std::fs::{create_dir_all, File}; -use std::io::{BufReader, BufWriter}; +use std::io::{BufReader, BufWriter, Read}; use std::marker::PhantomData; +use abomonation::{encode, Abomonation}; use camino::{Utf8Path, Utf8PathBuf}; +use nova::traits::Group; use crate::coprocessor::Coprocessor; -use crate::proof::nova::{CurveCycleEquipped, PublicParams}; +use crate::proof::nova::{CurveCycleEquipped, PublicParams, G1, G2}; use crate::public_parameters::error::Error; pub(crate) struct PublicParamDiskCache where F: CurveCycleEquipped, - C: Coprocessor + 'static, + C: Coprocessor, { dir: Utf8PathBuf, _t: PhantomData<(F, C)>, } -impl> PublicParamDiskCache { +impl> PublicParamDiskCache +where + // technical bounds that would disappear once associated_type_bounds stabilizes + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ pub(crate) fn new(disk_cache_path: &Utf8Path) -> Result { create_dir_all(disk_cache_path)?; @@ -39,6 +46,14 @@ impl> PublicParamDiskCache { }) } + pub(crate) fn get_raw_bytes(&self, key: &str) -> Result, Error> { + let file = File::open(self.key_path(key))?; + let mut reader = BufReader::new(file); + let mut bytes = Vec::new(); + reader.read_to_end(&mut bytes)?; + Ok(bytes) + } + pub(crate) fn set(&self, key: &str, data: &PublicParams<'static, F, C>) -> Result<(), Error> { let file = File::create(self.key_path(key)).expect("failed to create file"); let writer = BufWriter::new(&file); @@ -46,4 +61,10 @@ impl> PublicParamDiskCache { Error::CacheError(format!("Public param cache serialization error: {}", e)) }) } + + pub(crate) fn set_abomonated(&self, key: &str, data: &V) -> Result<(), Error> { + let mut file = File::create(self.key_path(key))?; + unsafe { encode(data, &mut file).expect("failed to encode") }; + Ok(()) + } } diff --git a/src/public_parameters/error.rs b/src/public_parameters/error.rs index 9624a614b7..946d0cade8 100644 --- a/src/public_parameters/error.rs +++ b/src/public_parameters/error.rs @@ -1,6 +1,7 @@ use std::io; use thiserror::Error; +#[non_exhaustive] #[derive(Error, Debug)] pub enum Error { #[error("IO error: {0}")] diff --git a/src/public_parameters/mem_cache.rs b/src/public_parameters/mem_cache.rs index 49cf519e14..3c93794dff 100644 --- a/src/public_parameters/mem_cache.rs +++ b/src/public_parameters/mem_cache.rs @@ -3,18 +3,24 @@ use std::{ sync::{Arc, Mutex}, }; +use abomonation::{decode, Abomonation}; use camino::Utf8Path; -use log::info; +use nova::traits::Group; use once_cell::sync::Lazy; use tap::TapFallible; +use tracing::{info, warn}; -use crate::{coprocessor::Coprocessor, eval::lang::Lang, proof::nova::PublicParams}; +use crate::{ + coprocessor::Coprocessor, + eval::lang::Lang, + proof::nova::{PublicParams, G1, G2}, +}; use crate::{proof::nova::CurveCycleEquipped, public_parameters::error::Error}; use super::disk_cache::PublicParamDiskCache; type AnyMap = anymap::Map; -type PublicParamMap = HashMap>>; +type PublicParamMap = HashMap<(usize, bool), Arc>>; /// This is a global registry for Coproc-specific parameters. /// It is used to cache parameters for each Coproc, so that they are not @@ -34,32 +40,64 @@ pub(crate) static PUBLIC_PARAM_MEM_CACHE: Lazy = impl PublicParamMemCache { fn get_from_disk_cache_or_update_with< F: CurveCycleEquipped, - C: Coprocessor + 'static, + C: Coprocessor, Fn: FnOnce(Arc>) -> Arc>, >( &'static self, rc: usize, + abomonated: bool, default: Fn, lang: Arc>, disk_cache_path: &Utf8Path, - ) -> Result>, Error> { + ) -> Result>, Error> + where + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + { // subdirectory search let disk_cache = PublicParamDiskCache::new(disk_cache_path).unwrap(); // use the cached language key let lang_key = lang.key(); + let quick_suffix = if abomonated { "-abomonated" } else { "" }; // Sanity-check: we're about to use a lang-dependent disk cache, which should be specialized // for this lang/coprocessor. - let key = format!("public-params-rc-{rc}-coproc-{lang_key}"); + let key = format!("public-params-rc-{rc}-coproc-{lang_key}{quick_suffix}"); // read the file if it exists, otherwise initialize - if let Ok(pp) = disk_cache.get(&key) { - info!("Using disk-cached public params for lang {lang_key}"); - Ok(Arc::new(pp)) + if abomonated { + match disk_cache.get_raw_bytes(&key) { + Ok(mut bytes) => { + info!("loading abomonated {lang_key}"); + let (pp, rest) = + unsafe { decode::>(&mut bytes).unwrap() }; + assert!(rest.is_empty()); + Ok(Arc::new(pp.clone())) // this clone is VERY expensive + } + Err(Error::IOError(e)) => { + warn!("{e}"); + info!("Generating fresh public parameters"); + let pp = default(lang); + // maybe just directly write + disk_cache + .set_abomonated(&key, &*pp) + .tap_ok(|_| info!("writing public params to disk-cache: {}", lang_key)) + .map_err(|e| Error::CacheError(format!("Disk write error: {}", e)))?; + Ok(pp) + } + _ => unreachable!(), + } } else { - let pp = default(lang); - disk_cache - .set(&key, &*pp) - .tap_ok(|_| info!("Writing public params to disk-cache for lang {lang_key}"))?; - Ok(pp) + // read the file if it exists, otherwise initialize + if let Ok(pp) = disk_cache.get(&key) { + info!("loading abomonated {lang_key}"); + Ok(Arc::new(pp)) + } else { + let pp = default(lang); + disk_cache + .set(&key, &*pp) + .tap_ok(|_| info!("writing public params to disk-cache: {}", lang_key)) + .map_err(|e| Error::CacheError(format!("Disk write error: {}", e)))?; + Ok(pp) + } } } @@ -72,6 +110,7 @@ impl PublicParamMemCache { >( &'static self, rc: usize, + abomonated: bool, default: Fn, lang: Arc>, disk_cache_path: &Utf8Path, @@ -79,21 +118,28 @@ impl PublicParamMemCache { where F::CK1: Sync + Send, F::CK2: Sync + Send, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, { // re-grab the lock let mut mem_cache = self.mem_cache.lock().unwrap(); // retrieve the per-Coproc public param table let entry = mem_cache.entry::>(); // deduce the map and populate it if needed - let param_entry = entry.or_insert_with(HashMap::new); - match param_entry.entry(rc) { + let param_entry = entry.or_default(); + match param_entry.entry((rc, abomonated)) { Entry::Occupied(o) => Ok(o.into_mut()), Entry::Vacant(v) => { - let val = - self.get_from_disk_cache_or_update_with(rc, default, lang, disk_cache_path)?; + let val = self.get_from_disk_cache_or_update_with( + rc, + true, + default, + lang, + disk_cache_path, + )?; Ok(v.insert(val)) } } - .cloned() + .cloned() // this clone is VERY expensive } } diff --git a/src/public_parameters/mod.rs b/src/public_parameters/mod.rs index 90ac65e5a4..f9134c1880 100644 --- a/src/public_parameters/mod.rs +++ b/src/public_parameters/mod.rs @@ -1,8 +1,10 @@ +use ::nova::traits::Group; +use abomonation::{decode, Abomonation}; use camino::{Utf8Path, Utf8PathBuf}; use std::sync::Arc; use crate::coprocessor::Coprocessor; -use crate::proof::nova::CurveCycleEquipped; +use crate::proof::nova::{CurveCycleEquipped, G1, G2}; use crate::{ eval::lang::Lang, proof::nova::{self, PublicParams}, @@ -28,22 +30,80 @@ pub fn public_params_default_dir() -> Utf8PathBuf { pub fn public_params + 'static>( rc: usize, + abomonated: bool, lang: Arc>, disk_cache_path: &Utf8Path, ) -> Result>, Error> where F::CK1: Sync + Send, F::CK2: Sync + Send, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, { let f = |lang: Arc>| Arc::new(nova::public_params(rc, lang)); mem_cache::PUBLIC_PARAM_MEM_CACHE.get_from_mem_cache_or_update_with( rc, + abomonated, f, lang, disk_cache_path, ) } +/// Attempts to extract abomonated public parameters. +/// To avoid all copying overhead, we zerocopy all of the data within the file; +/// this leads to extremely high performance, but restricts the lifetime of the data +/// to the lifetime of the file. Thus, we cannot pass a reference out and must +/// rely on a closure to capture the data and continue the computation in `bind`. +pub fn with_public_params( + rc: usize, + lang: Arc>, + bind: Fn, +) -> Result +where + C: Coprocessor + 'static, + Fn: FnOnce(&PublicParams<'static, F, C>) -> T, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, + < as Group>::Scalar as ff::PrimeField>::Repr: Abomonation, +{ + let disk_cache = + disk_cache::PublicParamDiskCache::::new(&public_params_default_dir()).unwrap(); + // use the cached language key + let lang_key = lang.key(); + // Sanity-check: we're about to use a lang-dependent disk cache, which should be specialized + // for this lang/coprocessor. + let key = format!("public-params-rc-{rc}-coproc-{lang_key}-abomonated"); + + match disk_cache.get_raw_bytes(&key) { + Ok(mut bytes) => { + if let Some((pp, remaining)) = unsafe { decode(&mut bytes) } { + assert!(remaining.is_empty()); + eprintln!("Using disk-cached public params for lang {lang_key}"); + Ok(bind(pp)) + } else { + eprintln!("failed to decode bytes"); + let pp = nova::public_params(rc, lang); + let mut bytes = Vec::new(); + unsafe { abomonation::encode(&pp, &mut bytes)? }; + // maybe just directly write + disk_cache + .set_abomonated(&key, &pp) + .map_err(|e| Error::CacheError(format!("Disk write error: {}", e)))?; + Ok(bind(&pp)) + } + } + Err(e) => { + eprintln!("{e}"); + let pp = nova::public_params(rc, lang); + // maybe just directly write + disk_cache + .set_abomonated(&key, &pp) + .map_err(|e| Error::CacheError(format!("Disk write error: {}", e)))?; + Ok(bind(&pp)) + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -61,8 +121,8 @@ mod tests { let lang: Arc>> = Arc::new(Lang::new()); // Without disk cache, writes to tmpfile - let _public_params = public_params(10, lang.clone(), &public_params_dir).unwrap(); + let _public_params = public_params(10, true, lang.clone(), &public_params_dir).unwrap(); // With disk cache, reads from tmpfile - let _public_params = public_params(10, lang, &public_params_dir).unwrap(); + let _public_params = public_params(10, true, lang, &public_params_dir).unwrap(); } } diff --git a/src/repl.rs b/src/repl.rs index c23161420d..95b7b44f9f 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -3,14 +3,15 @@ use crate::error::LurkError; use crate::eval::{empty_sym_env, lang::Lang, Evaluator, IO}; use crate::expr::Expression; use crate::field::LurkField; -use crate::parser; use crate::ptr::{ContPtr, Ptr}; +use crate::state::State; use crate::store::Store; use crate::symbol::Symbol; use crate::tag::ContTag; use crate::writer::Write; use crate::z_data::{from_z_data, ZData}; use crate::z_store::ZStore; +use crate::{lurk_sym_ptr, parser}; use anyhow::{bail, Context, Error, Result}; use clap::{Arg, ArgAction, Command}; use rustyline::error::ReadlineError; @@ -27,6 +28,7 @@ use std::path::Path; #[cfg(not(target_arch = "wasm32"))] use std::path::PathBuf; use std::sync::Arc; +use std::{cell::RefCell, rc::Rc}; use tap::TapOptional; #[derive(Completer, Helper, Highlighter, Hinter)] @@ -40,7 +42,7 @@ impl Validator for InputValidator { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ReplState> { pub env: Ptr, pub limit: usize, @@ -48,6 +50,7 @@ pub struct ReplState> { pub lang: Arc>, } +#[derive(Debug)] pub struct Repl, C: Coprocessor> { state: T, rl: Editor, @@ -70,33 +73,39 @@ pub trait ReplTrait> { fn handle_form<'a, P: AsRef + Copy>( &mut self, store: &mut Store, + state: Rc>, input: parser::Span<'a>, pwd: P, ) -> Result> { - let (input, ptr, is_meta) = store.read_maybe_meta(input)?; + let (input, ptr, is_meta) = store.read_maybe_meta_with_state(state.clone(), input)?; if is_meta { let pwd: &Path = pwd.as_ref(); - self.handle_meta(store, ptr, pwd)?; + self.handle_meta(store, state, ptr, pwd)?; Ok(input) } else { - self.handle_non_meta(store, ptr).map(|_| ())?; + self.handle_non_meta(store, &state.borrow(), ptr) + .map(|_| ())?; Ok(input) } } - fn handle_load>(&mut self, store: &mut Store, file_path: P) -> Result<()> { + fn handle_load>( + &mut self, + store: &mut Store, + state: Rc>, + file_path: P, + ) -> Result<()> { eprintln!("Loading from {}.", file_path.as_ref().to_str().unwrap()); - self.handle_file(store, file_path.as_ref()) + self.handle_file(store, state, file_path.as_ref()) } fn handle_file + Copy>( &mut self, store: &mut Store, + state: Rc>, file_path: P, ) -> Result<()> { - let file_path = file_path; - let input = read_to_string(file_path)?; eprintln!( "Read from {}: {}", @@ -108,6 +117,7 @@ pub trait ReplTrait> { loop { match self.handle_form( store, + state.clone(), input, // use this file's dir as pwd for further loading file_path.as_ref().parent().unwrap(), @@ -128,6 +138,7 @@ pub trait ReplTrait> { fn handle_meta + Copy>( &mut self, store: &mut Store, + state: Rc>, expr_ptr: Ptr, p: P, ) -> Result<()>; @@ -135,6 +146,7 @@ pub trait ReplTrait> { fn handle_non_meta( &mut self, store: &mut Store, + state: &State, expr_ptr: Ptr, ) -> Result<(IO, IO, usize)>; } @@ -250,10 +262,11 @@ pub fn run_repl, F: LurkField, T: ReplTrait, C: Coprocessor let name = T::name(); eprintln!("{name} welcomes you."); } + let state = State::init_lurk_state().rccell(); { if let Some(lurk_file) = lurk_file { - repl.state.handle_load(s, &lurk_file).unwrap(); + repl.state.handle_load(s, state, &lurk_file).unwrap(); return Ok(()); } } @@ -271,16 +284,16 @@ pub fn run_repl, F: LurkField, T: ReplTrait, C: Coprocessor #[cfg(not(target_arch = "wasm32"))] repl.save_history()?; - match s.read_maybe_meta(input) { + match s.read_maybe_meta_with_state(state.clone(), input) { Ok((_, expr, is_meta)) => { if is_meta { - if let Err(e) = repl.state.handle_meta(s, expr, p) { - eprintln!("!Error: {e:?}"); + if let Err(e) = repl.state.handle_meta(s, state.clone(), expr, p) { + eprintln!("!Error: {e}"); }; continue; } else { - if let Err(e) = repl.state.handle_non_meta(s, expr) { - eprintln!("REPL Error: {e:?}"); + if let Err(e) = repl.state.handle_non_meta(s, &state.borrow(), expr) { + eprintln!("REPL Error: {e}"); } continue; @@ -290,7 +303,7 @@ pub fn run_repl, F: LurkField, T: ReplTrait, C: Coprocessor continue; } Err(e) => { - eprintln!("Read error: {e:?}") + eprintln!("Read error: {e}") } } } @@ -299,7 +312,7 @@ pub fn run_repl, F: LurkField, T: ReplTrait, C: Coprocessor break; } Err(err) => { - eprintln!("Error: {err:?}"); + eprintln!("Error: {err}"); break; } } @@ -309,7 +322,7 @@ pub fn run_repl, F: LurkField, T: ReplTrait, C: Coprocessor } impl> ReplState { - pub fn new(s: &mut Store, limit: usize, command: Option, lang: Lang) -> Self { + pub fn new(s: &Store, limit: usize, command: Option, lang: Lang) -> Self { Self { env: empty_sym_env(s), limit, @@ -384,6 +397,7 @@ impl> ReplTrait for ReplState { fn handle_meta + Copy>( &mut self, store: &mut Store, + state: Rc>, expr_ptr: Ptr, p: P, ) -> Result<()> { @@ -395,7 +409,7 @@ impl> ReplTrait for ReplState { let s: Symbol = store .fetch_sym(&car) .ok_or(Error::msg("handle_meta fetch symbol"))?; - match format!("{}", s).as_str() { + match s.name()? { "assert" => { let (first, rest) = store.car_cdr(&rest)?; assert!(rest.is_nil()); @@ -421,10 +435,10 @@ impl> ReplTrait for ReplState { assert!( store.ptr_eq(&first_evaled, &second_evaled).unwrap(), "Assertion failed {:?} = {:?},\n {:?} != {:?}", - first.fmt_to_string(store), - second.fmt_to_string(store), - first_evaled.fmt_to_string(store), - second_evaled.fmt_to_string(store) + first.fmt_to_string(store, &state.borrow()), + second.fmt_to_string(store, &state.borrow()), + first_evaled.fmt_to_string(store, &state.borrow()), + second_evaled.fmt_to_string(store, &state.borrow()) ); None } @@ -441,14 +455,12 @@ impl> ReplTrait for ReplState { let (mut first_emitted, mut rest_emitted) = store.car_cdr(&first_evaled)?; for (i, elem) in emitted.iter().enumerate() { - if elem != &first_emitted { - panic!( + assert_eq!(elem , &first_emitted, ":ASSERT-EMITTED failed at position {}. Expected {}, but found {}.", i, - first_emitted.fmt_to_string(store), - elem.fmt_to_string(store), + first_emitted.fmt_to_string(store, &state.borrow()), + elem.fmt_to_string(store, &state.borrow()), ); - } (first_emitted, rest_emitted) = store.car_cdr(&rest_emitted)?; } None @@ -477,8 +489,8 @@ impl> ReplTrait for ReplState { let (first, rest) = store.car_cdr(&rest)?; let (second, rest) = store.car_cdr(&rest)?; assert!(rest.is_nil()); - let l = store.lurk_sym("let"); - let current_env = store.lurk_sym("current-env"); + let l = lurk_sym_ptr!(store, let_); + let current_env = lurk_sym_ptr!(store, current_env); let binding = store.list(&[first, second]); let bindings = store.list(&[binding]); let current_env_call = store.list(&[current_env]); @@ -503,8 +515,8 @@ impl> ReplTrait for ReplState { let (first, rest) = store.car_cdr(&rest)?; let (second, rest) = store.car_cdr(&rest)?; assert!(rest.is_nil()); - let l = store.lurk_sym("letrec"); - let current_env = store.lurk_sym("current-env"); + let l = lurk_sym_ptr!(store, letrec); + let current_env = lurk_sym_ptr!(store, current_env); let binding = store.list(&[first, second]); let bindings = store.list(&[binding]); let current_env_call = store.list(&[current_env]); @@ -526,7 +538,7 @@ impl> ReplTrait for ReplState { .fetch_string(car) .ok_or(Error::msg("handle_meta fetch_string"))?; let joined = p.as_ref().join(Path::new(&path)); - self.handle_load(store, &joined)? + self.handle_load(store, state.clone(), &joined)? } _ => bail!("Argument to LOAD must be a string."), } @@ -542,18 +554,27 @@ impl> ReplTrait for ReplState { None } _ => { - bail!("Unsupported command: {}", car.fmt_to_string(store)); + bail!( + "Unsupported command: {}", + car.fmt_to_string(store, &state.borrow()) + ); } } } - _ => bail!("Unsupported command: {}", car.fmt_to_string(store)), + _ => bail!( + "Unsupported command: {}", + car.fmt_to_string(store, &state.borrow()) + ), }, - _ => bail!("Unsupported meta form: {}", expr_ptr.fmt_to_string(store)), + _ => bail!( + "Unsupported meta form: {}", + expr_ptr.fmt_to_string(store, &state.borrow()) + ), }; if let Some(expr) = res { let mut handle = io::stdout().lock(); - expr.fmt(store, &mut handle)?; + expr.fmt(store, &state.borrow(), &mut handle)?; // TODO: Why is this seemingly necessary to flush? // This doesn't work: io::stdout().flush().unwrap(); @@ -568,6 +589,7 @@ impl> ReplTrait for ReplState { fn handle_non_meta( &mut self, store: &mut Store, + state: &State, expr_ptr: Ptr, ) -> Result<(IO, IO, usize)> { match Evaluator::new(expr_ptr, self.env, store, self.limit, &self.lang).eval() { @@ -590,7 +612,7 @@ impl> ReplTrait for ReplState { ContTag::Outermost | ContTag::Terminal => { let mut handle = io::stdout().lock(); - result.fmt(store, &mut handle)?; + result.fmt(store, state, &mut handle)?; println!(); } @@ -603,7 +625,7 @@ impl> ReplTrait for ReplState { } Err(e) => { - eprintln!("Evaluation error: {e:?}"); + eprintln!("Evaluation error: {e}"); Err(e.into()) } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000000..cc9a93dfc6 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,328 @@ +#![deny(missing_docs)] + +//! This module implements an abstraction for the Lurk state, which changes as +//! Lurk code is evaluated + +use anyhow::{bail, Result}; +use once_cell::sync::OnceCell; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use crate::Symbol; + +use super::package::{Package, SymbolRef}; + +/// Keeps track of the current package for symbol resolution when reading and printing +#[derive(Debug)] +pub struct State { + current_package: SymbolRef, + symbol_packages: HashMap, +} + +impl State { + /// Wraps a state with `Rc>` + #[inline] + pub fn rccell(self) -> Rc> { + Rc::new(RefCell::new(self)) + } + + /// Creates a new state with a given package as the current one + pub fn new_with_package(package: Package) -> Self { + let current_package = package.name().clone(); + let mut symbol_packages = HashMap::default(); + symbol_packages.insert(current_package.clone(), package); + Self { + current_package, + symbol_packages, + } + } + + /// Adds a package to a state + pub fn add_package(&mut self, package: Package) { + self.symbol_packages.insert(package.name().clone(), package); + } + + /// Sets the current package of the state + pub fn set_current_package(&mut self, package_name: SymbolRef) -> Result<()> { + if self.symbol_packages.contains_key(&package_name) { + self.current_package = package_name; + Ok(()) + } else { + bail!("Package {package_name} not found") + } + } + + /// Returns the name of the current package + #[inline] + pub const fn get_current_package_name(&self) -> &SymbolRef { + &self.current_package + } + + /// Returns a reference to the current package + fn get_current_package(&self) -> &Package { + self.symbol_packages + .get(&self.current_package) + .expect("current package must be in the hashmap") + } + + /// Returns a mutable reference to the current package + fn get_current_package_mut(&mut self) -> &mut Package { + self.symbol_packages + .get_mut(&self.current_package) + .expect("current package must be in the hashmap") + } + + /// Returns the resolved (fully-qualified) symbol given a symbol name + pub fn resolve(&self, symbol_name: &str) -> Option<&SymbolRef> { + self.get_current_package().resolve(symbol_name) + } + + /// Interns a symbol into the current package + pub fn intern>(&mut self, symbol_name: A) -> SymbolRef { + self.get_current_package_mut() + .intern(String::from(symbol_name.as_ref())) + } + + /// Imports a set of symbols to the current package + pub fn import(&mut self, symbols: &[SymbolRef]) -> Result<()> { + self.get_current_package_mut().import(symbols) + } + + /// Imports all symbols from a certain package + pub fn use_package(&mut self, package: &Package) -> Result<()> { + self.get_current_package_mut().use_package(package) + } + + /// Formats a symbol to string w.r.t. the current package + pub fn fmt_to_string(&self, symbol: &SymbolRef) -> String { + self.get_current_package().fmt_to_string(symbol) + } + + /// Sequentially intern a symbol into the potentially nested packages according + /// to its path + fn intern_fold>( + &mut self, + init: SymbolRef, + path: &[A], + create_unknown_packges: bool, + ) -> Result { + path.iter() + .try_fold(init, |acc, s| match self.symbol_packages.get_mut(&acc) { + Some(package) => Ok(package.intern(String::from(s.as_ref()))), + None => { + if create_unknown_packges { + let mut package = Package::new(acc); + let symbol = package.intern(String::from(s.as_ref())); + self.add_package(package); + Ok(symbol) + } else { + bail!("Package {acc} not found") + } + } + }) + } + + /// Call `intern_fold` w.r.t. the root package + #[inline] + pub fn intern_path>( + &mut self, + path: &[A], + keyword: bool, + create_unknown_packges: bool, + ) -> Result { + self.intern_fold(Symbol::root(keyword).into(), path, create_unknown_packges) + } + + /// Call `intern_fold` w.r.t. the current package + #[inline] + pub fn intern_relative_path>( + &mut self, + path: &[A], + create_unknown_packges: bool, + ) -> Result { + self.intern_fold(self.current_package.clone(), path, create_unknown_packges) + } + + /// Initiates the Lurk state with the appropriate structure of packages + pub fn init_lurk_state() -> Self { + let mut root_package = Package::new(SymbolRef::new(Symbol::root_sym())); + + // bootstrap the keyword package + let keyword_package = Package::new(SymbolRef::new(Symbol::root_key())); + + // bootstrap the lurk package + let mut lurk_package = Package::new(root_package.intern(LURK_PACKAGE_SYMBOL_NAME.into())); + for symbol_name in LURK_PACKAGE_SYMBOLS_NAMES.iter() { + lurk_package.intern((*symbol_name).to_string()); + } + + // bootstrap the meta package + let mut meta_package = Package::new(lurk_package.intern(META_PACKAGE_SYMBOL_NAME.into())); + for symbol_name in META_PACKAGE_SYMBOLS_NAMES.iter() { + meta_package.intern((*symbol_name).to_string()); + } + + // bootstrap the user package + let mut user_package = Package::new(lurk_package.intern(USER_PACKAGE_SYMBOL_NAME.into())); + user_package + .use_package(&lurk_package) + .expect("all symbols in the lurk package are importable"); + + // initiate the state with the lurk user package then add the others + let mut state = Self::new_with_package(user_package); + state.add_package(root_package); + state.add_package(keyword_package); + state.add_package(lurk_package); + state.add_package(meta_package); + state + } +} + +impl Default for State { + fn default() -> Self { + Self { + current_package: SymbolRef::new(Symbol::root_sym()), + symbol_packages: Default::default(), + } + } +} + +/// Returns the symbol in the Lurk package given the symbol name +#[inline] +pub fn lurk_sym(name: &str) -> Symbol { + Symbol::sym(&[LURK_PACKAGE_SYMBOL_NAME, name]) +} + +/// Returns the symbol corresponding to the name of the meta package +#[inline] +pub fn meta_package_symbol() -> Symbol { + lurk_sym(META_PACKAGE_SYMBOL_NAME) +} + +/// Returns the symbol in the user package given the symbol name +#[inline] +pub fn user_sym(name: &str) -> Symbol { + Symbol::sym(&[LURK_PACKAGE_SYMBOL_NAME, USER_PACKAGE_SYMBOL_NAME, name]) +} + +static INITIAL_LURK_STATE_CELL: OnceCell = OnceCell::new(); + +/// Returns a shared reference to the initial Lurk state +pub fn initial_lurk_state() -> &'static State { + INITIAL_LURK_STATE_CELL.get_or_init(State::init_lurk_state) +} + +const LURK_PACKAGE_SYMBOL_NAME: &str = "lurk"; +const USER_PACKAGE_SYMBOL_NAME: &str = "user"; +const META_PACKAGE_SYMBOL_NAME: &str = "meta"; + +const LURK_PACKAGE_SYMBOLS_NAMES: [&str; 36] = [ + "atom", + "begin", + "car", + "cdr", + "char", + "comm", + "commit", + "cons", + "current-env", + "emit", + "eval", + "eq", + "hide", + "if", + "lambda", + "let", + "letrec", + "nil", + "num", + "u64", + "open", + "quote", + "secret", + "strcons", + "t", + "+", + "-", + "*", + "/", + "%", + "=", + "<", + ">", + "<=", + ">=", + "_", +]; + +const META_PACKAGE_SYMBOLS_NAMES: [&str; 19] = [ + "def", + "defrec", + "load", + "assert", + "assert-eq", + "assert-emitted", + "assert-error", + "commit", + "hide", + "fetch", + "open", + "clear", + "set-env", + "prove", + "verify", + "defpackage", + "import", + "in-package", + "help", +]; + +#[cfg(test)] +pub mod test { + use super::{lurk_sym, State, LURK_PACKAGE_SYMBOLS_NAMES}; + use crate::{ + package::{Package, SymbolRef}, + Symbol, + }; + + #[inline] + fn test_printing_helper(state: &State, symbol: SymbolRef, expected: &str) { + assert_eq!(state.fmt_to_string(&symbol), expected.to_string()); + } + + #[test] + fn test_lurk_state_printing() { + let mut state = State::init_lurk_state(); + + LURK_PACKAGE_SYMBOLS_NAMES + .iter() + .for_each(|s| test_printing_helper(&state, lurk_sym(s).into(), s)); + + let user_sym = state.intern("user-sym"); + test_printing_helper(&state, user_sym.clone(), "user-sym"); + + let my_package_name = SymbolRef::new(Symbol::sym(&["my-package"])); + let mut my_package = Package::new(my_package_name.clone()); + let my_symbol = my_package.intern("my-symbol".into()); + state.add_package(my_package); + + test_printing_helper(&state, my_symbol.clone(), ".my-package.my-symbol"); + + let lambda_sym = SymbolRef::new(lurk_sym("lambda")); + + state.set_current_package(my_package_name).unwrap(); + test_printing_helper(&state, my_symbol, "my-symbol"); + test_printing_helper(&state, lambda_sym.clone(), ".lurk.lambda"); + + state.import(&[lambda_sym.clone()]).unwrap(); + test_printing_helper(&state, lambda_sym, "lambda"); + test_printing_helper(&state, user_sym, ".lurk.user.user-sym"); + + let path = ["my-package", "my-other-symbol"]; + state.intern_path(&path, false, false).unwrap(); + test_printing_helper( + &state, + SymbolRef::new(Symbol::sym(&path)), + "my-other-symbol", + ); + } +} diff --git a/src/store.rs b/src/store.rs index f7a85698df..0d5ccd87f8 100644 --- a/src/store.rs +++ b/src/store.rs @@ -1,7 +1,6 @@ use rayon::prelude::*; use std::collections::HashMap; use std::fmt; -use std::sync::Arc; use std::usize; use thiserror; @@ -13,6 +12,7 @@ use crate::expr; use crate::expr::{Expression, Thunk}; use crate::field::{FWrap, LurkField}; use crate::ptr::{ContPtr, Ptr, RawPtr}; +use crate::state::{lurk_sym, user_sym}; use crate::symbol::Symbol; use crate::tag::{ContTag, ExprTag, Op1, Op2, Tag}; use crate::z_cont::ZCont; @@ -71,19 +71,20 @@ pub struct Store { pub poseidon_cache: PoseidonCache, /// Caches poseidon preimages pub inverse_poseidon_cache: InversePoseidonCache, + /// Contains Ptrs which have not yet been hydrated. pub dehydrated: Vec>, pub dehydrated_cont: Vec>, - str_cache: HashMap, Ptr>, - symbol_cache: HashMap>>, + str_cache: HashMap>, + symbol_cache: HashMap>, pub constants: OnceCell>, } impl Default for Store { fn default() -> Self { - let mut store = Store { + let mut store = Self { cons_store: Default::default(), comm_store: Default::default(), sym_store: Default::default(), @@ -117,11 +118,7 @@ impl Default for Store { symbol_cache: Default::default(), constants: Default::default(), }; - - for (sym, _) in Symbol::lurk_syms() { - store.intern_symbol(sym); - } - + store.ensure_constants(); store } } @@ -135,31 +132,40 @@ impl fmt::Display for Error { } } +#[macro_export] +macro_rules! lurk_sym_ptr { + ( $store:expr, $sym:ident ) => {{ + $store.expect_constants().$sym.ptr() + }}; +} + /// These methods provide a more ergonomic means of constructing and manipulating Lurk data. /// They can be thought of as a minimal DSL for working with Lurk data in Rust code. /// Prefer these methods when constructing literal data or assembling program fragments in /// tests or during evaluation, etc. impl Store { - pub fn nil(&mut self) -> Ptr { - self.lurk_sym("nil") - } - - pub fn t(&mut self) -> Ptr { - self.lurk_sym("t") + pub fn expect_constants(&self) -> &NamedConstants { + self.constants + .get() + .expect("Constants must have been set during instantiation") } + #[inline] pub fn cons(&mut self, car: Ptr, cdr: Ptr) -> Ptr { self.intern_cons(car, cdr) } + #[inline] pub fn strcons(&mut self, car: Ptr, cdr: Ptr) -> Ptr { self.intern_strcons(car, cdr) } + #[inline] pub fn strnil(&self) -> Ptr { Ptr::null(ExprTag::Str) } + #[inline] pub fn symnil(&self) -> Ptr { Ptr::null(ExprTag::Sym) } @@ -252,20 +258,16 @@ impl Store { self.intern_u64(n) } - pub fn str>(&mut self, name: T) -> Ptr { - self.intern_string(name) - } - - pub fn lurk_sym>(&mut self, name: T) -> Ptr { - self.intern_symbol(Symbol::new(&["lurk", name.as_ref()])) + pub fn str(&mut self, s: &str) -> Ptr { + self.intern_string(s) } pub fn sym>(&mut self, name: T) -> Ptr { - self.intern_symbol(Symbol::new(&[name.as_ref()])) + self.intern_symbol(&Symbol::sym(&[name.as_ref()])) } pub fn key>(&mut self, name: T) -> Ptr { - self.intern_symbol(Symbol::new(&["keyword", name.as_ref()])) + self.intern_symbol(&Symbol::key(&[name.as_ref()])) } pub fn car(&self, expr: &Ptr) -> Result, Error> { @@ -292,26 +294,6 @@ impl Store { } } - pub fn intern_strnil(&self) -> Ptr { - Ptr::null(ExprTag::Str) - } - - pub fn get_nil(&self) -> Ptr { - self.get_lurk_sym("nil").expect("missing NIL") - } - - pub fn get_begin(&self) -> Ptr { - self.get_lurk_sym("begin").expect("missing BEGIN") - } - - pub fn get_quote(&self) -> Ptr { - self.get_lurk_sym("quote").expect("missing QUOTE") - } - - pub fn get_t(&self) -> Ptr { - self.get_lurk_sym("t").expect("missing T") - } - pub fn intern_cons(&mut self, car: Ptr, cdr: Ptr) -> Ptr { if car.is_opaque() || cdr.is_opaque() { self.hash_expr(&car); @@ -453,54 +435,39 @@ impl Store { pub fn intern_list(&mut self, elts: &[Ptr]) -> Ptr { elts.iter() .rev() - .fold(self.lurk_sym("nil"), |acc, elt| self.intern_cons(*elt, acc)) + .fold(lurk_sym_ptr!(self, nil), |acc, elt| { + self.intern_cons(*elt, acc) + }) } - pub fn intern_symbol(&mut self, sym: Symbol) -> Ptr { - let ptr = if sym.path.is_empty() { - Ptr::null(ExprTag::Sym) - } else { - let mut ptr = self.symnil(); - for s in sym.path.iter() { - let str_ptr = self.intern_string(s); - ptr = self.intern_symcons(str_ptr, ptr); - } - if sym == Symbol::nil() { - Ptr { - tag: ExprTag::Nil, - raw: ptr.raw, - _f: ptr._f, - } - } else if sym.is_keyword() { - Ptr { - tag: ExprTag::Key, - raw: ptr.raw, - _f: ptr._f, - } - } else { - ptr - } - }; - self.symbol_cache.insert(sym, Box::new(ptr)); - ptr + pub fn intern_symbol_path(&mut self, path: &[String]) -> Ptr { + path.iter().fold(self.symnil(), |acc, s| { + let s_ptr = self.intern_string(s); + self.intern_symcons(s_ptr, acc) + }) } - pub fn get_sym(&self, sym: &Symbol) -> Option> { - let ptr = self.symbol_cache.get(sym).cloned()?; - if *sym == Symbol::nil() { - Some(Ptr { - tag: ExprTag::Nil, - raw: ptr.raw, - _f: ptr._f, - }) - } else { - Some(*ptr) + pub fn intern_symbol(&mut self, sym: &Symbol) -> Ptr { + match self.symbol_cache.get(sym) { + Some(ptr) => *ptr, + None => { + use crate::tag::ExprTag::{Key, Nil}; + let path_ptr = self.intern_symbol_path(sym.path()); + let sym_ptr = if sym == &lurk_sym("nil") { + path_ptr.cast(Nil) + } else if sym.is_keyword() { + path_ptr.cast(Key) + } else { + path_ptr + }; + self.symbol_cache.insert(sym.clone(), sym_ptr); + sym_ptr + } } } - pub fn get_lurk_sym>(&self, name: T) -> Option> { - let sym = Symbol::lurk_sym(name.as_ref()); - self.get_sym(&sym) + pub fn user_sym(&mut self, name: &str) -> Ptr { + self.intern_symbol(&user_sym(name)) } pub fn intern_num>>(&mut self, num: T) -> Ptr { @@ -538,6 +505,7 @@ impl Store { .map(|x| Ptr::index(ExprTag::Num, x)) } + #[inline] pub fn intern_char(&self, c: char) -> Ptr { Ptr::index(ExprTag::Char, u32::from(c) as usize) } @@ -552,22 +520,15 @@ impl Store { Ptr::index(ExprTag::U64, n as usize) } - /// Intern a string into the Store, which generates the cons'ed representation - pub fn intern_string>(&mut self, s: T) -> Ptr { - let s = s.as_ref(); - if s.is_empty() { - return self.strnil(); - } - + pub fn intern_string(&mut self, s: &str) -> Ptr { match self.str_cache.get(s) { - Some(ptr_cache) => *ptr_cache, + Some(ptr) => *ptr, None => { - let tail = &s.chars().skip(1).collect::(); - let tail_ptr = self.intern_string(tail); - let head = s.chars().next().unwrap(); - let s_ptr = self.intern_strcons(self.intern_char(head), tail_ptr); - self.str_cache.insert(s.into(), s_ptr); - s_ptr + let ptr = s.chars().rev().fold(self.strnil(), |acc, c| { + self.intern_strcons(self.intern_char(c), acc) + }); + self.str_cache.insert(s.to_string(), ptr); + ptr } } } @@ -600,6 +561,7 @@ impl Store { pub fn get_cont_outermost(&self) -> ContPtr { Continuation::Outermost.get_simple_cont() } + pub fn get_cont_error(&self) -> ContPtr { Continuation::Error.get_simple_cont() } @@ -663,13 +625,7 @@ impl Store { // fetch a symbol cons or keyword cons pub fn fetch_symcons(&self, ptr: &Ptr) -> Option<(Ptr, Ptr)> { match (ptr.tag, ptr.raw) { - (ExprTag::Sym, RawPtr::Null) => None, - (ExprTag::Key, RawPtr::Null) => None, - (ExprTag::Sym, RawPtr::Index(x)) => { - let (car, cdr) = self.sym_store.get_index(x)?; - Some((*car, *cdr)) - } - (ExprTag::Key, RawPtr::Index(x)) => { + (ExprTag::Sym | ExprTag::Key, RawPtr::Index(x)) => { let (car, cdr) = self.sym_store.get_index(x)?; Some((*car, *cdr)) } @@ -694,24 +650,26 @@ impl Store { } pub fn fetch_symbol(&self, ptr: &Ptr) -> Option { - if ptr.tag == ExprTag::Nil { - return Some(Symbol::nil()); - } - let mut ptr = *ptr; - let mut path = Vec::new(); - while let Some((car, cdr)) = self.fetch_symcons(&ptr) { - let string = self.fetch_string(&car)?; - path.push(string); - ptr = cdr + match ptr.tag { + ExprTag::Nil => Some(lurk_sym("nil")), + ExprTag::Sym | ExprTag::Key => { + let is_key = ptr.tag == ExprTag::Key; + let mut ptr = *ptr; + let mut path = Vec::new(); + while let Some((car, cdr)) = self.fetch_symcons(&ptr) { + let string = self.fetch_string(&car)?; + path.push(string); + ptr = cdr + } + path.reverse(); + Some(Symbol::new_from_vec(path, is_key)) + } + _ => None, } - Some(Symbol { - path: path.into_iter().rev().collect(), - }) } pub fn fetch_strcons(&self, ptr: &Ptr) -> Option<(Ptr, Ptr)> { match (ptr.tag, ptr.raw) { - (ExprTag::Str, RawPtr::Null) => None, (ExprTag::Str, RawPtr::Index(x)) => { let (car, cdr) = self.str_store.get_index(x)?; Some((*car, *cdr)) @@ -746,7 +704,6 @@ impl Store { debug_assert!(matches!(ptr.tag, ExprTag::Fun)); if ptr.raw.is_opaque() { None - // Some(&self.opaque_fun) } else { self.fun_store.get_index(ptr.raw.idx()?) } @@ -801,6 +758,7 @@ impl Store { ExprTag::Sym => self .fetch_symcons(ptr) .map(|(car, cdr)| Expression::Sym(car, cdr)), + ExprTag::Key if ptr.raw.is_null() => Some(Expression::RootKey), ExprTag::Key => self .fetch_symcons(ptr) .map(|(car, cdr)| Expression::Key(car, cdr)), @@ -840,7 +798,10 @@ impl Store { } pub fn fetch_cont(&self, ptr: &ContPtr) -> Option> { - use ContTag::*; + use ContTag::{ + Binop, Binop2, Call, Call0, Call2, Dummy, Emit, Error, If, Let, LetRec, Lookup, + Outermost, Tail, Terminal, Unop, + }; match ptr.tag { Outermost => Some(Continuation::Outermost), Call0 => self @@ -1013,6 +974,10 @@ impl Store { ZExpr::RootSym.z_ptr(&self.poseidon_cache), Some(ZExpr::RootSym), ), + Some(Expression::RootKey) => ( + ZExpr::RootKey.z_ptr(&self.poseidon_cache), + Some(ZExpr::RootKey), + ), Some(Expression::Sym(car, cdr)) => { let (z_car, _) = self.get_z_expr(&car, z_store)?; let (z_cdr, _) = self.get_z_expr(&cdr, z_store)?; @@ -1320,7 +1285,7 @@ impl Store { .0 } - pub fn hash_symbol(&mut self, s: Symbol) -> ZExprPtr { + pub fn hash_symbol(&mut self, s: &Symbol) -> ZExprPtr { let ptr = self.intern_symbol(s); self.get_z_expr(&ptr, &mut None) .expect("known symbol can't be opaque") @@ -1329,17 +1294,16 @@ impl Store { pub fn car_cdr(&self, ptr: &Ptr) -> Result<(Ptr, Ptr), Error> { match ptr.tag { - ExprTag::Nil => Ok((self.get_nil(), self.get_nil())), + ExprTag::Nil => Ok((lurk_sym_ptr!(self, nil), lurk_sym_ptr!(self, nil))), ExprTag::Cons => match self.fetch(ptr) { Some(Expression::Cons(car, cdr)) => Ok((car, cdr)), e => Err(Error(format!( - "Can only extract car_cdr from known Cons, instead got {:?} {:?}", - ptr, e, + "Can only extract car_cdr from known Cons, instead got {ptr:?} {e:?}", ))), }, ExprTag::Str => match self.fetch(ptr) { Some(Expression::Str(car, cdr)) => Ok((car, cdr)), - Some(Expression::EmptyStr) => Ok((self.get_nil(), self.strnil())), + Some(Expression::EmptyStr) => Ok((lurk_sym_ptr!(self, nil), self.strnil())), _ => unreachable!(), }, _ => Err(Error("Can only extract car_cdr from Cons".into())), @@ -1390,6 +1354,7 @@ impl Store { /// Fill the cache for Scalars. Only Ptrs which have been interned since last hydration will be hashed, so it is /// safe to call this incrementally. However, for best proving performance, we should call exactly once so all /// hashing can be batched, e.g. on the GPU. + #[tracing::instrument(skip_all, name = "Store::hydrate_scalar_cache")] pub fn hydrate_scalar_cache(&mut self) { self.ensure_constants(); @@ -1409,12 +1374,10 @@ impl Store { } fn ensure_constants(&mut self) { - // This will clobber whatever was there before. - let _ = self.constants.set(NamedConstants::new(self)); - } - - pub fn get_constants(&self) -> &NamedConstants { - self.constants.get_or_init(|| NamedConstants::new(self)) + if self.constants.get().is_none() { + let new = NamedConstants::new(self); + self.constants.set(new).expect("constants are not set"); + } } /// The only places that `ZPtr`s for `Ptr`s should be created, to @@ -1453,10 +1416,12 @@ impl Store { if let Some(ptr) = self.fetch_z_expr_ptr(z_ptr) { Some(ptr) } else { - use ZExpr::*; + use ZExpr::{ + Char, Comm, Cons, EmptyStr, Fun, Key, Nil, Num, RootSym, Str, Sym, Thunk, UInt, + }; match (z_ptr.tag(), z_store.get_expr(z_ptr)) { (ExprTag::Nil, Some(Nil)) => { - let ptr = self.lurk_sym("nil"); + let ptr = lurk_sym_ptr!(self, nil); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) } @@ -1474,7 +1439,7 @@ impl Store { Some(ptr) } (ExprTag::Str, Some(EmptyStr)) => { - let ptr = self.intern_strnil(); + let ptr = self.strnil(); self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) } @@ -1490,6 +1455,11 @@ impl Store { self.create_z_expr_ptr(ptr, *z_ptr.value()); Some(ptr) } + (ExprTag::Key, Some(RootSym)) => { + let ptr = self.intern_symnil(true); + self.create_z_expr_ptr(ptr, *z_ptr.value()); + Some(ptr) + } (ExprTag::Sym, Some(Sym(symcar, symcdr))) => { let symcar = self.intern_z_expr_ptr(&symcar, z_store)?; let symcdr = self.intern_z_expr_ptr(&symcdr, z_store)?; @@ -1542,7 +1512,7 @@ impl Store { Some(ptr) } _ => { - //println!("Failed to get ptr for zptr: {:?}", z_ptr); + // println!("Failed to get ptr for zptr: {:?}", z_ptr); None } } @@ -1560,7 +1530,10 @@ impl Store { z_ptr: &ZContPtr, z_store: &ZStore, ) -> Option> { - use ZCont::*; + use ZCont::{ + Binop, Binop2, Call, Call0, Call2, Dummy, Emit, Error, If, Let, LetRec, Lookup, + Outermost, Tail, Terminal, Unop, + }; let tag: ContTag = z_ptr.tag(); if let Some(cont) = z_store.get_cont(z_ptr) { @@ -1683,42 +1656,6 @@ impl Store { } impl Expression { - pub fn is_keyword_sym(&self) -> bool { - matches!(self, Expression::Key(_, _)) - } - - pub const fn as_str(&self) -> Option<&str> { - match self { - Expression::Str(_, _) => todo!(), - Expression::EmptyStr => Some(""), - _ => None, - } - } - - //pub fn as_sym_str(&self) -> Option { - // todo!() - // //match self { - // // Expression::Sym(s) => Some(s.full_name()), - // // _ => None, - // //} - //} - - //pub const fn as_sym(&self) -> Option<&Symbol> { - // todo!() - // //match self { - // // Expression::Sym(s) => Some(s), - // // _ => None, - // //} - //} - - //pub fn as_simple_keyword_string(&self) -> Option { - // todo!() - // //match self { - // // Expression::Sym(s) => s.simple_keyword_name(), - // // _ => None, - // //} - //} - pub const fn is_null(&self) -> bool { matches!(self, Self::Nil) } @@ -1807,18 +1744,18 @@ pub struct NamedConstants { } impl NamedConstants { - pub fn new(store: &Store) -> Self { - let hash_sym = |name: &str| { - let ptr = store.get_lurk_sym(name).unwrap(); + pub fn new(store: &mut Store) -> Self { + let nil_ptr = store.intern_symbol(&lurk_sym("nil")); + let nil_z_ptr = Some(ZExpr::Nil.z_ptr(&store.poseidon_cache)); + + let mut hash_sym = |name: &str| { + let ptr = store.intern_symbol(&lurk_sym(name)); let maybe_z_ptr = store.hash_expr(&ptr); ConstantPtrs(maybe_z_ptr, ptr) }; + let nil = ConstantPtrs(nil_z_ptr, nil_ptr); let t = hash_sym("t"); - let nil = ConstantPtrs( - Some(ZExpr::Nil.z_ptr(&store.poseidon_cache)), - store.get_nil(), - ); let lambda = hash_sym("lambda"); let quote = hash_sym("quote"); let let_ = hash_sym("let"); @@ -1911,11 +1848,11 @@ impl ZStore { pub fn to_store_with_z_ptr(&self, z_ptr: &ZExprPtr) -> Result<(Store, Ptr), Error> { let mut store = Store::new(); - for ptr in self.expr_map.keys() { - store.intern_z_expr_ptr(ptr, self); + for z_ptr in self.expr_map.keys() { + store.intern_z_expr_ptr(z_ptr, self); } - for ptr in self.cont_map.keys() { - store.intern_z_cont_ptr(ptr, self); + for z_ptr in self.cont_map.keys() { + store.intern_z_cont_ptr(z_ptr, self); } match store.intern_z_expr_ptr(z_ptr, self) { Some(ptr_ret) => Ok((store, ptr_ret)), @@ -1928,6 +1865,7 @@ impl ZStore { pub mod test { use super::*; + use crate::state::{initial_lurk_state, State}; use crate::writer::Write; use crate::{ eval::{ @@ -1944,14 +1882,6 @@ pub mod test { use pasta_curves::pallas::Scalar as S1; use rand::rngs::OsRng; - #[test] - fn test_print_num() { - let mut store = Store::::default(); - let num = store.num(5); - let res = num.fmt_to_string(&store); - assert_eq!(&res, &"5"); - } - #[test] fn tag_vals() { assert_eq!(0, ExprTag::Nil as u64); @@ -1969,7 +1899,10 @@ pub mod test { #[test] fn cont_tag_vals() { - use super::ContTag::*; + use super::ContTag::{ + Binop, Binop2, Call, Call0, Call2, Dummy, Emit, Error, If, Let, LetRec, Lookup, + Outermost, Tail, Terminal, Unop, + }; assert_eq!(0b0001_0000_0000_0000, Outermost as u16); assert_eq!(0b0001_0000_0000_0001, Call0 as u16); @@ -2035,9 +1968,9 @@ pub mod test { let opaque_fun = store.intern_opaque_fun(*fun_hash.value()); let opaque_fun2 = store.intern_opaque_fun(*fun_hash2.value()); - let eq = store.lurk_sym("eq"); - let t = store.lurk_sym("t"); - let nil = store.nil(); + let eq = lurk_sym_ptr!(store, equal); + let t = lurk_sym_ptr!(store, t); + let nil = lurk_sym_ptr!(store, nil); let limit = 10; let lang: Lang> = Lang::new(); { @@ -2050,7 +1983,6 @@ pub mod test { } { let comparison_expr = store.list(&[eq, fun2, opaque_fun]); - println!("comparison_expr: {}", comparison_expr.fmt_to_string(&store)); let (result, _, _) = Evaluator::new(comparison_expr, empty_env, &mut store, limit, &lang) .eval() @@ -2070,7 +2002,7 @@ pub mod test { // without this affecting equality semantics. let n = store.num(123); - let cons = store.lurk_sym("cons"); + let cons = lurk_sym_ptr!(store, cons); let cons_expr1 = store.list(&[cons, fun, n]); let cons_expr2 = store.list(&[cons, opaque_fun, n]); @@ -2095,17 +2027,19 @@ pub mod test { let opaque_sym = store.intern_opaque_sym(*sym_hash.value()); let opaque_sym2 = store.intern_opaque_sym(*sym_hash2.value()); - let quote = store.lurk_sym("quote"); + let quote = lurk_sym_ptr!(store, quote); let qsym = store.list(&[quote, sym]); let qsym2 = store.list(&[quote, sym2]); let qsym_opaque = store.list(&[quote, opaque_sym]); let qsym_opaque2 = store.list(&[quote, opaque_sym2]); - let eq = store.lurk_sym("eq"); - let t = store.lurk_sym("t"); - let nil = store.nil(); + let eq = lurk_sym_ptr!(store, equal); + let t = lurk_sym_ptr!(store, t); + let nil = lurk_sym_ptr!(store, nil); let limit = 10; + let state = initial_lurk_state(); + // When an opaque sym is inserted into a store which contains the same sym, the store knows its identity. // Should we just immediately coalesce and never create an opaque version in that case? Probably not because // that may interact badly with explicit hiding to be implemented. @@ -2115,7 +2049,7 @@ pub mod test { // assert_eq!(sym.fmt_to_string(&store), opaque_sym.fmt_to_string(&store)); // For now, all opaque data remains opaque, even if the Store has enough information to clarify it. - assert!(sym.fmt_to_string(&store) != opaque_sym.fmt_to_string(&store)); + assert!(sym.fmt_to_string(&store, state) != opaque_sym.fmt_to_string(&store, state)); let mut other_store = Store::::default(); let other_opaque_sym = other_store.intern_opaque_sym(*sym_hash.value()); @@ -2125,16 +2059,17 @@ pub mod test { // TODO: we could check for this and fix when inserting non-opaque syms. If we decide to clarify opaque data // when possible, we should do this too. assert!( - other_sym.fmt_to_string(&other_store) != other_opaque_sym.fmt_to_string(&other_store) + other_sym.fmt_to_string(&other_store, state) + != other_opaque_sym.fmt_to_string(&other_store, state) ); let num = num::Num::from_scalar(*sym_hash.value()); assert_eq!( format!( "", - Expression::Num(num).fmt_to_string(&store) + Expression::Num(num).fmt_to_string(&store, state) ), - other_opaque_sym.fmt_to_string(&other_store) + other_opaque_sym.fmt_to_string(&other_store, state) ); // We need to insert a few opaque syms in other_store, in order to acquire a raw_ptr that doesn't exist in @@ -2171,7 +2106,7 @@ pub mod test { // without this affecting equality semantics. let n = store.num(123); - let cons = store.lurk_sym("cons"); + let cons = lurk_sym_ptr!(store, cons); let cons_expr1 = store.list(&[cons, qsym, n]); let cons_expr2 = store.list(&[cons, qsym_opaque, n]); @@ -2199,11 +2134,11 @@ pub mod test { let opaque_cons = store.intern_opaque_cons(*cons_hash.value()); let opaque_cons2 = store.intern_opaque_cons(*cons_hash2.value()); - let eq = store.lurk_sym("eq"); - let t = store.lurk_sym("t"); - let nil = store.nil(); + let eq = lurk_sym_ptr!(store, equal); + let t = lurk_sym_ptr!(store, t); + let nil = lurk_sym_ptr!(store, nil); let limit = 10; - let quote = store.lurk_sym("quote"); + let quote = lurk_sym_ptr!(store, quote); let qcons = store.list(&[quote, cons]); let qcons2 = store.list(&[quote, cons2]); let qcons_opaque = store.list(&[quote, opaque_cons]); @@ -2212,9 +2147,11 @@ pub mod test { let num = Expression::Num(num::Num::Scalar(*cons_hash.value())); let lang = Lang::>::new(); + let state = initial_lurk_state(); + assert_eq!( - format!("", num.fmt_to_string(&store)), - opaque_cons.fmt_to_string(&store) + format!("", num.fmt_to_string(&store, state)), + opaque_cons.fmt_to_string(&store, state) ); { @@ -2247,7 +2184,7 @@ pub mod test { let n = store.num(123); let n2 = store.num(321); - let cons_sym = store.lurk_sym("cons"); + let cons_sym = lurk_sym_ptr!(store, cons); let cons_expr1 = store.list(&[cons_sym, qcons, n]); let cons_expr2 = store.list(&[cons_sym, qcons_opaque, n]); let cons_expr3 = store.list(&[cons_sym, qcons_opaque, n2]); @@ -2318,39 +2255,61 @@ pub mod test { store.cdr(&opaque_cons).unwrap(); } + #[test] + fn symbol_hashing() { + let s = &mut Store::::default(); + let foo_ptr = s.intern_string("foo"); + let bar_ptr = s.intern_string("bar"); + let foo_bar_ptr = s.intern_symbol(&Symbol::sym_from_vec(vec!["foo".into(), "bar".into()])); + + let foo_z_ptr = s.hash_expr(&foo_ptr).unwrap(); + let bar_z_ptr = s.hash_expr(&bar_ptr).unwrap(); + let foo_bar_hash = s.hash_expr(&foo_bar_ptr).unwrap().1; + + let foo_bar_hash_manual = s.poseidon_cache.hash4(&[ + bar_z_ptr.0.to_field(), + bar_z_ptr.1, + ExprTag::Sym.to_field(), + s.poseidon_cache.hash4(&[ + foo_z_ptr.0.to_field(), + foo_z_ptr.1, + ExprTag::Sym.to_field(), + Fr::ZERO, + ]), + ]); + assert_eq!(foo_bar_hash, foo_bar_hash_manual); + } + #[test] fn sym_and_key_hashes() { let s = &mut Store::::default(); - let root = s.intern_symbol(Symbol::root()); - let str1 = s.str("keyword"); - let sym1 = s.intern_symcons(str1, root); - let str2 = s.str("orange"); - let sym2 = s.intern_symcons(str2, sym1); - let key = s.key("orange"); + let sym_ptr = s.intern_symbol(&Symbol::sym(&["a", "b", "c"])); + let key_ptr = s.intern_symbol(&Symbol::key(&["a", "b", "c"])); - let sym_ptr = s.hash_expr(&sym2).unwrap(); - let key_ptr = s.hash_expr(&key).unwrap(); - let sym_hash = sym_ptr.1; - let key_hash = key_ptr.1; + let sym_z_ptr = s.hash_expr(&sym_ptr).unwrap(); + let key_z_ptr = s.hash_expr(&key_ptr).unwrap(); + let sym_hash = sym_z_ptr.1; + let key_hash = key_z_ptr.1; + assert_ne!(sym_ptr, key_ptr); + assert_ne!(sym_z_ptr, key_z_ptr); assert_eq!(sym_hash, key_hash); - assert!(sym_ptr != key_ptr); } #[test] fn sym_in_list() { - let s = &mut Store::::default(); + let store = &mut Store::::default(); let foo_list = list!(Fr, [symbol!(["foo"])]); let foo_sym = symbol!(Fr, ["foo"]); - let expr = s.intern_syntax(foo_list); - let sym = s.intern_syntax(foo_sym); - let sym1 = s.car(&expr).unwrap(); - let sss = s.fetch_sym(&sym); - let hash = s.hash_expr(&sym); - dbg!(&sym1, &sss, &hash); + let expr = store.intern_syntax(foo_list); + let sym = store.intern_syntax(foo_sym); + let sym1 = store.car(&expr).unwrap(); + let sss = store.fetch_sym(&sym); + let hash = store.hash_expr(&sym); + tracing::debug!("{:?} {:?} {:?}", &sym1, &sss, &hash); assert_eq!(sym, sym1); } @@ -2447,9 +2406,13 @@ pub mod test { let opaque_comm = s.intern_opaque_comm(Fr::from(123)); let num = num::Num::from_scalar(scalar); + let state = initial_lurk_state(); assert_eq!( - format!("", Expression::Num(num).fmt_to_string(s)), - opaque_comm.fmt_to_string(s), + format!( + "", + Expression::Num(num).fmt_to_string(s, state) + ), + opaque_comm.fmt_to_string(s, state), ); } @@ -2465,8 +2428,9 @@ pub mod test { #[test] fn commitment_z_store_roundtrip() { let store = &mut Store::::default(); - let two = store.read("(+ 1 1)").unwrap(); - let three = store.read("(+ 1 2)").unwrap(); + let state = State::init_lurk_state().rccell(); + let two = store.read_with_state(state.clone(), "(+ 1 1)").unwrap(); + let three = store.read_with_state(state, "(+ 1 2)").unwrap(); let comm2 = commit_and_open(store, two); let comm3 = commit_and_open(store, three); diff --git a/src/symbol.rs b/src/symbol.rs index c184753630..8b5cdd8aef 100644 --- a/src/symbol.rs +++ b/src/symbol.rs @@ -1,6 +1,8 @@ use std::fmt; -use crate::parser::LURK_WHITESPACE; +use anyhow::{bail, Result}; + +use crate::{parser::LURK_WHITESPACE, state::State}; #[cfg(not(target_arch = "wasm32"))] use lurk_macros::serde_test; #[cfg(not(target_arch = "wasm32"))] @@ -8,7 +10,6 @@ use proptest_derive::Arbitrary; /// Module for symbol type, Sym. use serde::{Deserialize, Serialize}; -use std::collections::HashMap; pub const KEYWORD_MARKER: char = ':'; pub const SYM_SEPARATOR: char = '.'; @@ -19,33 +20,94 @@ pub const ESCAPE_CHARS: &str = "|(){}[],.:'\\\""; #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[cfg_attr(not(target_arch = "wasm32"), serde_test)] /// Type for hierarchical symbol names. -/// -/// The symbol path is encoded with a vector of strings. Keywords are symbols -/// whose first path component is the string "keyword". pub struct Symbol { - pub path: Vec, + path: Vec, + keyword: bool, } impl Symbol { + #[inline] + pub fn path(&self) -> &[String] { + &self.path + } + + #[inline] + pub const fn is_keyword(&self) -> bool { + self.keyword + } + + #[inline] + pub const fn root(keyword: bool) -> Self { + Self { + path: vec![], + keyword, + } + } + + /// Creates a new `Symbol` with an empty path. + #[inline] + pub fn root_sym() -> Self { + Self::root(false) + } + /// Creates a new `Symbol` with an empty path. #[inline] - pub fn root() -> Self { - Self { path: vec![] } + pub fn root_key() -> Self { + Self::root(true) } - /// Returns true if the `Symbol` is the root symbol, i.e. if it has an empty path. #[inline] pub fn is_root(&self) -> bool { self.path.is_empty() } - /// Creates a new Symbol with the path extended by the given vector of path segments. - pub fn new>(path: &[A]) -> Self { + /// Returns true if the `Symbol` is the root symbol + #[inline] + pub fn is_root_sym(&self) -> bool { + self.is_root() && !self.keyword + } + + #[inline] + pub fn is_root_key(&self) -> bool { + self.is_root() && self.keyword + } + + pub fn new>(path: &[A], keyword: bool) -> Self { Self { path: path.iter().map(|x| String::from(x.as_ref())).collect(), + keyword, } } + pub fn new_from_vec(path: Vec, keyword: bool) -> Self { + Self { path, keyword } + } + + #[inline] + pub fn sym>(path: &[A]) -> Self { + Self::new(path, false) + } + + #[inline] + pub fn key>(path: &[A]) -> Self { + Self::new(path, true) + } + + #[inline] + pub fn sym_from_vec(path: Vec) -> Self { + Self::new_from_vec(path, false) + } + + #[inline] + pub fn key_from_vec(path: Vec) -> Self { + Self::new_from_vec(path, true) + } + + #[inline] + pub fn set_as_keyword(&mut self) { + self.keyword = true; + } + /// Creates a new Symbol with the path extended by the given vector of path segments. pub fn extend>(&self, child: &[A]) -> Self { let mut path = Vec::with_capacity(self.path.len() + child.len()); @@ -55,15 +117,10 @@ impl Symbol { for elt in child.iter() { path.push(String::from(elt.as_ref())); } - Self { path } - } - - pub fn nil() -> Symbol { - Self::new(&["lurk", "nil"]) - } - - pub fn keyword>(key: &[A]) -> Symbol { - Self::new(&["keyword"]).extend(key) + Self { + path, + keyword: self.keyword, + } } pub fn has_parent(&self, parent: &Symbol) -> bool { @@ -85,6 +142,7 @@ impl Symbol { if self.path.len() < parent.path.len() { None } else { + let keyword = parent.keyword; let mut parent = parent.path.iter(); let mut child = self.path.iter().peekable(); @@ -101,43 +159,26 @@ impl Symbol { } else { let path = child.cloned().collect(); // return the remaining child path - return Some(Symbol { path }); + return Some(Symbol { path, keyword }); } } // only reachable if self == parent - Some(Symbol::root()) - } - } - - pub fn is_keyword(&self) -> bool { - self == &Self::new(&["keyword"]) || self.has_parent(&Self::new(&["keyword"])) - } - - pub fn lurk_sym(name: &str) -> Symbol { - Self::new(&["lurk", name]) - } - - pub fn is_lurk_sym(&self) -> bool { - self.has_parent(&Self::new(&["lurk"])) - } - - pub fn lurk_syms() -> HashMap { - let mut syms = HashMap::new(); - for lurk_sym in LURK_SYMBOLS { - syms.insert(Self::lurk_sym(&format!("{}", lurk_sym)), *lurk_sym); + Some(Symbol { + path: vec![], + keyword, + }) } - syms } pub fn is_whitespace(c: char) -> bool { LURK_WHITESPACE.iter().any(|x| *x == c) } - pub fn escape_symbol_element(xs: &str) -> String { + pub fn fmt_path_component_to_string(xs: &str) -> String { let mut res = String::new(); for x in xs.chars() { if ESCAPE_CHARS.chars().any(|c| c == x) { - res.push_str(&format!("\\{}", x)); + res.push_str(&format!("\\{x}")); } else if Self::is_whitespace(x) { res.push_str(&format!("{}", x.escape_unicode())); } else { @@ -147,11 +188,11 @@ impl Symbol { res } - pub fn print_path(&self) -> String { + pub fn fmt_path_to_string(&self) -> String { let mut res = String::new(); let mut iter = self.path.iter().peekable(); while let Some(next) = iter.next() { - res.push_str(&Self::escape_symbol_element(next)); + res.push_str(&Self::fmt_path_component_to_string(next)); if iter.peek().is_some() || next.is_empty() { res.push('.'); } @@ -160,7 +201,7 @@ impl Symbol { } // TODO: needs some kind of whitespace escaping - pub fn print_raw(&self) -> String { + pub fn fmt_to_string_raw(&self) -> String { let mut res = String::from("~("); let mut iter = self.path.iter().peekable(); while let Some(next) = iter.next() { @@ -199,12 +240,13 @@ impl Symbol { } pub fn direct_parent(&self) -> Option { - if self.path.is_empty() { + if self.is_root() { None } else { Some(Self { // drop the last component of the path path: self.path[0..self.path.len() - 1].to_vec(), + keyword: self.keyword, }) } } @@ -212,259 +254,173 @@ impl Symbol { pub fn direct_child(&self, child: &str) -> Symbol { let mut path = self.path.clone(); path.push(child.into()); - Self { path } + Self { + path, + keyword: self.keyword, + } } -} -impl fmt::Display for Symbol { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(child) = self.as_child(&Symbol::new(&["keyword"])) { - if child.path.is_empty() { - write!(f, ".keyword")?; + pub fn name(&self) -> Result<&str> { + if self.is_root() { + bail!("Root symbols don't have names") + } else { + Ok(&self.path[self.path.len() - 1]) + } + } + + pub fn fmt_to_string(&self) -> String { + if self.is_keyword() { + if self.is_root() { + "~:()".into() } else { - write!(f, ":{}", child.print_path())?; + format!(":{}", &self.fmt_path_to_string()) } } else if self.is_root() { - write!(f, "~()")?; - } else if self.prints_as_absolute() { - write!(f, ".{}", self.print_path())?; + "~()".into() } else { - write!(f, "{}", self.print_path())?; + format!(".{}", &self.fmt_path_to_string()) + } + } + + pub fn from_str_impl(name: &str) -> Option { + use crate::parser::{ + syntax::{parse_space, parse_symbol}, + Span, + }; + use crate::syntax::Syntax; + use nom::{sequence::preceded, Parser}; + use pasta_curves::pallas::Scalar; + match preceded( + parse_space::, + parse_symbol(State::default().rccell(), true), + ) + .parse(Span::new(name)) + { + Ok((_, Syntax::Symbol(_, symbol))) => Some((*symbol).clone()), + _ => None, } - Ok(()) } } -#[derive(Clone, Debug, PartialEq, Eq, Copy)] -#[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] -pub enum LurkSym { - Atom, - Begin, - Car, - Cdr, - Char, - Comm, - Commit, - Cons, - CurrentEnv, - Emit, - Eval, - Eq, - Hide, - If, - Lambda, - Let, - Letrec, - Nil, - Num, - U64, - Open, - Quote, - Secret, - Strcons, - T, - OpAdd, - OpSub, - OpMul, - OpDiv, - OpMod, - OpEql, - OpLth, - OpGth, - OpLte, - OpGte, - Dummy, +impl From<&'static str> for Symbol { + fn from(value: &'static str) -> Self { + Symbol::from_str_impl(value).unwrap() + } } -impl fmt::Display for LurkSym { +impl fmt::Display for Symbol { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Atom => write!(f, "atom"), - Self::Begin => write!(f, "begin"), - Self::Car => write!(f, "car"), - Self::Cdr => write!(f, "cdr"), - Self::Char => write!(f, "char"), - Self::Comm => write!(f, "comm"), - Self::Commit => write!(f, "commit"), - Self::Cons => write!(f, "cons"), - Self::CurrentEnv => write!(f, "current-env"), - Self::Emit => write!(f, "emit"), - Self::Eval => write!(f, "eval"), - Self::Eq => write!(f, "eq"), - Self::Hide => write!(f, "hide"), - Self::If => write!(f, "if"), - Self::Lambda => write!(f, "lambda"), - Self::Let => write!(f, "let"), - Self::Letrec => write!(f, "letrec"), - Self::Nil => write!(f, "nil"), - Self::Num => write!(f, "num"), - Self::U64 => write!(f, "u64"), - Self::Open => write!(f, "open"), - Self::Quote => write!(f, "quote"), - Self::Secret => write!(f, "secret"), - Self::Strcons => write!(f, "strcons"), - Self::T => write!(f, "t"), - Self::OpAdd => write!(f, "+"), - Self::OpSub => write!(f, "-"), - Self::OpMul => write!(f, "*"), - Self::OpDiv => write!(f, "/"), - Self::OpMod => write!(f, "%"), - Self::OpEql => write!(f, "="), - Self::OpLth => write!(f, "<"), - Self::OpGth => write!(f, ">"), - Self::OpLte => write!(f, "<="), - Self::OpGte => write!(f, ">="), - Self::Dummy => write!(f, "_"), - } + write!(f, "{}", self.fmt_to_string()) } } -pub const LURK_SYMBOLS: &[LurkSym] = &[ - LurkSym::Atom, - LurkSym::Begin, - LurkSym::Car, - LurkSym::Cdr, - LurkSym::Char, - LurkSym::Comm, - LurkSym::Commit, - LurkSym::Cons, - LurkSym::CurrentEnv, - LurkSym::Emit, - LurkSym::Eval, - LurkSym::Eq, - LurkSym::Hide, - LurkSym::If, - LurkSym::Lambda, - LurkSym::Let, - LurkSym::Letrec, - LurkSym::Nil, - LurkSym::Num, - LurkSym::U64, - LurkSym::Open, - LurkSym::Quote, - LurkSym::Secret, - LurkSym::Strcons, - LurkSym::T, - LurkSym::OpAdd, - LurkSym::OpSub, - LurkSym::OpMul, - LurkSym::OpDiv, - LurkSym::OpMod, - LurkSym::OpEql, - LurkSym::OpLth, - LurkSym::OpGth, - LurkSym::OpLte, - LurkSym::OpGte, - LurkSym::Dummy, -]; +#[macro_export] +macro_rules! sym { + [$( $x:expr ),*] => { + { + let temp_vec = vec![ $( $x.to_string() ),* ]; + $crate::symbol::Symbol::sym(&temp_vec) + } + }; +} #[cfg(test)] pub mod test { use super::*; - #[test] - fn test_new() { - let a = Symbol::new(&["foo", "bar"]); - let b = Symbol { - path: vec!["foo".to_string(), "bar".to_string()], - }; - - assert_eq!(a, b) - } - #[test] fn test_parent_child() { - let a = Symbol::new(&["foo", "bar"]); - let b = Symbol::new(&["baz", "bam"]); - let c = Symbol::new(&["foo", "bar", "baz", "bam"]); + let a = Symbol::sym(&["foo", "bar"]); + let b = Symbol::sym(&["baz", "bam"]); + let c = Symbol::sym(&["foo", "bar", "baz", "bam"]); assert_eq!(a.extend(&b.path), c); assert!(c.has_parent(&a)); assert_eq!(c.as_child(&a), Some(b)); assert_eq!( - Symbol::new(&["a", "b"]).as_child(&Symbol::new(&["a"])), - Some(Symbol::new(&["b"])) + Symbol::sym(&["a", "b"]).as_child(&Symbol::sym(&["a"])), + Some(Symbol::sym(&["b"])) ); - assert!(Symbol::new(&["a", "b"]).has_parent(&Symbol::new(&["a"])),); + assert!(Symbol::sym(&["a", "b"]).has_parent(&Symbol::sym(&["a"])),); assert_eq!( - Symbol::new(&["a", "b", "c"]).as_child(&Symbol::new(&["a"])), - Some(Symbol::new(&["b", "c"])) + Symbol::sym(&["a", "b", "c"]).as_child(&Symbol::sym(&["a"])), + Some(Symbol::sym(&["b", "c"])) ); - assert!(Symbol::new(&["a", "b", "c"]).has_parent(&Symbol::new(&["a"])),); + assert!(Symbol::sym(&["a", "b", "c"]).has_parent(&Symbol::sym(&["a"])),); assert_eq!( - Symbol::new(&["a"]).as_child(&Symbol::root()), - Some(Symbol::new(&["a"])) + Symbol::sym(&["a"]).as_child(&Symbol::root_sym()), + Some(Symbol::sym(&["a"])) ); - assert!(Symbol::new(&["a"]).has_parent(&Symbol::new(&["a"])),); + assert!(Symbol::sym(&["a"]).has_parent(&Symbol::sym(&["a"])),); assert_eq!( - Symbol::new(&["a"]).as_child(&Symbol::new(&["a"])), - Some(Symbol::root()) + Symbol::sym(&["a"]).as_child(&Symbol::sym(&["a"])), + Some(Symbol::root_sym()) ); - assert!(Symbol::root().has_parent(&Symbol::root(),)); + assert!(Symbol::root_sym().has_parent(&Symbol::root_sym(),)); assert_eq!( - Symbol::root().as_child(&Symbol::root()), - Some(Symbol::root()) + Symbol::root_sym().as_child(&Symbol::root_sym()), + Some(Symbol::root_sym()) ); - assert_eq!(Symbol::new(&["a"]).as_child(&Symbol::new(&["b"])), None,); - assert!(!Symbol::new(&["a"]).has_parent(&Symbol::new(&["b"]))); + assert_eq!(Symbol::sym(&["a"]).as_child(&Symbol::sym(&["b"])), None,); + assert!(!Symbol::sym(&["a"]).has_parent(&Symbol::sym(&["b"]))); assert_eq!( - Symbol::new(&["a"]).as_child(&Symbol::new(&["a,", "b"])), + Symbol::sym(&["a"]).as_child(&Symbol::sym(&["a,", "b"])), None, ); - assert!(!Symbol::new(&["a"]).has_parent(&Symbol::new(&["a", "b"]))); + assert!(!Symbol::sym(&["a"]).has_parent(&Symbol::sym(&["a", "b"]))); } #[test] fn test_prints_as_absolute() { - assert!(!Symbol::root().prints_as_absolute()); - assert!(Symbol::new(&[""]).prints_as_absolute()); - assert!(Symbol::new(&["~"]).prints_as_absolute()); - assert!(Symbol::new(&["#"]).prints_as_absolute()); - assert!(Symbol::new(&["1"]).prints_as_absolute()); - assert!(Symbol::new(&["2"]).prints_as_absolute()); - assert!(Symbol::new(&["3"]).prints_as_absolute()); - assert!(Symbol::new(&["4"]).prints_as_absolute()); - assert!(Symbol::new(&["5"]).prints_as_absolute()); - assert!(Symbol::new(&["6"]).prints_as_absolute()); - assert!(Symbol::new(&["7"]).prints_as_absolute()); - assert!(Symbol::new(&["8"]).prints_as_absolute()); - assert!(Symbol::new(&["9"]).prints_as_absolute()); - assert!(Symbol::new(&["0"]).prints_as_absolute()); - assert!(Symbol::new(&["."]).prints_as_absolute()); - assert!(Symbol::new(&[":"]).prints_as_absolute()); - assert!(Symbol::new(&["["]).prints_as_absolute()); - assert!(Symbol::new(&["]"]).prints_as_absolute()); - assert!(Symbol::new(&["("]).prints_as_absolute()); - assert!(Symbol::new(&[")"]).prints_as_absolute()); - assert!(Symbol::new(&["{"]).prints_as_absolute()); - assert!(Symbol::new(&["}"]).prints_as_absolute()); - assert!(Symbol::new(&["\""]).prints_as_absolute()); - assert!(Symbol::new(&["\\"]).prints_as_absolute()); - assert!(Symbol::new(&["-1"]).prints_as_absolute()); - assert!(Symbol::new(&["-2"]).prints_as_absolute()); - assert!(Symbol::new(&["-3"]).prints_as_absolute()); - assert!(Symbol::new(&["-4"]).prints_as_absolute()); - assert!(Symbol::new(&["-5"]).prints_as_absolute()); - assert!(Symbol::new(&["-6"]).prints_as_absolute()); - assert!(Symbol::new(&["-7"]).prints_as_absolute()); - assert!(Symbol::new(&["-8"]).prints_as_absolute()); - assert!(Symbol::new(&["-9"]).prints_as_absolute()); - assert!(Symbol::new(&["-0"]).prints_as_absolute()); - assert!(Symbol::new(&[" "]).prints_as_absolute()); - assert!(Symbol::new(&["\x00"]).prints_as_absolute()); + assert!(!Symbol::root_sym().prints_as_absolute()); + assert!(Symbol::sym(&[""]).prints_as_absolute()); + assert!(Symbol::sym(&["~"]).prints_as_absolute()); + assert!(Symbol::sym(&["#"]).prints_as_absolute()); + assert!(Symbol::sym(&["1"]).prints_as_absolute()); + assert!(Symbol::sym(&["2"]).prints_as_absolute()); + assert!(Symbol::sym(&["3"]).prints_as_absolute()); + assert!(Symbol::sym(&["4"]).prints_as_absolute()); + assert!(Symbol::sym(&["5"]).prints_as_absolute()); + assert!(Symbol::sym(&["6"]).prints_as_absolute()); + assert!(Symbol::sym(&["7"]).prints_as_absolute()); + assert!(Symbol::sym(&["8"]).prints_as_absolute()); + assert!(Symbol::sym(&["9"]).prints_as_absolute()); + assert!(Symbol::sym(&["0"]).prints_as_absolute()); + assert!(Symbol::sym(&["."]).prints_as_absolute()); + assert!(Symbol::sym(&[":"]).prints_as_absolute()); + assert!(Symbol::sym(&["["]).prints_as_absolute()); + assert!(Symbol::sym(&["]"]).prints_as_absolute()); + assert!(Symbol::sym(&["("]).prints_as_absolute()); + assert!(Symbol::sym(&[")"]).prints_as_absolute()); + assert!(Symbol::sym(&["{"]).prints_as_absolute()); + assert!(Symbol::sym(&["}"]).prints_as_absolute()); + assert!(Symbol::sym(&["\""]).prints_as_absolute()); + assert!(Symbol::sym(&["\\"]).prints_as_absolute()); + assert!(Symbol::sym(&["-1"]).prints_as_absolute()); + assert!(Symbol::sym(&["-2"]).prints_as_absolute()); + assert!(Symbol::sym(&["-3"]).prints_as_absolute()); + assert!(Symbol::sym(&["-4"]).prints_as_absolute()); + assert!(Symbol::sym(&["-5"]).prints_as_absolute()); + assert!(Symbol::sym(&["-6"]).prints_as_absolute()); + assert!(Symbol::sym(&["-7"]).prints_as_absolute()); + assert!(Symbol::sym(&["-8"]).prints_as_absolute()); + assert!(Symbol::sym(&["-9"]).prints_as_absolute()); + assert!(Symbol::sym(&["-0"]).prints_as_absolute()); + assert!(Symbol::sym(&[" "]).prints_as_absolute()); + assert!(Symbol::sym(&["\x00"]).prints_as_absolute()); } #[test] fn test_sym() { - assert_eq!("a.b.c", format!("{}", Symbol::new(&["a", "b", "c"]))); - let root = Symbol::root(); + assert_eq!(".a.b.c", format!("{}", Symbol::sym(&["a", "b", "c"]))); + let root = Symbol::root_sym(); let a = root.direct_child("a"); let a_b = a.direct_child("b"); let a_b_path = vec!["a", "b"]; - assert_eq!("a", format!("{}", a)); - assert_eq!("a.b", format!("{}", a_b)); + assert_eq!(".a", format!("{a}")); + assert_eq!(".a.b", format!("{a_b}")); assert_eq!(&a_b_path, &a_b.path); assert_eq!(Some(a.clone()), a_b.direct_parent()); assert_eq!(Some(root.clone()), a.direct_parent()); @@ -473,14 +429,16 @@ pub mod test { #[test] fn test_keywords() { - let root = Symbol::root(); - let key_root = Symbol::new(&["keyword"]); + let root = Symbol::root_sym(); + let key_root = Symbol::root_key(); let apple = root.direct_child("apple"); let orange = key_root.direct_child("orange"); - assert_eq!("apple", format!("{}", apple)); - assert_eq!(":orange", format!("{}", orange)); + assert!(!root.is_keyword()); + assert!(key_root.is_keyword()); + assert_eq!(".apple", format!("{apple}")); + assert_eq!(":orange", format!("{orange}")); assert!(!apple.is_keyword()); assert!(orange.is_keyword()); assert_eq!(key_root, orange.direct_parent().unwrap()); diff --git a/src/syntax.rs b/src/syntax.rs index bee5c24972..6553df92cd 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -2,13 +2,15 @@ use std::fmt; use crate::expr::Expression; use crate::field::LurkField; +use crate::lurk_sym_ptr; use crate::num::Num; +use crate::package::SymbolRef; use crate::parser::position::Pos; use crate::ptr::Ptr; +use crate::state::lurk_sym; use crate::store::Store; -use crate::symbol::{LurkSym, Symbol}; +use crate::tag::ExprTag; use crate::uint::UInt; -use std::collections::HashMap; #[cfg(not(target_arch = "wasm32"))] use proptest::prelude::*; @@ -20,11 +22,7 @@ pub enum Syntax { // A u64 integer: 1u64, 0xffu64 UInt(Pos, UInt), // A hierarchical symbol foo, foo.bar.baz or keyword :foo - Symbol(Pos, Symbol), - // A hierarchical symbol foo, foo.bar.baz or keyword :foo - Keyword(Pos, Symbol), - // Temporary shim until packages are correctly implemented - LurkSym(Pos, LurkSym), + Symbol(Pos, SymbolRef), // A string literal: "foobar", "foo\nbar" String(Pos, String), // A character literal: #\A #\λ #\u03BB @@ -37,31 +35,17 @@ pub enum Syntax { Improper(Pos, Vec>, Box>), } -impl Syntax { - pub fn nil(pos: Pos) -> Syntax { - Syntax::LurkSym(pos, LurkSym::Nil) - } -} - #[cfg(not(target_arch = "wasm32"))] impl Arbitrary for Syntax { type Parameters = (); type Strategy = BoxedStrategy; fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + use crate::Symbol; let leaf = prop_oneof![ any::>().prop_map(|x| Syntax::Num(Pos::No, x)), any::().prop_map(|x| Syntax::UInt(Pos::No, x)), - any::() - .prop_map(|x| Syntax::Keyword(Pos::No, Symbol::new(&["keyword"]).extend(&x.path))), - any::().prop_map(|x| { - if let Some(val) = Symbol::lurk_syms().get(&Symbol::lurk_sym(&format!("{}", x))) { - Syntax::LurkSym(Pos::No, *val) - } else { - Syntax::Symbol(Pos::No, x) - } - }), - any::().prop_map(|x| Syntax::LurkSym(Pos::No, x)), + any::().prop_map(|x| Syntax::Symbol(Pos::No, x.into())), any::().prop_map(|x| Syntax::String(Pos::No, x)), any::().prop_map(|x| Syntax::Char(Pos::No, x)) ]; @@ -84,26 +68,25 @@ impl Arbitrary for Syntax { impl fmt::Display for Syntax { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Num(_, x) => write!(f, "{}", x), - Self::UInt(_, x) => write!(f, "{}u64", x), - Self::Symbol(_, sym) | Self::Keyword(_, sym) => write!(f, "{}", sym), - Self::LurkSym(_, sym) => write!(f, "{}", sym), + Self::Num(_, x) => write!(f, "{x}"), + Self::UInt(_, x) => write!(f, "{x}u64"), + Self::Symbol(_, x) => write!(f, "{x}"), Self::String(_, x) => write!(f, "\"{}\"", x.escape_default()), Self::Char(_, x) => { if *x == '(' || *x == ')' { - write!(f, "'\\{}'", x) + write!(f, "'\\{x}'") } else { write!(f, "'{}'", x.escape_default()) } } - Self::Quote(_, x) => write!(f, "'{}", x), + Self::Quote(_, x) => write!(f, "'{x}"), Self::List(_, xs) => { let mut iter = xs.iter().peekable(); write!(f, "(")?; while let Some(x) = iter.next() { match iter.peek() { - Some(_) => write!(f, "{} ", x)?, - None => write!(f, "{}", x)?, + Some(_) => write!(f, "{x} ")?, + None => write!(f, "{x}")?, } } write!(f, ")") @@ -113,7 +96,7 @@ impl fmt::Display for Syntax { write!(f, "(")?; while let Some(x) = iter.next() { match iter.peek() { - Some(_) => write!(f, "{} ", x)?, + Some(_) => write!(f, "{x} ")?, None => write!(f, "{} . {}", x, *end)?, } } @@ -129,15 +112,14 @@ impl Store { Syntax::Num(_, x) => self.intern_num(x), Syntax::UInt(_, x) => self.intern_uint(x), Syntax::Char(_, x) => self.intern_char(x), - Syntax::Symbol(_, x) | Syntax::Keyword(_, x) => self.intern_symbol(x), - Syntax::LurkSym(_, x) => self.intern_symbol(Symbol::lurk_sym(&format!("{x}"))), - Syntax::String(_, x) => self.intern_string(x), + Syntax::Symbol(_, symbol) => self.intern_symbol(&symbol), + Syntax::String(_, x) => self.intern_string(&x), Syntax::Quote(pos, x) => { - let xs = vec![Syntax::Symbol(pos, Symbol::lurk_sym("quote")), *x]; + let xs = vec![Syntax::Symbol(pos, lurk_sym("quote").into()), *x]; self.intern_syntax(Syntax::List(pos, xs)) } Syntax::List(_, xs) => { - let mut cdr = self.nil(); + let mut cdr = lurk_sym_ptr!(self, nil); for x in xs.into_iter().rev() { let car = self.intern_syntax(x); cdr = self.intern_cons(car, cdr); @@ -160,6 +142,7 @@ impl Store { /// return None. If after traversing zero or more cons cells we hit a `nil`, we return a proper /// list (`Syntax::List`), otherwise an improper list (`Syntax::Improper`). If the proper list /// is a quotation `(quote x)`, then we return the syntactic quotation `Syntax::Quote` + #[allow(dead_code)] fn fetch_syntax_list(&self, mut ptr: Ptr) -> Option> { let mut list = vec![]; loop { @@ -169,13 +152,7 @@ impl Store { ptr = cdr; } Expression::Nil => { - return match (list.len(), list.get(0)) { - (0, _) => Some(Syntax::LurkSym(Pos::No, LurkSym::Nil)), - (2, Some(Syntax::LurkSym(_, LurkSym::Quote))) => { - Some(Syntax::Quote(Pos::No, Box::new(list[1].clone()))) - } - _ => Some(Syntax::List(Pos::No, list)), - } + return Some(Syntax::List(Pos::No, list)); } _ => { if list.is_empty() { @@ -189,51 +166,30 @@ impl Store { } } - fn fetch_syntax_aux( - &self, - lurk_syms: &HashMap, - ptr: Ptr, - ) -> Option> { - let expr = self.fetch(&ptr)?; - match expr { - Expression::Num(f) => Some(Syntax::Num(Pos::No, f)), - Expression::Char(_) => Some(Syntax::Char(Pos::No, self.fetch_char(&ptr)?)), - Expression::UInt(_) => Some(Syntax::UInt(Pos::No, self.fetch_uint(&ptr)?)), - Expression::Nil | Expression::Cons(..) => self.fetch_syntax_list(ptr), - Expression::EmptyStr => Some(Syntax::String(Pos::No, "".to_string())), - Expression::Str(..) => Some(Syntax::String(Pos::No, self.fetch_string(&ptr)?)), - Expression::RootSym => Some(Syntax::Symbol(Pos::No, Symbol::root())), - Expression::Sym(..) => { - let sym = self.fetch_symbol(&ptr)?; - if let Some(sym) = lurk_syms.get(&sym) { - Some(Syntax::LurkSym(Pos::No, *sym)) - } else { - Some(Syntax::Symbol(Pos::No, sym)) - } - } - Expression::Key(..) => { - let sym = self.fetch_symbol(&ptr)?; - Some(Syntax::Keyword(Pos::No, sym)) - } - Expression::Comm(..) | Expression::Thunk(..) | Expression::Fun(..) => None, + fn fetch_syntax(&self, ptr: Ptr) -> Option> { + match ptr.tag { + ExprTag::Num => Some(Syntax::Num(Pos::No, *self.fetch_num(&ptr)?)), + ExprTag::Char => Some(Syntax::Char(Pos::No, self.fetch_char(&ptr)?)), + ExprTag::U64 => Some(Syntax::UInt(Pos::No, self.fetch_uint(&ptr)?)), + ExprTag::Str => Some(Syntax::String(Pos::No, self.fetch_string(&ptr)?)), + ExprTag::Nil => Some(Syntax::Symbol(Pos::No, lurk_sym("nil").into())), + ExprTag::Cons => self.fetch_syntax_list(ptr), + ExprTag::Sym => Some(Syntax::Symbol(Pos::No, self.fetch_sym(&ptr)?.into())), + ExprTag::Key => Some(Syntax::Symbol(Pos::No, self.fetch_key(&ptr)?.into())), + _ => None, } } - - pub fn fetch_syntax(&self, ptr: Ptr) -> Option> { - let lurk_syms = Symbol::lurk_syms(); - self.fetch_syntax_aux(&lurk_syms, ptr) - } } #[cfg(test)] mod test { use super::*; + use crate::Symbol; use blstrs::Scalar as Fr; #[test] fn display_syntax() { let mut s = Store::::default(); - let lurk_syms = Symbol::lurk_syms(); macro_rules! improper { ( $( $x:expr ),+ ) => { @@ -252,7 +208,7 @@ mod test { ( $( $x:expr ),* ) => { { let mut vec = vec!($($x,)*); - let mut tmp = s.nil(); + let mut tmp = lurk_sym_ptr!(s, nil); while let Some(x) = vec.pop() { tmp = s.cons(x, tmp); } @@ -263,52 +219,52 @@ mod test { macro_rules! sym { ( $sym:ident ) => {{ - let sym = stringify!($sym); - if lurk_syms.contains_key(&Symbol::lurk_sym(sym)) { - s.lurk_sym(sym) - } else { - s.sym(sym) - } + s.sym(stringify!($sym)) }}; } // Quote tests - let expr = list!(sym!(quote), list!(sym!(f), sym!(x), sym!(y))); + let expr = list!(lurk_sym_ptr!(s, quote), list!(sym!(f), sym!(x), sym!(y))); + let output = s.fetch_syntax(expr).unwrap(); + assert_eq!("(.lurk.quote (.f .x .y))", &format!("{output}")); + + let expr = list!(lurk_sym_ptr!(s, quote), list!(sym!(f), sym!(x), sym!(y))); let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("'(f x y)".to_string(), format!("{}", output)); + assert_eq!("(.lurk.quote (.f .x .y))", &format!("{output}")); - let expr = list!(sym!(quote), sym!(f), sym!(x), sym!(y)); + let expr = list!(lurk_sym_ptr!(s, quote), sym!(f), sym!(x), sym!(y)); let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("(quote f x y)".to_string(), format!("{}", output)); + assert_eq!("(.lurk.quote .f .x .y)", &format!("{output}")); // List tests let expr = list!(); let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("nil".to_string(), format!("{}", output)); + assert_eq!(".lurk.nil", &format!("{output}")); let expr = improper!(sym!(x), sym!(y), sym!(z)); let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("(x y . z)".to_string(), format!("{}", output)); + assert_eq!("(.x .y . .z)", &format!("{output}")); - let expr = improper!(sym!(x), sym!(y), sym!(nil)); + let expr = improper!(sym!(x), sym!(y), lurk_sym_ptr!(s, nil)); let output = s.fetch_syntax(expr).unwrap(); - assert_eq!("(x y)".to_string(), format!("{}", output)); + assert_eq!("(.x .y)", &format!("{output}")); } #[test] fn syntax_rootkey_roundtrip() { let mut store1 = Store::::default(); - let ptr1 = store1.intern_syntax(Syntax::Keyword(Pos::No, Symbol::new(&["keyword"]))); + let ptr1 = store1.intern_syntax(Syntax::Symbol(Pos::No, Symbol::root_key().into())); let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); let y = store2.fetch_syntax(ptr2).unwrap(); let ptr2 = store1.intern_syntax(y); assert!(store1.ptr_eq(&ptr1, &ptr2).unwrap()); } + #[test] fn syntax_empty_keyword_roundtrip() { let mut store1 = Store::::default(); - let ptr1 = store1.intern_syntax(Syntax::Keyword(Pos::No, Symbol::new(&["keyword", ""]))); + let ptr1 = store1.intern_syntax(Syntax::Symbol(Pos::No, Symbol::key(&[""]).into())); let (z_store, z_ptr) = store1.to_z_store_with_ptr(&ptr1).unwrap(); let (store2, ptr2) = z_store.to_store_with_z_ptr(&z_ptr).unwrap(); let y = store2.fetch_syntax(ptr2).unwrap(); diff --git a/src/syntax_macros.rs b/src/syntax_macros.rs index 5fc5298f35..0438fe7af6 100644 --- a/src/syntax_macros.rs +++ b/src/syntax_macros.rs @@ -1,10 +1,10 @@ #[macro_export] macro_rules! num { ($f:ty, $i:literal) => { - $crate::syntax::Syntax::<$f>::Num(Pos::No, ($i as u64).into()) + $crate::syntax::Syntax::<$f>::Num(Pos::No, ($i).into()) }; ($i:literal) => { - $crate::syntax::Syntax::Num(Pos::No, ($i as u64).into()) + $crate::syntax::Syntax::Num(Pos::No, ($i).into()) }; ($i:expr) => { $crate::syntax::Syntax::Num(Pos::No, $i) @@ -14,10 +14,10 @@ macro_rules! num { #[macro_export] macro_rules! uint { ($f:ty, $i:literal) => { - $crate::syntax::Syntax::<$f>::UInt(Pos::No, $crate::uint::UInt::U64($i as u64)) + $crate::syntax::Syntax::<$f>::UInt(Pos::No, $crate::uint::UInt::U64($i)) }; ($i:literal) => { - $crate::syntax::Syntax::UInt(Pos::No, $crate::uint::UInt::U64($i as u64)) + $crate::syntax::Syntax::UInt(Pos::No, $crate::uint::UInt::U64($i)) }; } @@ -41,60 +41,38 @@ macro_rules! char { }; } -#[allow(unused_macros)] #[macro_export] macro_rules! symbol { - ([$( $x:expr ),*]) => { + ( [$( $x:expr ),*] ) => { { let temp_vec = vec![ $( $x.to_string() ),* ]; - $crate::syntax::Syntax::Symbol(Pos::No, $crate::symbol::Symbol { path: temp_vec }) + $crate::syntax::Syntax::Symbol(Pos::No, $crate::symbol::Symbol::sym_from_vec(temp_vec).into()) } }; - ($f:ty, [$( $x:expr ),*] ) => { + ( $f:ty, [$( $x:expr ),*] ) => { { let temp_vec = vec![ $( $x.to_owned() ),* ]; - $crate::syntax::Syntax::<$f>::Symbol(Pos::No, $crate::symbol::Symbol {path: temp_vec}) + $crate::syntax::Syntax::<$f>::Symbol(Pos::No, $crate::symbol::Symbol::sym_from_vec(temp_vec).into()) } }; } -#[allow(unused_macros)] #[macro_export] -macro_rules! sym { - [$( $x:expr ),*] => { +macro_rules! keyword { + ( [$( $x:expr ),*] ) => { { let temp_vec = vec![ $( $x.to_string() ),* ]; - $crate::symbol::Symbol::new(&temp_vec) + $crate::syntax::Syntax::Symbol(Pos::No, $crate::symbol::Symbol::key_from_vec(temp_vec).into()) } }; -} - -#[allow(unused_macros)] -#[macro_export] -macro_rules! lurksym { - [$( $x:expr ),*] => { + ( $f:ty, [$( $x:expr ),*] ) => { { - let temp_vec = vec![ "lurk".to_owned(), $( $x.to_string() ),* ]; - $crate::symbol::Symbol::new(&temp_vec) + let temp_vec = vec![ $( $x.to_owned() ),* ]; + $crate::syntax::Syntax::<$f>::Path(Pos::No, $crate::symbol::Symbol::key_from_vec(temp_vec).into()) } }; } -#[macro_export] -macro_rules! keyword { - ([$( $x:expr ),*]) => { - { - let temp_vec = vec![ "keyword", $( $x ),* ]; - $crate::syntax::Syntax::Keyword(Pos::No, $crate::symbol::Symbol::new(&temp_vec)) - } - }; - ($f:ty, [$( $x:expr ),*]) => { - { - let temp_vec = vec![ "keyword".to_owned(), $( $x.to_string() ),* ]; - $crate::syntax::Syntax::Keyword(Pos::No, $crate::symbol::Symbol {path: temp_vec}) - } - }; -} #[macro_export] macro_rules! list { ([$( $x:expr ),*], $end:expr ) => { diff --git a/src/tag.rs b/src/tag.rs index 352cf9209b..b759a649f9 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -1,4 +1,4 @@ -use anyhow::anyhow; +use lurk_macros::TryFromRepr; #[cfg(not(target_arch = "wasm32"))] use proptest_derive::Arbitrary; use serde_repr::{Deserialize_repr, Serialize_repr}; @@ -7,7 +7,9 @@ use std::{convert::TryFrom, fmt}; use crate::field::LurkField; use crate::ptr::TypePredicates; -pub trait Tag: Into + TryFrom + Copy + Sized + Eq + fmt::Debug { +pub trait Tag: + Into + TryFrom + Copy + Sized + std::hash::Hash + Eq + fmt::Debug +{ fn from_field(f: &F) -> Option; fn to_field(&self) -> F; @@ -20,7 +22,9 @@ pub trait Tag: Into + TryFrom + Copy + Sized + Eq + fmt::Debug { } /// A tag for expressions. Note that ExprTag, ContTag, Op1, Op2 all live in the same u16 namespace -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr)] +#[derive( + Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize_repr, Deserialize_repr, TryFromRepr, +)] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum ExprTag { @@ -49,27 +53,6 @@ impl From for u64 { } } -impl TryFrom for ExprTag { - type Error = anyhow::Error; - - fn try_from(x: u16) -> Result>::Error> { - match x { - f if f == ExprTag::Nil as u16 => Ok(ExprTag::Nil), - f if f == ExprTag::Cons as u16 => Ok(ExprTag::Cons), - f if f == ExprTag::Sym as u16 => Ok(ExprTag::Sym), - f if f == ExprTag::Fun as u16 => Ok(ExprTag::Fun), - f if f == ExprTag::Thunk as u16 => Ok(ExprTag::Thunk), - f if f == ExprTag::Num as u16 => Ok(ExprTag::Num), - f if f == ExprTag::Str as u16 => Ok(ExprTag::Str), - f if f == ExprTag::Char as u16 => Ok(ExprTag::Char), - f if f == ExprTag::Comm as u16 => Ok(ExprTag::Comm), - f if f == ExprTag::U64 as u16 => Ok(ExprTag::U64), - f if f == ExprTag::Key as u16 => Ok(ExprTag::Key), - f => Err(anyhow!("Invalid ExprTag value: {}", f)), - } - } -} - impl fmt::Display for ExprTag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -94,17 +77,15 @@ impl TypePredicates for ExprTag { } fn is_self_evaluating(&self) -> bool { match self { - Self::Cons => false, - Self::Thunk => false, - Self::Sym => false, - Self::Nil => true, - Self::Fun => true, - Self::Num => true, - Self::Str => true, - Self::Char => true, - Self::Comm => true, - Self::U64 => true, - Self::Key => true, + Self::Cons | Self::Thunk | Self::Sym => false, + Self::Nil + | Self::Fun + | Self::Num + | Self::Str + | Self::Char + | Self::Comm + | Self::U64 + | Self::Key => true, } } @@ -131,7 +112,9 @@ impl Tag for ExprTag { } /// A tag for continuations. Note that ExprTag, ContTag, Op1, Op2 all live in the same u16 namespace -#[derive(Serialize_repr, Deserialize_repr, Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[derive( + Serialize_repr, Deserialize_repr, Debug, Copy, Clone, PartialEq, Eq, Hash, TryFromRepr, +)] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum ContTag { @@ -165,32 +148,6 @@ impl From for u64 { } } -impl TryFrom for ContTag { - type Error = anyhow::Error; - - fn try_from(x: u16) -> Result>::Error> { - match x { - f if f == ContTag::Outermost as u16 => Ok(ContTag::Outermost), - f if f == ContTag::Call0 as u16 => Ok(ContTag::Call0), - f if f == ContTag::Call as u16 => Ok(ContTag::Call), - f if f == ContTag::Call2 as u16 => Ok(ContTag::Call2), - f if f == ContTag::Tail as u16 => Ok(ContTag::Tail), - f if f == ContTag::Error as u16 => Ok(ContTag::Error), - f if f == ContTag::Lookup as u16 => Ok(ContTag::Lookup), - f if f == ContTag::Unop as u16 => Ok(ContTag::Unop), - f if f == ContTag::Binop as u16 => Ok(ContTag::Binop), - f if f == ContTag::Binop2 as u16 => Ok(ContTag::Binop2), - f if f == ContTag::If as u16 => Ok(ContTag::If), - f if f == ContTag::Let as u16 => Ok(ContTag::Let), - f if f == ContTag::LetRec as u16 => Ok(ContTag::LetRec), - f if f == ContTag::Dummy as u16 => Ok(ContTag::Dummy), - f if f == ContTag::Terminal as u16 => Ok(ContTag::Terminal), - f if f == ContTag::Emit as u16 => Ok(ContTag::Emit), - f => Err(anyhow!("Invalid ContTag value: {}", f)), - } - } -} - impl Tag for ContTag { fn from_field(f: &F) -> Option { Self::try_from(f.to_u16()?).ok() @@ -231,7 +188,18 @@ impl fmt::Display for ContTag { } } -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash, Serialize_repr, Deserialize_repr)] +#[derive( + Copy, + Clone, + Debug, + PartialEq, + PartialOrd, + Eq, + Hash, + Serialize_repr, + Deserialize_repr, + TryFromRepr, +)] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum Op1 { @@ -261,28 +229,6 @@ impl From for u64 { } } -impl TryFrom for Op1 { - type Error = anyhow::Error; - - fn try_from(x: u16) -> Result>::Error> { - match x { - f if f == Op1::Car as u16 => Ok(Op1::Car), - f if f == Op1::Cdr as u16 => Ok(Op1::Cdr), - f if f == Op1::Atom as u16 => Ok(Op1::Atom), - f if f == Op1::Emit as u16 => Ok(Op1::Emit), - f if f == Op1::Open as u16 => Ok(Op1::Open), - f if f == Op1::Secret as u16 => Ok(Op1::Secret), - f if f == Op1::Commit as u16 => Ok(Op1::Commit), - f if f == Op1::Num as u16 => Ok(Op1::Num), - f if f == Op1::Comm as u16 => Ok(Op1::Comm), - f if f == Op1::Char as u16 => Ok(Op1::Char), - f if f == Op1::Eval as u16 => Ok(Op1::Eval), - f if f == Op1::U64 as u16 => Ok(Op1::U64), - f => Err(anyhow!("Invalid Op1 value: {}", f)), - } - } -} - pub trait Op where Self: 'static, @@ -371,7 +317,18 @@ impl fmt::Display for Op1 { } } -#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Hash, Serialize_repr, Deserialize_repr)] +#[derive( + Copy, + Clone, + Debug, + PartialEq, + PartialOrd, + Eq, + Hash, + Serialize_repr, + Deserialize_repr, + TryFromRepr, +)] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[repr(u16)] pub enum Op2 { @@ -405,32 +362,6 @@ impl From for u64 { } } -impl TryFrom for Op2 { - type Error = anyhow::Error; - - fn try_from(x: u16) -> Result>::Error> { - match x { - f if f == Op2::Sum as u16 => Ok(Op2::Sum), - f if f == Op2::Diff as u16 => Ok(Op2::Diff), - f if f == Op2::Product as u16 => Ok(Op2::Product), - f if f == Op2::Quotient as u16 => Ok(Op2::Quotient), - f if f == Op2::Equal as u16 => Ok(Op2::Equal), - f if f == Op2::NumEqual as u16 => Ok(Op2::NumEqual), - f if f == Op2::Less as u16 => Ok(Op2::Less), - f if f == Op2::Greater as u16 => Ok(Op2::Greater), - f if f == Op2::LessEqual as u16 => Ok(Op2::LessEqual), - f if f == Op2::GreaterEqual as u16 => Ok(Op2::GreaterEqual), - f if f == Op2::Cons as u16 => Ok(Op2::Cons), - f if f == Op2::StrCons as u16 => Ok(Op2::StrCons), - f if f == Op2::Begin as u16 => Ok(Op2::Begin), - f if f == Op2::Hide as u16 => Ok(Op2::Hide), - f if f == Op2::Modulo as u16 => Ok(Op2::Modulo), - f if f == Op2::Eval as u16 => Ok(Op2::Eval), - f => Err(anyhow!("Invalid Op2 value: {}", f)), - } - } -} - impl Tag for Op2 { fn from_field(f: &F) -> Option { Self::try_from(f.to_u16()?).ok() diff --git a/src/writer.rs b/src/writer.rs index 345778d4a2..c221bf9c90 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -1,23 +1,26 @@ use crate::cont::Continuation; use crate::expr::Expression; use crate::field::LurkField; +use crate::lurk_sym_ptr; +use crate::package::SymbolRef; use crate::ptr::{ContPtr, Ptr}; +use crate::state::State; use crate::store::Store; use crate::symbol::Symbol; use crate::z_expr::ZExpr; use std::io; pub trait Write { - fn fmt(&self, store: &Store, w: &mut W) -> io::Result<()>; - fn fmt_to_string(&self, store: &Store) -> String { + fn fmt(&self, store: &Store, state: &State, w: &mut W) -> io::Result<()>; + fn fmt_to_string(&self, store: &Store, state: &State) -> String { let mut out = Vec::new(); - self.fmt(store, &mut out).expect("preallocated"); + self.fmt(store, state, &mut out).expect("preallocated"); String::from_utf8(out).expect("I know it") } } impl Write for Ptr { - fn fmt(&self, store: &Store, w: &mut W) -> io::Result<()> { + fn fmt(&self, store: &Store, state: &State, w: &mut W) -> io::Result<()> { if self.is_opaque() { // This should never fail. write!(w, " Write for Ptr { if let Some(x) = store.hash_expr(self) { write!(w, " ")?; - crate::expr::Expression::Num(crate::num::Num::Scalar(*x.value())).fmt(store, w)?; + crate::expr::Expression::Num(crate::num::Num::Scalar(*x.value())) + .fmt(store, state, w)?; } write!(w, ">") } else if let Some(expr) = store.fetch(self) { - expr.fmt(store, w) + expr.fmt(store, state, w) } else { Err(io::Error::new( io::ErrorKind::Other, @@ -40,9 +44,9 @@ impl Write for Ptr { } impl Write for ContPtr { - fn fmt(&self, store: &Store, w: &mut W) -> io::Result<()> { + fn fmt(&self, store: &Store, state: &State, w: &mut W) -> io::Result<()> { if let Some(cont) = store.fetch_cont(self) { - cont.fmt(store, w) + cont.fmt(store, state, w) } else { Err(io::Error::new( io::ErrorKind::Other, @@ -52,31 +56,30 @@ impl Write for ContPtr { } } -fn write_symbol(w: &mut W, sym: &Symbol) -> io::Result<()> { - let lurk_syms = Symbol::lurk_syms(); - if let Some(sym) = lurk_syms.get(sym) { - write!(w, "{}", sym) - } else { - write!(w, "{}", sym) - } +fn write_symbol(w: &mut W, sym: Symbol, state: &State) -> io::Result<()> { + write!(w, "{}", state.fmt_to_string(&SymbolRef::new(sym))) } impl Write for Expression { - fn fmt(&self, store: &Store, w: &mut W) -> io::Result<()> { - use Expression::*; + fn fmt(&self, store: &Store, state: &State, w: &mut W) -> io::Result<()> { + use Expression::{ + Char, Comm, Cons, EmptyStr, Fun, Key, Nil, Num, RootKey, RootSym, Str, Sym, Thunk, UInt, + }; match self { Nil => write!(w, "nil"), - RootSym => write_symbol(w, &Symbol::root()), + RootSym => write_symbol(w, Symbol::root_sym(), state), + RootKey => write_symbol(w, Symbol::root_key(), state), Sym(car, cdr) => { let head = store.fetch_string(car).expect("missing symbol head"); let tail = store.fetch_sym(cdr).expect("missing symbol tail"); - write_symbol(w, &tail.extend(&[head])) + write_symbol(w, tail.extend(&[head]), state) } Key(car, cdr) => { let head = store.fetch_string(car).expect("missing keyword head"); - let tail = store.fetch_sym(cdr).expect("missing keyword tail"); - write_symbol(w, &tail.extend(&[head])) + let mut tail = store.fetch_sym(cdr).expect("missing keyword tail"); + tail.set_as_keyword(); + write_symbol(w, tail.extend(&[head]), state) } EmptyStr => write!(w, "\"\""), Str(car, cdr) => { @@ -85,11 +88,11 @@ impl Write for Expression { write!(w, "\"{head}{tail}\"") } Fun(arg, body, _closed_env) => { - let is_zero_arg = *arg == store.get_lurk_sym("_").expect("dummy_arg (_) missing"); + let is_zero_arg = *arg == lurk_sym_ptr!(store, dummy); let arg = store.fetch(arg).unwrap(); write!(w, " Write for Expression { match store.fetch(body).unwrap() { Expression::Cons(expr, _) => { let expr = store.fetch(&expr).unwrap(); - expr.fmt(store, w)?; + expr.fmt(store, state, w)?; } Expression::Nil => { - store.get_nil().fmt(store, w)?; + lurk_sym_ptr!(store, nil).fmt(store, state, w)?; } _ => { panic!("Function body was neither a Cons nor Nil"); @@ -111,14 +114,14 @@ impl Write for Expression { Num(n) => write!(w, "{n}"), Thunk(f) => { write!(w, "Thunk{{ value: ")?; - f.value.fmt(store, w)?; + f.value.fmt(store, state, w)?; write!(w, " => cont: ")?; - f.continuation.fmt(store, w)?; + f.continuation.fmt(store, state, w)?; write!(w, "}}") } Cons(_, _) => { write!(w, "(")?; - self.print_tail(store, w) + self.print_tail(store, state, w) } Comm(secret, payload) => { // This requires a run-time coercion. @@ -126,7 +129,7 @@ impl Write for Expression { write!(w, "(comm ")?; let c = ZExpr::Comm(*secret, store.hash_expr(payload).unwrap()) .z_ptr(&store.poseidon_cache); - Num(crate::num::Num::Scalar(c.1)).fmt(store, w)?; + Num(crate::num::Num::Scalar(c.1)).fmt(store, state, w)?; write!(w, ")") } Char(c) => { @@ -138,7 +141,12 @@ impl Write for Expression { } impl Expression { - fn print_tail(&self, store: &Store, w: &mut W) -> io::Result<()> { + fn print_tail( + &self, + store: &Store, + state: &State, + w: &mut W, + ) -> io::Result<()> { match self { Expression::Nil => write!(w, ")"), Expression::Cons(car, cdr) => { @@ -146,14 +154,14 @@ impl Expression { let cdr = store.fetch(cdr); let fmt_car = |store, w: &mut W| { if let Some(car) = car { - car.fmt(store, w) + car.fmt(store, state, w) } else { write!(w, "") } }; let fmt_cdr = |store, w: &mut W| { if let Some(cdr) = &cdr { - cdr.fmt(store, w) + cdr.fmt(store, state, w) } else { write!(w, "") } @@ -168,7 +176,7 @@ impl Expression { fmt_car(store, w)?; write!(w, " ")?; if let Some(cdr) = cdr { - cdr.print_tail(store, w) + cdr.print_tail(store, state, w) } else { write!(w, "") } @@ -182,13 +190,13 @@ impl Expression { None => write!(w, ""), } } - expr => expr.fmt(store, w), + expr => expr.fmt(store, state, w), } } } impl Write for Continuation { - fn fmt(&self, store: &Store, w: &mut W) -> io::Result<()> { + fn fmt(&self, store: &Store, state: &State, w: &mut W) -> io::Result<()> { match self { Continuation::Outermost => write!(w, "Outermost"), Continuation::Call0 { @@ -196,9 +204,9 @@ impl Write for Continuation { continuation, } => { write!(w, "Call0{{ saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Call { @@ -207,11 +215,11 @@ impl Write for Continuation { continuation, } => { write!(w, "Call{{ unevaled_arg: ")?; - unevaled_arg.fmt(store, w)?; + unevaled_arg.fmt(store, state, w)?; write!(w, ", saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Call2 { @@ -220,11 +228,11 @@ impl Write for Continuation { continuation, } => { write!(w, "Call2{{ function: ")?; - function.fmt(store, w)?; + function.fmt(store, state, w)?; write!(w, ", saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Tail { @@ -232,9 +240,9 @@ impl Write for Continuation { continuation, } => { write!(w, "Tail{{ saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Error => write!(w, "Error"), @@ -243,9 +251,9 @@ impl Write for Continuation { continuation, } => { write!(w, "Lookup{{ saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Unop { @@ -253,7 +261,7 @@ impl Write for Continuation { continuation, } => { write!(w, "Unop{{ operator: {operator}, continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Binop { @@ -264,11 +272,11 @@ impl Write for Continuation { } => { write!(w, "Binop{{ operator: ")?; write!(w, "{operator}, unevaled_args: ")?; - unevaled_args.fmt(store, w)?; + unevaled_args.fmt(store, state, w)?; write!(w, ", saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Binop2 { @@ -277,9 +285,9 @@ impl Write for Continuation { continuation, } => { write!(w, "Binop2{{ operator: {operator}, evaled_arg: ")?; - evaled_arg.fmt(store, w)?; + evaled_arg.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::If { @@ -287,9 +295,9 @@ impl Write for Continuation { continuation, } => { write!(w, "If{{ unevaled_args: ")?; - unevaled_args.fmt(store, w)?; + unevaled_args.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Let { @@ -299,13 +307,13 @@ impl Write for Continuation { continuation, } => { write!(w, "Let{{ var: ")?; - var.fmt(store, w)?; + var.fmt(store, state, w)?; write!(w, ", body: ")?; - body.fmt(store, w)?; + body.fmt(store, state, w)?; write!(w, ", saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::LetRec { @@ -315,13 +323,13 @@ impl Write for Continuation { continuation, } => { write!(w, "LetRec{{var: ")?; - var.fmt(store, w)?; + var.fmt(store, state, w)?; write!(w, ", saved_env: ")?; - saved_env.fmt(store, w)?; + saved_env.fmt(store, state, w)?; write!(w, ", body: ")?; - body.fmt(store, w)?; + body.fmt(store, state, w)?; write!(w, ", continuation: ")?; - continuation.fmt(store, w)?; + continuation.fmt(store, state, w)?; write!(w, " }}") } Continuation::Dummy => write!(w, "Dummy"), @@ -332,7 +340,7 @@ impl Write for Continuation { write!(w, "Emit")?; write!(w, "") // Omit continuation for clarity when logging and using output. // write!(w, " {{ continuation: ")?; - // continuation.fmt(store, w)?; + // continuation.fmt(store, state, w)?; // write!(w, " }}") } } @@ -341,15 +349,19 @@ impl Write for Continuation { #[cfg(test)] pub mod test { + use crate::state::initial_lurk_state; + use super::*; use pasta_curves::pallas::Scalar as Fr; #[test] fn print_expr() { let mut s = Store::::default(); - let nil = s.nil(); - let x = s.sym("x"); - let lambda = s.lurk_sym("lambda"); + let state = &mut State::init_lurk_state(); + let nil = lurk_sym_ptr!(s, nil); + let x = s.user_sym("x"); + state.intern("x"); + let lambda = lurk_sym_ptr!(&s, lambda); let val = s.num(123); let lambda_args = s.cons(x, nil); let body = s.cons(x, nil); @@ -357,17 +369,24 @@ pub mod test { let whole_lambda = s.cons(lambda, rest); let lambda_arguments = s.cons(val, nil); let expr = s.cons(whole_lambda, lambda_arguments); - let output = expr.fmt_to_string(&s); + let output = expr.fmt_to_string(&s, state); assert_eq!("((lambda (x) x) 123)".to_string(), output); } #[test] - fn print_expr2() { - let mut s = Store::::default(); - let expr = s.intern_symbol(Symbol::new(&["foo", "bar", "baz"])); - let output = expr.fmt_to_string(&s); + fn test_print_num() { + let mut store = Store::::default(); + let num = store.num(5); + let res = num.fmt_to_string(&store, initial_lurk_state()); + assert_eq!(&res, &"5"); + } - assert_eq!("foo.bar.baz".to_string(), output); + #[test] + fn test_print_keyword() { + let mut store = Store::::default(); + let foo_key_ptr = store.intern_symbol(&Symbol::key_from_vec(vec!["foo".into()])); + let foo_key_str = foo_key_ptr.fmt_to_string(&store, initial_lurk_state()); + assert_eq!(":foo", foo_key_str); } } diff --git a/src/z_data.rs b/src/z_data.rs index dc564d0c33..6aa8447744 100644 --- a/src/z_data.rs +++ b/src/z_data.rs @@ -62,7 +62,7 @@ impl Display for ZData { .map(|x| format!("{:02x?}", x)) .collect::>() .join(", "); - write!(f, "a:{}", xs_str)?; + write!(f, "a:{xs_str}")?; } Self::Cell(xs) => { let xs_str = xs @@ -70,7 +70,7 @@ impl Display for ZData { .map(|x| format!("{}", x)) .collect::>() .join(", "); - write!(f, "c:{}", xs_str)?; + write!(f, "c:{xs_str}")?; } } write!(f, "]")?; @@ -200,7 +200,7 @@ impl ZData { (i, ZData::Atom(data.to_vec())) } else { let (i, xs) = count(ZData::from_bytes_aux, size)(i)?; - (i, ZData::Cell(xs.to_vec())) + (i, ZData::Cell(xs)) }; Ok((i, res)) diff --git a/src/z_data/serde/de.rs b/src/z_data/serde/de.rs index cae6dc8040..c363c6dac6 100644 --- a/src/z_data/serde/de.rs +++ b/src/z_data/serde/de.rs @@ -49,9 +49,9 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { ZData::Atom(x) if x.len() == 1 => match x[0] { 0u8 => visitor.visit_bool(false), 1u8 => visitor.visit_bool(true), - err => Err(SerdeError::Type(format!("expected bool, got: {}", err))), + err => Err(SerdeError::Type(format!("expected bool, got: {err}"))), }, - err => Err(SerdeError::Type(format!("expected bool, got: {}", err))), + err => Err(SerdeError::Type(format!("expected bool, got: {err}"))), } } @@ -105,7 +105,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { { match self.input { ZData::Atom(x) if x.len() == 1 => visitor.visit_u8(x[0]), - err => Err(SerdeError::Type(format!("expected u8, got: {}", err))), + err => Err(SerdeError::Type(format!("expected u8, got: {err}"))), } } @@ -119,7 +119,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let a: [u8; 2] = x.clone().try_into().unwrap(); visitor.visit_u16(u16::from_le_bytes(a)) } - err => Err(SerdeError::Type(format!("expected u16, got: {}", err))), + err => Err(SerdeError::Type(format!("expected u16, got: {err}"))), } } @@ -133,7 +133,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let a: [u8; 4] = x.clone().try_into().unwrap(); visitor.visit_u32(u32::from_le_bytes(a)) } - err => Err(SerdeError::Type(format!("expected u32: got {}", err))), + err => Err(SerdeError::Type(format!("expected u32: got {err}"))), } } @@ -147,7 +147,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let a: [u8; 8] = x.clone().try_into().unwrap(); visitor.visit_u64(u64::from_le_bytes(a)) } - err => Err(SerdeError::Type(format!("expected u64: got {}", err))), + err => Err(SerdeError::Type(format!("expected u64: got {err}"))), } } @@ -182,10 +182,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { .map_err(|e| SerdeError::Type(format!("expected char: {}", e)))?; match char::from_u32(num) { Some(a) => visitor.visit_char(a), - None => Err(SerdeError::Type(format!( - "failed to get char from: {}", - num - ))), + None => Err(SerdeError::Type(format!("failed to get char from: {num}"))), } } @@ -203,12 +200,12 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { v.push(char::deserialize(&mut Deserializer::from_z_data(zd))?) } err => { - return Err(SerdeError::Type(format!("expected string, got: {}", err))) + return Err(SerdeError::Type(format!("expected string, got: {err}"))) } } } } - err => return Err(SerdeError::Type(format!("expected string, got: {}", err))), + err => return Err(SerdeError::Type(format!("expected string, got: {err}"))), } visitor.visit_str(&v.into_iter().collect::()) } @@ -228,7 +225,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { { match self.input { ZData::Atom(x) => visitor.visit_bytes(x), - err => Err(SerdeError::Type(format!("expected bytes, got: {}", err))), + err => Err(SerdeError::Type(format!("expected bytes, got: {err}"))), } } @@ -248,11 +245,11 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { match self.input { ZData::Atom(x) => match x.as_slice() { [] => visitor.visit_none(), - err => Err(SerdeError::Type(format!("expected Option, got: {:?}", err))), + err => Err(SerdeError::Type(format!("expected Option, got: {err:?}"))), }, ZData::Cell(xs) => match xs.as_slice() { [a] => visitor.visit_some(&mut Deserializer::from_z_data(a)), - err => Err(SerdeError::Type(format!("expected Option, got: {:?}", err))), + err => Err(SerdeError::Type(format!("expected Option, got: {err:?}"))), }, } } @@ -265,12 +262,9 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { match self.input { ZData::Atom(x) => match x.as_slice() { [] => visitor.visit_none(), - err => Err(SerdeError::Type(format!( - "expected Unit (), got: {:?}", - err - ))), + err => Err(SerdeError::Type(format!("expected Unit (), got: {err:?}"))), }, - err => Err(SerdeError::Type(format!("expected Unit (), got: {}", err))), + err => Err(SerdeError::Type(format!("expected Unit (), got: {err}"))), } } @@ -305,7 +299,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { { match self.input { ZData::Cell(xs) => visitor.visit_seq(SeqAccess::new(xs, 0)), - err => Err(SerdeError::Type(format!("expected sequence, got: {}", err))), + err => Err(SerdeError::Type(format!("expected sequence, got: {err}"))), } } @@ -337,7 +331,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { { match self.input { ZData::Cell(xs) => visitor.visit_map(MapAccess::new(xs)), - err => Err(SerdeError::Type(format!("expected map, got: {}", err))), + err => Err(SerdeError::Type(format!("expected map, got: {err}"))), } } @@ -370,9 +364,9 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { let variant = String::from(variants[idx_vec[0] as usize]); visitor.visit_enum(Enum::new(self, variant, 1)) } - err => Err(SerdeError::Type(format!("expected enum, got: {}", err))), + err => Err(SerdeError::Type(format!("expected enum, got: {err}"))), }, - err => Err(SerdeError::Type(format!("expected enum, got: {}", err))), + err => Err(SerdeError::Type(format!("expected enum, got: {err}"))), } } @@ -515,8 +509,7 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> { ZData::Cell(xs) if xs.len() > 1 => Deserializer::from_z_data(&xs[1]), err => { return Err(SerdeError::Type(format!( - "expected newtype variant, got: {}", - err + "expected newtype variant, got: {err}" ))) } }; @@ -529,7 +522,7 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> { { match self.de.input { ZData::Cell(xs) => visitor.visit_seq(SeqAccess::new(xs, self.index)), - err => Err(SerdeError::Type(format!("expected sequence, got: {}", err))), + err => Err(SerdeError::Type(format!("expected sequence, got: {err}"))), } } diff --git a/src/z_data/serde/ser.rs b/src/z_data/serde/ser.rs index 617ecaea65..ef9d092baa 100644 --- a/src/z_data/serde/ser.rs +++ b/src/z_data/serde/ser.rs @@ -10,22 +10,27 @@ where value.serialize(&Serializer) } +#[derive(Debug)] pub struct Serializer; +#[derive(Debug)] pub struct SerializeCell { cell: Vec, } +#[derive(Debug)] pub struct SerializeMap { cell: Vec, next_key: Option, } +#[derive(Debug)] pub struct SerializeTupleVariant { variant_index: u32, cell: Vec, } +#[derive(Debug)] pub struct StructSerializer<'a> { ser: &'a Serializer, cell: Vec, @@ -42,8 +47,8 @@ impl<'a> StructSerializer<'a> { Ok(()) } - fn end_inner(self) -> Result, SerdeError> { - Ok(self.cell) + fn end_inner(self) -> Vec { + self.cell } } @@ -394,7 +399,7 @@ impl<'a> ser::SerializeStruct for StructSerializer<'a> { } fn end(self) -> Result { - Ok(ZData::Cell(self.end_inner()?)) + Ok(ZData::Cell(self.end_inner())) } } @@ -413,7 +418,7 @@ impl<'a> ser::SerializeStructVariant for StructSerializer<'a> { let mut cell = vec![u8::try_from(self.variant_index) .unwrap() .serialize(self.ser)?]; - cell.extend(self.end_inner()?); + cell.extend(self.end_inner()); Ok(ZData::Cell(cell)) } } diff --git a/src/z_data/z_cont.rs b/src/z_data/z_cont.rs index 6091afa806..92050450b2 100644 --- a/src/z_data/z_cont.rs +++ b/src/z_data/z_cont.rs @@ -90,20 +90,7 @@ impl ZCont { /// Creates a list of field elements corresponding to the `ZCont` for hashing pub fn hash_components(&self) -> [F; 8] { match self { - Self::Outermost => [F::ZERO; 8], - Self::Call0 { - saved_env, - continuation, - } => [ - saved_env.0.to_field(), - saved_env.1, - continuation.0.to_field(), - continuation.1, - F::ZERO, - F::ZERO, - F::ZERO, - F::ZERO, - ], + Self::Outermost | Self::Error | Self::Dummy | Self::Terminal => [F::ZERO; 8], Self::Call { saved_env, unevaled_arg, @@ -132,21 +119,15 @@ impl ZCont { F::ZERO, F::ZERO, ], - Self::Tail { + Self::Call0 { saved_env, continuation, - } => [ - saved_env.0.to_field(), - saved_env.1, - continuation.0.to_field(), - continuation.1, - F::ZERO, - F::ZERO, - F::ZERO, - F::ZERO, - ], - Self::Error => [F::ZERO; 8], - Self::Lookup { + } + | Self::Tail { + saved_env, + continuation, + } + | Self::Lookup { saved_env, continuation, } => [ @@ -219,17 +200,8 @@ impl ZCont { body, saved_env, continuation, - } => [ - var.0.to_field(), - var.1, - body.0.to_field(), - body.1, - saved_env.0.to_field(), - saved_env.1, - continuation.0.to_field(), - continuation.1, - ], - Self::LetRec { + } + | Self::LetRec { var, body, saved_env, @@ -254,8 +226,6 @@ impl ZCont { F::ZERO, F::ZERO, ], - Self::Dummy => [F::ZERO; 8], - Self::Terminal => [F::ZERO; 8], } } diff --git a/src/z_data/z_expr.rs b/src/z_data/z_expr.rs index 831838ecb8..47487ba7bc 100644 --- a/src/z_data/z_expr.rs +++ b/src/z_data/z_expr.rs @@ -31,6 +31,7 @@ pub enum ZExpr { /// A commitment, which contains an opaque value and a pointer to the hidden data in the `ZStore` Comm(F, ZExprPtr), RootSym, + RootKey, /// Contains a symbol (a list of strings) and a pointer to the tail. Sym(ZExprPtr, ZExprPtr), Key(ZExprPtr, ZExprPtr), @@ -54,24 +55,25 @@ impl std::fmt::Display for ZExpr { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ZExpr::Nil => write!(f, "nil"), - ZExpr::Cons(x, y) => write!(f, "({} . {})", x, y), - ZExpr::Str(x, y) => write!(f, "(str {} {})", x, y), - ZExpr::Sym(x, y) => write!(f, "(sym {} {})", x, y), - ZExpr::Key(x, y) => write!(f, "(key {} {})", x, y), + ZExpr::Cons(x, y) => write!(f, "({x} . {y})"), + ZExpr::Str(x, y) => write!(f, "(str {x} {y})"), + ZExpr::Sym(x, y) => write!(f, "(sym {x} {y})"), + ZExpr::Key(x, y) => write!(f, "(key {x} {y})"), ZExpr::Comm(ff, x) => { write!(f, "(comm {} {})", ff.trimmed_hex_digits(), x) } ZExpr::EmptyStr => write!(f, "emptystr"), ZExpr::RootSym => write!(f, "rootsym"), - ZExpr::Thunk(val, cont) => write!(f, "(thunk {} {})", val, cont), + ZExpr::RootKey => write!(f, "rootkey"), + ZExpr::Thunk(val, cont) => write!(f, "(thunk {val} {cont})"), ZExpr::Fun { arg, body, closed_env, - } => write!(f, "(fun {} {} {})", arg, body, closed_env), - ZExpr::Char(x) => write!(f, "(char {})", x), - ZExpr::Num(x) => write!(f, "(num {:?})", x), - ZExpr::UInt(x) => write!(f, "(uint {})", x), + } => write!(f, "(fun {arg} {body} {closed_env})"), + ZExpr::Char(x) => write!(f, "(char {x})"), + ZExpr::Num(x) => write!(f, "(num {x:?})"), + ZExpr::UInt(x) => write!(f, "(uint {x})"), } } } @@ -88,6 +90,7 @@ impl ZExpr { ), ZExpr::Comm(f, x) => ZPtr(ExprTag::Comm, cache.hash3(&[*f, x.0.to_field(), x.1])), ZExpr::RootSym => ZPtr(ExprTag::Sym, F::ZERO), + ZExpr::RootKey => ZPtr(ExprTag::Key, F::ZERO), ZExpr::Sym(x, y) => ZPtr( ExprTag::Sym, cache.hash4(&[x.0.to_field(), x.1, y.0.to_field(), y.1]), diff --git a/src/z_data/z_ptr.rs b/src/z_data/z_ptr.rs index eb611d3705..fa40144a58 100644 --- a/src/z_data/z_ptr.rs +++ b/src/z_data/z_ptr.rs @@ -54,24 +54,23 @@ impl Display for ZPtr { impl PartialOrd for ZPtr { fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ZPtr { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { ( self.0.to_field_bytes::().as_ref(), self.1.to_repr().as_ref(), ) - .partial_cmp(&( + .cmp(&( other.0.to_field_bytes::().as_ref(), other.1.to_repr().as_ref(), )) } } -impl Ord for ZPtr { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.partial_cmp(other) - .expect("ZPtr::cmp: partial_cmp domain invariant violation") - } -} - #[allow(clippy::derived_hash_with_manual_eq)] impl Hash for ZPtr { fn hash(&self, state: &mut H) { @@ -110,17 +109,17 @@ impl ZPtr { pub fn to_base32(&self) -> String { let tag_b32 = Base32Unpadded::encode_string(&self.0.into().to_le_bytes()); let val_b32 = Base32Unpadded::encode_string(self.1.to_repr().as_ref()); - format!("{}z{}", tag_b32, val_b32) + format!("{tag_b32}z{val_b32}") } /// Converts a base32-encoded string to a ZPtr pub fn from_base32(zptr: &str) -> Result { let tag_bytes = Base32Unpadded::decode_vec(&zptr[0..4]) - .map_err(|_| anyhow!("Failed to decode base32"))?; + .map_err(|e| anyhow!(format!("Failed to decode base32: {}", e)))?; let val_bytes = Base32Unpadded::decode_vec(&zptr[5..]) - .map_err(|_| anyhow!("Failed to decode base32"))?; + .map_err(|e| anyhow!(format!("Failed to decode base32: {}", e)))?; let tag = E::try_from(u16::from_le_bytes(tag_bytes[..2].try_into().unwrap())) - .map_err(|_| anyhow!("Failed to decode tag"))?; + .map_err(|e| anyhow!(format!("Failed to decode tag: {}", e)))?; let val = F::from_bytes(&val_bytes).ok_or_else(|| anyhow!("Failed to decode field"))?; Ok(Self::from_parts(tag, val)) } @@ -135,7 +134,7 @@ impl ZExprPtr { let mut store = Store::::default(); let ptr = store .read(value) - .map_err(|_| store::Error("Parse error".into()))?; + .map_err(|e| store::Error(format!("Parse error: {}", e)))?; let zptr = store .hash_expr(&ptr) .ok_or(store::Error("Invalid ptr".into()))?; diff --git a/src/z_data/z_store.rs b/src/z_data/z_store.rs index 42d360aeba..0aab5e4650 100644 --- a/src/z_data/z_store.rs +++ b/src/z_data/z_store.rs @@ -22,7 +22,7 @@ use crate::z_ptr::ZPtr; use crate::field::LurkField; -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] #[cfg_attr(not(target_arch = "wasm32"), derive(Arbitrary))] #[cfg_attr(not(target_arch = "wasm32"), proptest(no_bound))] #[cfg_attr( @@ -107,6 +107,7 @@ impl ZStore { ZPtr(ExprTag::Num, val) => Some(ZExpr::Num(*val)), ZPtr(ExprTag::Str, val) if *val == F::ZERO => Some(ZExpr::EmptyStr), ZPtr(ExprTag::Sym, val) if *val == F::ZERO => Some(ZExpr::RootSym), + ZPtr(ExprTag::Key, val) if *val == F::ZERO => Some(ZExpr::RootSym), _ => None, } } @@ -137,7 +138,9 @@ impl ZStore { /// Stores a null symbol in the `ZStore` and returns the resulting pointer pub fn nil_z_ptr(&mut self, poseidon_cache: &PoseidonCache) -> ZExprPtr { - let z_ptr = self.put_symbol(&Symbol::nil(), poseidon_cache).0; + let z_ptr = self + .put_symbol(&crate::state::lurk_sym("nil"), poseidon_cache) + .0; ZPtr(ExprTag::Nil, z_ptr.1) } @@ -165,7 +168,7 @@ impl ZStore { ) -> (ZExprPtr, ZExpr) { let mut expr = ZExpr::RootSym; let mut ptr = expr.z_ptr(poseidon_cache); - for s in sym.path.iter().rev() { + for s in sym.path() { let (str_ptr, _) = self.put_string(s, poseidon_cache); expr = ZExpr::Sym(str_ptr, ptr); ptr = expr.z_ptr(poseidon_cache); diff --git a/tests/lurk-cli-tests.rs b/tests/lurk-cli-tests.rs index c8410f7a61..66c1acb047 100644 --- a/tests/lurk-cli-tests.rs +++ b/tests/lurk-cli-tests.rs @@ -4,13 +4,23 @@ use std::fs::File; use std::io::prelude::*; use std::process::Command; use tempfile::Builder; +use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry}; +use tracing_texray::TeXRayLayer; fn lurk_cmd() -> Command { Command::cargo_bin("lurk").unwrap() } #[test] -fn test_help_command() { +fn test_help_subcommand() { + let mut cmd = lurk_cmd(); + + cmd.arg("help"); + cmd.assert().success(); +} + +#[test] +fn test_help_flag_command() { let mut cmd = lurk_cmd(); cmd.arg("--help"); @@ -37,10 +47,15 @@ fn test_bad_command() { #[test] fn test_config_file() { - pretty_env_logger::formatted_builder() - .is_test(true) - .try_init() - .unwrap(); + let subscriber = Registry::default() + .with(fmt::layer().pretty().with_test_writer()) + .with(EnvFilter::from_default_env()) + // note: we don't `tracing_texray::examine` anything below, so no spans are printed + // but we add the layer to allow the option in the future, maybe with a feature? + .with(TeXRayLayer::new()); + + tracing::subscriber::set_global_default(subscriber).unwrap(); + let tmp_dir = Builder::new().prefix("tmp").tempdir().unwrap(); let tmp_dir = Utf8Path::from_path(tmp_dir.path()).unwrap(); let config_dir = tmp_dir.join("lurk.toml"); @@ -50,13 +65,13 @@ fn test_config_file() { let mut config_file = File::create(&config_dir).unwrap(); config_file - .write_all(format!("public_params = \"{}\"\n", public_params_dir).as_bytes()) + .write_all(format!("public_params = \"{public_params_dir}\"\n").as_bytes()) .unwrap(); config_file - .write_all(format!("proofs = \"{}\"\n", proofs_dir).as_bytes()) + .write_all(format!("proofs = \"{proofs_dir}\"\n").as_bytes()) .unwrap(); config_file - .write_all(format!("commits = \"{}\"\n", commits_dir).as_bytes()) + .write_all(format!("commits = \"{commits_dir}\"\n").as_bytes()) .unwrap(); // Overwrite proof dir with env var @@ -83,7 +98,7 @@ fn test_prove_and_verify() { let mut file = File::create(lurk_file.clone()).unwrap(); file.write_all(b"!(prove (+ 1 1))\n").unwrap(); - file.write_all(b"!(verify \"Nova_Pallas_10_049abe0ff3b8c08c6022f44c3da7e27962b4a92af7c204a38976e52c94c9cea6\")\n").unwrap(); + file.write_all(b"!(verify \"Nova_Pallas_10_3f2526abf20fc9006dd93c0d3ff49954ef070ef52d2e88426974de42cc27bdb2\")\n").unwrap(); let mut cmd = lurk_cmd(); cmd.arg("load"); diff --git a/tests/lurk-lib-tests.rs b/tests/lurk-lib-tests.rs index 660db467b1..7950e7ad24 100644 --- a/tests/lurk-lib-tests.rs +++ b/tests/lurk-lib-tests.rs @@ -1,6 +1,9 @@ +use camino::Utf8PathBuf; use lurk::{ + cli::{backend::Backend, repl::Repl}, eval::lang::{Coproc, Lang}, repl::{repl, ReplState}, + store::Store, }; use pasta_curves::pallas::Scalar as S1; use std::path::Path; @@ -33,10 +36,13 @@ fn lurk_cli_tests() { git submodule update" ); } + let mut repl_new = Repl::new(Store::default(), 10, 100000000, Backend::Nova); for f in test_files { let joined = example_dir.join(f); + let joined_new = Utf8PathBuf::from_path_buf(joined.clone()).unwrap(); repl::>, _, Coproc>(Some(joined), Lang::new()).unwrap(); + let _ = repl_new.load_file(&joined_new); } } diff --git a/tests/lurk-nivc-test.rs b/tests/lurk-nivc-test.rs new file mode 100644 index 0000000000..83112d2615 --- /dev/null +++ b/tests/lurk-nivc-test.rs @@ -0,0 +1,12 @@ +use assert_cmd::prelude::*; +use std::process::Command; + +/// TODO: replace this test for more granular ones, specific for the NIVC +/// pipeline steps +#[test] +#[ignore] +fn test_sha256_nivc() { + let mut cmd = Command::new("cargo"); + cmd.args(["run", "--release", "--example", "sha256_nivc"]); + cmd.assert().success(); +}