From b1edefbd44f5f3e5906ce768cdbffc80dd35d116 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Tue, 23 Apr 2024 14:14:27 +0000 Subject: [PATCH] [1 changes] chore: Delete unused brillig methods (https://github.com/noir-lang/noir/pull/4887) feat(acir_gen): Brillig stdlib (https://github.com/noir-lang/noir/pull/4848) --- .noir-sync-commit | 2 +- noir/noir-repo/.github/workflows/docs-pr.yml | 2 +- .../.github/workflows/formatting.yml | 4 +- .../.github/workflows/gates_report.yml | 2 +- .../.github/workflows/publish-acvm.yml | 2 +- .../.github/workflows/publish-es-packages.yml | 6 +- .../.github/workflows/publish-nargo.yml | 4 +- .../.github/workflows/test-js-packages.yml | 8 +- .../workflows/test-rust-workspace-msrv.yml | 4 +- .../.github/workflows/test-rust-workspace.yml | 4 +- noir/noir-repo/CHANGELOG.md | 2 +- noir/noir-repo/Cargo.lock | 33 +- noir/noir-repo/Cargo.toml | 11 +- noir/noir-repo/README.md | 61 +- noir/noir-repo/acvm-repo/acir/Cargo.toml | 6 + .../acvm-repo/acir/benches/serialization.rs | 123 ++ .../acvm-repo/acir/src/circuit/brillig.rs | 2 +- noir/noir-repo/acvm-repo/acvm_js/build.sh | 5 +- .../test/browser/black_box_solvers.test.ts | 3 - .../compute_note_hash_and_nullifier.rs | 3 +- .../src/transforms/contract_interface.rs | 5 +- .../aztec_macros/src/transforms/events.rs | 6 +- .../aztec_macros/src/transforms/functions.rs | 15 +- .../src/transforms/note_interface.rs | 8 +- .../aztec_macros/src/transforms/storage.rs | 8 +- .../aztec_macros/src/utils/ast_utils.rs | 13 +- .../aztec_macros/src/utils/errors.rs | 9 +- .../aztec_macros/src/utils/hir_utils.rs | 13 +- noir/noir-repo/compiler/fm/Cargo.toml | 1 + .../compiler/noirc_driver/Cargo.toml | 1 + .../compiler/noirc_driver/src/abi_gen.rs | 2 +- .../compiler/noirc_errors/Cargo.toml | 1 + .../compiler/noirc_evaluator/Cargo.toml | 5 +- .../compiler/noirc_evaluator/src/ssa.rs | 5 +- .../src/ssa/acir_gen/acir_ir/acir_variable.rs | 107 +- .../ssa/acir_gen/acir_ir/generated_acir.rs | 91 +- .../noirc_evaluator/src/ssa/acir_gen/mod.rs | 402 ++++- .../src/ssa/function_builder/data_bus.rs | 5 +- .../src/ssa/ssa_gen/context.rs | 20 +- .../noirc_evaluator/src/ssa/ssa_gen/mod.rs | 16 +- .../compiler/noirc_frontend/Cargo.toml | 2 + .../noirc_frontend/src/ast/expression.rs | 9 +- .../noirc_frontend/src/ast/function.rs | 2 +- .../noirc_frontend/src/ast/statement.rs | 74 +- .../noirc_frontend/src/ast/structure.rs | 4 +- .../compiler/noirc_frontend/src/ast/traits.rs | 7 +- .../noirc_frontend/src/ast/type_alias.rs | 2 +- .../compiler/noirc_frontend/src/debug/mod.rs | 3 + .../src/hir/comptime/hir_to_ast.rs | 19 +- .../src/hir/comptime/interpreter.rs | 1294 +++++++++++++++++ .../noirc_frontend/src/hir/comptime/mod.rs | 2 + .../noirc_frontend/src/hir/comptime/tests.rs | 166 +++ .../src/hir/def_collector/dc_crate.rs | 8 +- .../src/hir/def_collector/dc_mod.rs | 7 +- .../src/hir/def_collector/errors.rs | 3 +- .../src/hir/def_map/item_scope.rs | 7 +- .../src/hir/def_map/module_data.rs | 7 +- .../src/hir/def_map/namespace.rs | 2 +- .../src/hir/resolution/errors.rs | 4 +- .../src/hir/resolution/impls.rs | 3 +- .../src/hir/resolution/import.rs | 2 +- .../src/hir/resolution/path_resolver.rs | 2 +- .../src/hir/resolution/resolver.rs | 35 +- .../src/hir/resolution/structs.rs | 3 +- .../src/hir/resolution/traits.rs | 3 +- .../src/hir/type_check/errors.rs | 5 +- .../noirc_frontend/src/hir/type_check/expr.rs | 17 +- .../noirc_frontend/src/hir/type_check/mod.rs | 76 +- .../noirc_frontend/src/hir/type_check/stmt.rs | 3 +- .../noirc_frontend/src/hir_def/expr.rs | 9 +- .../noirc_frontend/src/hir_def/function.rs | 8 +- .../noirc_frontend/src/hir_def/stmt.rs | 8 +- .../noirc_frontend/src/hir_def/traits.rs | 3 +- .../noirc_frontend/src/hir_def/types.rs | 55 +- .../noirc_frontend/src/lexer/token.rs | 12 +- .../compiler/noirc_frontend/src/lib.rs | 20 +- .../src/monomorphization/ast.rs | 8 +- .../src/monomorphization/mod.rs | 25 +- .../noirc_frontend/src/node_interner.rs | 12 +- .../noirc_frontend/src/noir_parser.lalrpop | 2 +- .../noirc_frontend/src/parser/errors.rs | 3 +- .../compiler/noirc_frontend/src/parser/mod.rs | 9 +- .../noirc_frontend/src/parser/parser.rs | 97 +- .../src/parser/parser/assertion.rs | 4 +- .../src/parser/parser/function.rs | 26 +- .../src/parser/parser/lambdas.rs | 5 +- .../src/parser/parser/literals.rs | 4 +- .../noirc_frontend/src/parser/parser/path.rs | 2 +- .../src/parser/parser/primitives.rs | 2 +- .../src/parser/parser/structs.rs | 2 +- .../src/parser/parser/traits.rs | 6 +- .../compiler/noirc_frontend/src/tests.rs | 3 +- .../compiler/noirc_printable_type/Cargo.toml | 1 + .../noir-repo/compiler/utils/arena/Cargo.toml | 1 + .../compiler/utils/iter-extended/Cargo.toml | 1 + noir/noir-repo/compiler/wasm/Cargo.toml | 1 + .../docs/noir/concepts/data_types/slices.mdx | 25 + .../standard_library/containers/hashmap.md | 5 +- .../cryptographic_primitives/hashes.mdx | 57 +- .../docs/docs/noir/standard_library/traits.md | 2 + .../docs/docs/tutorials/noirjs_app.md | 89 +- .../getting_started/tooling/index.md | 6 +- .../getting_started/tooling/index.mdx | 6 +- .../getting_started/tooling/index.mdx | 6 +- .../getting_started/tooling/index.mdx | 6 +- .../version-v0.25.0/tutorials/noirjs_app.md | 81 +- .../getting_started/tooling/index.mdx | 6 +- .../version-v0.26.0/tutorials/noirjs_app.md | 81 +- .../getting_started/tooling/index.mdx | 6 +- noir/noir-repo/noir-logo.png | Bin 0 -> 224467 bytes noir/noir-repo/noir_stdlib/src/cmp.nr | 52 + .../src/collections/bounded_vec.nr | 36 + noir/noir-repo/noir_stdlib/src/convert.nr | 6 +- noir/noir-repo/noir_stdlib/src/hash.nr | 81 +- noir/noir-repo/noir_stdlib/src/hash/mimc.nr | 13 +- .../noir_stdlib/src/hash/pedersen.nr | 24 - .../noir_stdlib/src/hash/poseidon.nr | 40 +- .../noir_stdlib/src/hash/poseidon2.nr | 15 +- noir/noir-repo/noir_stdlib/src/slice.nr | 10 + noir/noir-repo/rust-toolchain.toml | 2 +- noir/noir-repo/scripts/benchmark_start.sh | 3 + noir/noir-repo/scripts/benchmark_stop.sh | 3 + .../hashmap_load_factor/src/main.nr | 4 +- .../execution_success/eddsa/src/main.nr | 4 - .../fold_numeric_generic_poseidon/Nargo.toml | 7 + .../fold_numeric_generic_poseidon/Prover.toml | 2 + .../fold_numeric_generic_poseidon/src/main.nr | 33 + .../execution_success/hashmap/src/main.nr | 26 +- .../regression_sha256_slice/Nargo.toml | 7 - .../regression_sha256_slice/Prover.toml | 1 - .../regression_sha256_slice/src/main.nr | 12 - .../tooling/backend_interface/Cargo.toml | 2 +- .../tooling/backend_interface/src/cli/info.rs | 2 +- .../tooling/bb_abstraction_leaks/build.rs | 2 +- noir/noir-repo/tooling/debugger/Cargo.toml | 1 + .../tooling/debugger/ignored-tests.txt | 3 + noir/noir-repo/tooling/lsp/Cargo.toml | 3 +- noir/noir-repo/tooling/nargo/Cargo.toml | 4 +- noir/noir-repo/tooling/nargo/build.rs | 12 - noir/noir-repo/tooling/nargo_cli/Cargo.toml | 10 +- noir/noir-repo/tooling/nargo_cli/build.rs | 10 - noir/noir-repo/tooling/nargo_fmt/Cargo.toml | 1 + .../tooling/nargo_fmt/src/rewrite/array.rs | 2 +- .../tooling/nargo_fmt/src/rewrite/expr.rs | 6 +- .../tooling/nargo_fmt/src/rewrite/imports.rs | 14 +- .../tooling/nargo_fmt/src/rewrite/infix.rs | 2 +- .../nargo_fmt/src/rewrite/parenthesized.rs | 3 +- .../tooling/nargo_fmt/src/rewrite/typ.rs | 2 +- noir/noir-repo/tooling/nargo_fmt/src/utils.rs | 2 +- .../tooling/nargo_fmt/src/visitor/expr.rs | 7 +- .../tooling/nargo_fmt/src/visitor/item.rs | 14 +- .../tooling/nargo_fmt/src/visitor/stmt.rs | 168 +-- noir/noir-repo/tooling/nargo_toml/Cargo.toml | 1 + .../noir_js_backend_barretenberg/package.json | 2 +- noir/noir-repo/tooling/noirc_abi/Cargo.toml | 1 + noir/noir-repo/tooling/noirc_abi/src/lib.rs | 3 +- noir/noir-repo/yarn.lock | 13 +- 157 files changed, 3198 insertions(+), 969 deletions(-) create mode 100644 noir/noir-repo/acvm-repo/acir/benches/serialization.rs create mode 100644 noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs create mode 100644 noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs create mode 100644 noir/noir-repo/noir-logo.png delete mode 100644 noir/noir-repo/noir_stdlib/src/hash/pedersen.nr create mode 100755 noir/noir-repo/scripts/benchmark_start.sh create mode 100755 noir/noir-repo/scripts/benchmark_stop.sh create mode 100644 noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr delete mode 100644 noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Nargo.toml delete mode 100644 noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Prover.toml delete mode 100644 noir/noir-repo/test_programs/execution_success/regression_sha256_slice/src/main.nr delete mode 100644 noir/noir-repo/tooling/nargo/build.rs diff --git a/.noir-sync-commit b/.noir-sync-commit index 8f6aafb96d3..2a6270fb774 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -6cc105ee441e093b4fccdd5fcc3db922eb28a3fb +9704bd0abfe2dba1e7a4aef6cdb6cc83d70b929e diff --git a/noir/noir-repo/.github/workflows/docs-pr.yml b/noir/noir-repo/.github/workflows/docs-pr.yml index 1f9ccdd946b..945ed555639 100644 --- a/noir/noir-repo/.github/workflows/docs-pr.yml +++ b/noir/noir-repo/.github/workflows/docs-pr.yml @@ -55,7 +55,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: diff --git a/noir/noir-repo/.github/workflows/formatting.yml b/noir/noir-repo/.github/workflows/formatting.yml index 279e90f5f6f..8166fb0f7c2 100644 --- a/noir/noir-repo/.github/workflows/formatting.yml +++ b/noir/noir-repo/.github/workflows/formatting.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: ${{ matrix.target }} components: clippy, rustfmt @@ -73,7 +73,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: diff --git a/noir/noir-repo/.github/workflows/gates_report.yml b/noir/noir-repo/.github/workflows/gates_report.yml index be55236c40f..e694e5fad04 100644 --- a/noir/noir-repo/.github/workflows/gates_report.yml +++ b/noir/noir-repo/.github/workflows/gates_report.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: diff --git a/noir/noir-repo/.github/workflows/publish-acvm.yml b/noir/noir-repo/.github/workflows/publish-acvm.yml index d17d8f294fb..feb4d4216c3 100644 --- a/noir/noir-repo/.github/workflows/publish-acvm.yml +++ b/noir/noir-repo/.github/workflows/publish-acvm.yml @@ -18,7 +18,7 @@ jobs: ref: ${{ inputs.noir-ref }} - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 # These steps are in a specific order so crate dependencies are updated first - name: Publish acir_field diff --git a/noir/noir-repo/.github/workflows/publish-es-packages.yml b/noir/noir-repo/.github/workflows/publish-es-packages.yml index 819be308169..682fed69c7b 100644 --- a/noir/noir-repo/.github/workflows/publish-es-packages.yml +++ b/noir/noir-repo/.github/workflows/publish-es-packages.yml @@ -24,7 +24,7 @@ jobs: ref: ${{ inputs.noir-ref }} - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: @@ -58,7 +58,7 @@ jobs: ref: ${{ inputs.noir-ref }} - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: @@ -95,7 +95,7 @@ jobs: ref: ${{ inputs.noir-ref }} - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: diff --git a/noir/noir-repo/.github/workflows/publish-nargo.yml b/noir/noir-repo/.github/workflows/publish-nargo.yml index e47e1a13053..85010503b00 100644 --- a/noir/noir-repo/.github/workflows/publish-nargo.yml +++ b/noir/noir-repo/.github/workflows/publish-nargo.yml @@ -46,7 +46,7 @@ jobs: echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx$(sw_vers -productVersion) --show-sdk-platform-version)" >> $GITHUB_ENV - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: ${{ matrix.target }} @@ -120,7 +120,7 @@ jobs: ref: ${{ inputs.tag || env.GITHUB_REF }} - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: ${{ matrix.target }} diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index 06a96ee8932..7fd198d9a89 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: @@ -55,7 +55,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: @@ -88,7 +88,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: @@ -123,7 +123,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 - uses: Swatinem/rust-cache@v2 with: diff --git a/noir/noir-repo/.github/workflows/test-rust-workspace-msrv.yml b/noir/noir-repo/.github/workflows/test-rust-workspace-msrv.yml index cdd7a064a8d..c13b0815d94 100644 --- a/noir/noir-repo/.github/workflows/test-rust-workspace-msrv.yml +++ b/noir/noir-repo/.github/workflows/test-rust-workspace-msrv.yml @@ -29,7 +29,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: x86_64-unknown-linux-gnu @@ -72,7 +72,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: x86_64-unknown-linux-gnu diff --git a/noir/noir-repo/.github/workflows/test-rust-workspace.yml b/noir/noir-repo/.github/workflows/test-rust-workspace.yml index 22684de3044..cf35950fa3e 100644 --- a/noir/noir-repo/.github/workflows/test-rust-workspace.yml +++ b/noir/noir-repo/.github/workflows/test-rust-workspace.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: x86_64-unknown-linux-gnu @@ -59,7 +59,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup toolchain - uses: dtolnay/rust-toolchain@1.73.0 + uses: dtolnay/rust-toolchain@1.74.1 with: targets: x86_64-unknown-linux-gnu diff --git a/noir/noir-repo/CHANGELOG.md b/noir/noir-repo/CHANGELOG.md index 0ab84df44e7..a084811fc21 100644 --- a/noir/noir-repo/CHANGELOG.md +++ b/noir/noir-repo/CHANGELOG.md @@ -109,7 +109,7 @@ * reserve `unchecked` keyword ([#4432](https://github.com/noir-lang/noir/issues/4432)) * Remove empty value from bounded vec ([#4431](https://github.com/noir-lang/noir/issues/4431)) * Ban Fields in for loop indices and bitwise ops ([#4376](https://github.com/noir-lang/noir/issues/4376)) -* bump msrv to 1.73.0 ([#4406](https://github.com/noir-lang/noir/issues/4406)) +* Bump msrv to 1.73.0 ([#4406](https://github.com/noir-lang/noir/issues/4406)) * **ci:** Bump MSRV to 1.72.1 and enforce that ACVM can be published using updated lockfile ([#4385](https://github.com/noir-lang/noir/issues/4385)) * Restrict bit sizes ([#4235](https://github.com/noir-lang/noir/issues/4235)) * move noir out of yarn-project (https://github.com/AztecProtocol/aztec-packages/pull/4479) diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index ee83f7f8ddf..35702c8df03 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -10,8 +10,10 @@ dependencies = [ "base64 0.21.2", "bincode", "brillig", + "criterion", "flate2", "fxhash", + "pprof 0.13.0", "serde", "serde-big-array", "serde-generate", @@ -454,7 +456,7 @@ dependencies = [ [[package]] name = "backend-interface" -version = "0.11.0" +version = "0.27.0" dependencies = [ "acvm", "bb_abstraction_leaks", @@ -624,7 +626,7 @@ dependencies = [ "lazy_static", "noir_grumpkin", "num-bigint", - "pprof", + "pprof 0.12.1", "thiserror", "wasm-bindgen-futures", "wasmer", @@ -2825,7 +2827,6 @@ dependencies = [ "noirc_frontend", "noirc_printable_type", "rayon", - "rustc_version", "serde", "tempfile", "thiserror", @@ -2866,11 +2867,10 @@ dependencies = [ "notify", "notify-debouncer-full", "paste", - "pprof", + "pprof 0.13.0", "predicates 2.1.5", "prettytable-rs", "rayon", - "rustc_version", "serde", "serde_json", "similar-asserts", @@ -3161,6 +3161,7 @@ dependencies = [ "base64 0.21.2", "chumsky", "fm", + "im", "iter-extended", "lalrpop", "lalrpop-util", @@ -3547,6 +3548,28 @@ dependencies = [ "thiserror", ] +[[package]] +name = "pprof" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5c97c51bd34c7e742402e216abdeb44d415fbe6ae41d56b114723e953711cb" +dependencies = [ + "backtrace", + "cfg-if 1.0.0", + "criterion", + "findshlibs", + "inferno", + "libc", + "log", + "nix 0.26.4", + "once_cell", + "parking_lot 0.12.1", + "smallvec", + "symbolic-demangle", + "tempfile", + "thiserror", +] + [[package]] name = "ppv-lite86" version = "0.2.17" diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 5dd453415aa..132ff181e44 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -45,7 +45,7 @@ version = "0.27.0" # x-release-please-end authors = ["The Noir Team "] edition = "2021" -rust-version = "1.73.0" +rust-version = "1.74.1" license = "MIT OR Apache-2.0" repository = "https://github.com/noir-lang/noir/" @@ -104,6 +104,14 @@ chumsky = { git = "https://github.com/jfecher/chumsky", rev = "ad9d312", default "ahash", "std", ] } + +# Benchmarking +criterion = "0.5.0" +# Note that using the "frame-pointer" feature breaks framegraphs on linux +# https://github.com/tikv/pprof-rs/pull/172 +pprof = { version = "0.13", features = ["flamegraph","criterion"] } + + dirs = "4" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0" @@ -124,6 +132,7 @@ tempfile = "3.6.0" jsonrpc = { version = "0.16.0", features = ["minreq_http"] } flate2 = "1.0.24" +im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" tracing-web = "0.1.3" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } diff --git a/noir/noir-repo/README.md b/noir/noir-repo/README.md index adf68b290ef..eea2280c50c 100644 --- a/noir/noir-repo/README.md +++ b/noir/noir-repo/README.md @@ -1,3 +1,13 @@ +
+ + The Noir Programming Language + + +[Website][Noir] | [Getting started] | [Documentation] | [Contributing] +
+ + + # The Noir Programming Language Noir is a Domain Specific Language for SNARK proving systems. It has been designed to use any ACIR compatible proving system. @@ -6,58 +16,35 @@ Noir is a Domain Specific Language for SNARK proving systems. It has been design ## Quick Start -Read the installation section [here](https://noir-lang.org/docs/dev/getting_started/installation/). +Read the [installation section][Getting started] from the [Noir docs][Documentation]. Once you have read through the documentation, you can visit [Awesome Noir](https://github.com/noir-lang/awesome-noir) to run some of the examples that others have created. -## Current Features - -Backends: +## Getting Help -- Barretenberg via FFI -- Marlin via arkworks (Note -- latest interfaces may not be updated to support Marlin backend. Please open an issue if this is relevant to your project and requires attention.) +Join the Noir [forum][Forum] or [Discord][Discord] -Compiler: +## Contributing -- Module System -- For expressions -- Arrays -- Bit Operations -- Binary operations (<, <=, >, >=, +, -, \*, /, %) [See documentation for an extensive list] -- Unsigned integers -- If statements -- Structures and Tuples -- Generics - -ACIR Supported OPCODES: - -- Sha256 -- Blake2s -- Schnorr signature verification -- Pedersen -- HashToField +See [CONTRIBUTING.md][CONTRIBUTING]. ## Future Work The current focus is to gather as much feedback as possible while in the alpha phase. The main focuses of Noir are _safety_ and _developer experience_. If you find a feature that does not seem to be in line with these goals, please open an issue! -Concretely the following items are on the road map: - -- General code sanitization and documentation (ongoing effort) -- Prover and Verifier Key logic. (Prover and Verifier pre-process per compile) -- Fallback mechanism for backend unsupported opcodes -- Visibility modifiers -- Signed integers -- Backend integration: (Bulletproofs) -- Recursion -- Big integers - ## Minimum Rust version -This crate's minimum supported rustc version is 1.73.0. +This workspace's minimum supported rustc version is 1.74.1. ## License Noir is free and open source. It is distributed under a dual license. (MIT/APACHE) -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this repository by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. + +[Noir]: https://www.noir-lang.org/ +[Getting Started]: https://noir-lang.org/docs/getting_started/installation/ +[Forum]: https://forum.aztec.network/c/noir +[Discord]: https://discord.gg/JtqzkdeQ6G +[Documentation]: https://noir-lang.org/docs +[Contributing]: CONTRIBUTING.md \ No newline at end of file diff --git a/noir/noir-repo/acvm-repo/acir/Cargo.toml b/noir/noir-repo/acvm-repo/acir/Cargo.toml index 4fae9ea20ff..d6990f83281 100644 --- a/noir/noir-repo/acvm-repo/acir/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acir/Cargo.toml @@ -29,8 +29,14 @@ strum_macros = "0.24" serde-reflection = "0.3.6" serde-generate = "0.25.1" fxhash.workspace = true +criterion.workspace = true +pprof.workspace = true [features] default = ["bn254"] bn254 = ["acir_field/bn254", "brillig/bn254"] bls12_381 = ["acir_field/bls12_381", "brillig/bls12_381"] + +[[bench]] +name = "serialization" +harness = false diff --git a/noir/noir-repo/acvm-repo/acir/benches/serialization.rs b/noir/noir-repo/acvm-repo/acir/benches/serialization.rs new file mode 100644 index 00000000000..73e3916a73b --- /dev/null +++ b/noir/noir-repo/acvm-repo/acir/benches/serialization.rs @@ -0,0 +1,123 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use std::{collections::BTreeSet, time::Duration}; + +use acir::{ + circuit::{Circuit, ExpressionWidth, Opcode, Program, PublicInputs}, + native_types::{Expression, Witness}, + FieldElement, +}; + +use pprof::criterion::{Output, PProfProfiler}; + +const SIZES: [usize; 9] = [10, 50, 100, 500, 1000, 5000, 10000, 50000, 100000]; + +fn sample_program(num_opcodes: usize) -> Program { + let assert_zero_opcodes: Vec = (0..num_opcodes) + .map(|i| { + Opcode::AssertZero(Expression { + mul_terms: vec![( + FieldElement::from(2 * i), + Witness(i as u32), + Witness(i as u32 + 10), + )], + linear_combinations: vec![ + (FieldElement::from(2 * i), Witness(i as u32)), + (FieldElement::from(3 * i), Witness(i as u32 + 1)), + ], + q_c: FieldElement::from(i), + }) + }) + .collect(); + + Program { + functions: vec![Circuit { + current_witness_index: 4000, + opcodes: assert_zero_opcodes.to_vec(), + expression_width: ExpressionWidth::Bounded { width: 3 }, + private_parameters: BTreeSet::from([Witness(1), Witness(2), Witness(3), Witness(4)]), + public_parameters: PublicInputs(BTreeSet::from([Witness(5)])), + return_values: PublicInputs(BTreeSet::from([Witness(6)])), + assert_messages: Vec::new(), + recursive: false, + }], + } +} + +fn bench_serialization(c: &mut Criterion) { + let mut group = c.benchmark_group("serialize_program"); + for size in SIZES.iter() { + let program = sample_program(*size); + + group.throughput(Throughput::Elements(*size as u64)); + group.bench_with_input(BenchmarkId::from_parameter(size), &program, |b, program| { + b.iter(|| Program::serialize_program(program)); + }); + } + group.finish(); + + let mut group = c.benchmark_group("serialize_program_json"); + for size in SIZES.iter() { + let program = sample_program(*size); + + group.throughput(Throughput::Elements(*size as u64)); + group.bench_with_input(BenchmarkId::from_parameter(size), &program, |b, program| { + b.iter(|| { + let mut bytes = Vec::new(); + let mut serializer = serde_json::Serializer::new(&mut bytes); + Program::serialize_program_base64(program, &mut serializer) + }); + }); + } + group.finish(); +} + +fn bench_deserialization(c: &mut Criterion) { + let mut group = c.benchmark_group("deserialize_program"); + for size in SIZES.iter() { + let program = sample_program(*size); + let serialized_program = Program::serialize_program(&program); + + group.throughput(Throughput::Elements(*size as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(size), + &serialized_program, + |b, program| { + b.iter(|| Program::deserialize_program(program)); + }, + ); + } + group.finish(); + + let mut group = c.benchmark_group("deserialize_program_json"); + for size in SIZES.iter() { + let program = sample_program(*size); + + let serialized_program = { + let mut bytes = Vec::new(); + let mut serializer = serde_json::Serializer::new(&mut bytes); + Program::serialize_program_base64(&program, &mut serializer).expect("should succeed"); + bytes + }; + + group.throughput(Throughput::Elements(*size as u64)); + group.bench_with_input( + BenchmarkId::from_parameter(size), + &serialized_program, + |b, program| { + b.iter(|| { + let mut deserializer = serde_json::Deserializer::from_slice(program); + Program::deserialize_program_base64(&mut deserializer) + }); + }, + ); + } + group.finish(); +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(40).measurement_time(Duration::from_secs(20)).with_profiler(PProfProfiler::new(100, Output::Flamegraph(None))); + targets = bench_serialization, bench_deserialization +); + +criterion_main!(benches); diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs index e75d335d52b..7f87aabf9d5 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/brillig.rs @@ -33,7 +33,7 @@ pub struct Brillig { /// This is purely a wrapper struct around a list of Brillig opcode's which represents /// a full Brillig function to be executed by the Brillig VM. /// This is stored separately on a program and accessed through a [BrilligPointer]. -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default, Debug)] pub struct BrilligBytecode { pub bytecode: Vec, } diff --git a/noir/noir-repo/acvm-repo/acvm_js/build.sh b/noir/noir-repo/acvm-repo/acvm_js/build.sh index 58724dee02c..4486a214c9c 100755 --- a/noir/noir-repo/acvm-repo/acvm_js/build.sh +++ b/noir/noir-repo/acvm-repo/acvm_js/build.sh @@ -25,6 +25,7 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') @@ -48,5 +49,5 @@ BROWSER_WASM=${BROWSER_DIR}/${pname}_bg.wasm run_or_fail cargo build --lib --release --target $TARGET --package ${pname} run_or_fail wasm-bindgen $WASM_BINARY --out-dir $NODE_DIR --typescript --target nodejs run_or_fail wasm-bindgen $WASM_BINARY --out-dir $BROWSER_DIR --typescript --target web -run_if_available wasm-opt $NODE_WASM -o $NODE_WASM -O -run_if_available wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O +run_or_fail wasm-opt $NODE_WASM -o $NODE_WASM -O +run_or_fail wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O diff --git a/noir/noir-repo/acvm-repo/acvm_js/test/browser/black_box_solvers.test.ts b/noir/noir-repo/acvm-repo/acvm_js/test/browser/black_box_solvers.test.ts index 3c54fe8e38f..695f6b89afc 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/test/browser/black_box_solvers.test.ts +++ b/noir/noir-repo/acvm-repo/acvm_js/test/browser/black_box_solvers.test.ts @@ -4,7 +4,6 @@ import initACVM, { blake2s256, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify, - initLogLevel, keccak256, sha256, xor, @@ -12,8 +11,6 @@ import initACVM, { beforeEach(async () => { await initACVM(); - - initLogLevel('INFO'); }); it('successfully calculates the bitwise AND of two fields', async () => { diff --git a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs index 4ff97a5dcae..70d05e5c59e 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs @@ -1,8 +1,9 @@ use noirc_errors::{Location, Span}; +use noirc_frontend::ast::{FunctionReturnType, NoirFunction, UnresolvedTypeData}; use noirc_frontend::{ graph::CrateId, macros_api::{FileId, HirContext}, - parse_program, FunctionReturnType, NoirFunction, Type, UnresolvedTypeData, + parse_program, Type, }; use crate::utils::{ diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index 4401c867df9..5f68ce98c8a 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -1,9 +1,10 @@ +use noirc_frontend::ast::{NoirFunction, UnresolvedTypeData}; use noirc_frontend::{ graph::CrateId, macros_api::{FileId, HirContext, HirExpression, HirLiteral, HirStatement}, parse_program, parser::SortedModule, - NoirFunction, Type, UnresolvedTypeData, + Type, }; use crate::utils::{ @@ -52,7 +53,7 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction) -> String { }) .collect::>() .join(", "); - let fn_return_type: noirc_frontend::UnresolvedType = func.return_type(); + let fn_return_type: noirc_frontend::ast::UnresolvedType = func.return_type(); let fn_selector = format!("dep::aztec::protocol_types::abis::function_selector::FunctionSelector::from_signature(\"{}\")", SELECTOR_PLACEHOLDER); diff --git a/noir/noir-repo/aztec_macros/src/transforms/events.rs b/noir/noir-repo/aztec_macros/src/transforms/events.rs index b77a5821b81..9c8cd826360 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/events.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/events.rs @@ -1,5 +1,9 @@ use iter_extended::vecmap; use noirc_errors::Span; +use noirc_frontend::ast::{ + ExpressionKind, FunctionDefinition, FunctionReturnType, ItemVisibility, Literal, NoirFunction, + Visibility, +}; use noirc_frontend::{ graph::CrateId, macros_api::{ @@ -8,8 +12,6 @@ use noirc_frontend::{ UnresolvedTypeData, }, token::SecondaryAttribute, - ExpressionKind, FunctionDefinition, FunctionReturnType, ItemVisibility, Literal, NoirFunction, - Visibility, }; use crate::{ diff --git a/noir/noir-repo/aztec_macros/src/transforms/functions.rs b/noir/noir-repo/aztec_macros/src/transforms/functions.rs index 534d24289b7..dd7a416b941 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/functions.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/functions.rs @@ -1,12 +1,15 @@ use convert_case::{Case, Casing}; use noirc_errors::Span; -use noirc_frontend::{ - macros_api::FieldElement, parse_program, BlockExpression, ConstrainKind, ConstrainStatement, - Distinctness, Expression, ExpressionKind, ForLoopStatement, ForRange, FunctionReturnType, - Ident, Literal, NoirFunction, NoirStruct, Param, PathKind, Pattern, Signedness, Statement, - StatementKind, UnresolvedType, UnresolvedTypeData, Visibility, +use noirc_frontend::ast; +use noirc_frontend::ast::{ + BlockExpression, ConstrainKind, ConstrainStatement, Distinctness, Expression, ExpressionKind, + ForLoopStatement, ForRange, FunctionReturnType, Ident, Literal, NoirFunction, NoirStruct, + Param, PathKind, Pattern, Signedness, Statement, StatementKind, UnresolvedType, + UnresolvedTypeData, Visibility, }; +use noirc_frontend::{macros_api::FieldElement, parse_program}; + use crate::{ chained_dep, chained_path, utils::{ @@ -334,7 +337,7 @@ fn serialize_to_hasher( &UnresolvedType { typ: UnresolvedTypeData::Integer( Signedness::Unsigned, - noirc_frontend::IntegerBitSize::ThirtyTwo, + ast::IntegerBitSize::ThirtyTwo, ), span: None, }, diff --git a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs index 4b72759a5db..70db1ebd336 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/note_interface.rs @@ -1,12 +1,16 @@ use noirc_errors::Span; +use noirc_frontend::ast::{ + ItemVisibility, LetStatement, NoirFunction, NoirStruct, PathKind, TraitImplItem, TypeImpl, + UnresolvedTypeData, UnresolvedTypeExpression, +}; use noirc_frontend::{ graph::CrateId, macros_api::{FileId, HirContext, HirExpression, HirLiteral, HirStatement}, parse_program, parser::SortedModule, - ItemVisibility, LetStatement, NoirFunction, NoirStruct, PathKind, TraitImplItem, Type, - TypeImpl, UnresolvedTypeData, UnresolvedTypeExpression, + Type, }; + use regex::Regex; use crate::{ diff --git a/noir/noir-repo/aztec_macros/src/transforms/storage.rs b/noir/noir-repo/aztec_macros/src/transforms/storage.rs index 9135be32443..49fcee9fe29 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/storage.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/storage.rs @@ -1,6 +1,10 @@ use std::borrow::Borrow; use noirc_errors::Span; +use noirc_frontend::ast::{ + BlockExpression, Expression, ExpressionKind, FunctionDefinition, Ident, Literal, NoirFunction, + NoirStruct, PathKind, Pattern, StatementKind, TypeImpl, UnresolvedType, UnresolvedTypeData, +}; use noirc_frontend::{ graph::CrateId, macros_api::{ @@ -10,9 +14,7 @@ use noirc_frontend::{ parse_program, parser::SortedModule, token::SecondaryAttribute, - BlockExpression, Expression, ExpressionKind, FunctionDefinition, Ident, Literal, NoirFunction, - NoirStruct, PathKind, Pattern, StatementKind, Type, TypeImpl, UnresolvedType, - UnresolvedTypeData, + Type, }; use crate::{ diff --git a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs index 1731dfab49c..ebb4854f86e 100644 --- a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs @@ -1,10 +1,11 @@ use noirc_errors::{Span, Spanned}; -use noirc_frontend::{ - token::SecondaryAttribute, BinaryOpKind, CallExpression, CastExpression, Expression, - ExpressionKind, FunctionReturnType, Ident, IndexExpression, InfixExpression, Lambda, - LetStatement, MethodCallExpression, NoirTraitImpl, Path, Pattern, PrefixExpression, Statement, - StatementKind, TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData, +use noirc_frontend::ast::{ + BinaryOpKind, CallExpression, CastExpression, Expression, ExpressionKind, FunctionReturnType, + Ident, IndexExpression, InfixExpression, Lambda, LetStatement, MethodCallExpression, + NoirTraitImpl, Path, Pattern, PrefixExpression, Statement, StatementKind, TraitImplItem, + UnaryOp, UnresolvedType, UnresolvedTypeData, }; +use noirc_frontend::token::SecondaryAttribute; // // Helper macros for creating noir ast nodes @@ -66,6 +67,7 @@ pub fn mutable_assignment(name: &str, assigned_to: Expression) -> Statement { pattern: mutable(name), r#type: make_type(UnresolvedTypeData::Unspecified), expression: assigned_to, + comptime: false, attributes: vec![], })) } @@ -90,6 +92,7 @@ pub fn assignment_with_type( pattern: pattern(name), r#type: make_type(typ), expression: assigned_to, + comptime: false, attributes: vec![], })) } diff --git a/noir/noir-repo/aztec_macros/src/utils/errors.rs b/noir/noir-repo/aztec_macros/src/utils/errors.rs index 4c5411dfe0f..db86012a007 100644 --- a/noir/noir-repo/aztec_macros/src/utils/errors.rs +++ b/noir/noir-repo/aztec_macros/src/utils/errors.rs @@ -1,5 +1,6 @@ use noirc_errors::Span; -use noirc_frontend::{macros_api::MacroError, UnresolvedTypeData}; +use noirc_frontend::ast; +use noirc_frontend::macros_api::MacroError; use super::constants::MAX_CONTRACT_PRIVATE_FUNCTIONS; @@ -7,9 +8,9 @@ use super::constants::MAX_CONTRACT_PRIVATE_FUNCTIONS; pub enum AztecMacroError { AztecDepNotFound, ContractHasTooManyPrivateFunctions { span: Span }, - UnsupportedFunctionArgumentType { span: Span, typ: UnresolvedTypeData }, - UnsupportedFunctionReturnType { span: Span, typ: UnresolvedTypeData }, - UnsupportedStorageType { span: Option, typ: UnresolvedTypeData }, + UnsupportedFunctionArgumentType { span: Span, typ: ast::UnresolvedTypeData }, + UnsupportedFunctionReturnType { span: Span, typ: ast::UnresolvedTypeData }, + UnsupportedStorageType { span: Option, typ: ast::UnresolvedTypeData }, CouldNotAssignStorageSlots { secondary_message: Option }, CouldNotImplementComputeNoteHashAndNullifier { secondary_message: Option }, CouldNotImplementNoteInterface { span: Option, secondary_message: Option }, diff --git a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs index ae895d2075c..cafa6ed38ca 100644 --- a/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/hir_utils.rs @@ -1,5 +1,6 @@ use iter_extended::vecmap; use noirc_errors::Location; +use noirc_frontend::ast; use noirc_frontend::{ graph::CrateId, hir::{ @@ -9,7 +10,7 @@ use noirc_frontend::{ }, macros_api::{FileId, HirContext, MacroError, ModuleDefId, StructId}, node_interner::{FuncId, TraitId}, - ItemVisibility, LetStatement, NoirFunction, Shared, Signedness, StructType, Type, + Shared, StructType, Type, }; use super::ast_utils::is_custom_attribute; @@ -65,8 +66,8 @@ pub fn collect_traits(context: &HirContext) -> Vec { /// Computes the aztec signature for a resolved type. pub fn signature_of_type(typ: &Type) -> String { match typ { - Type::Integer(Signedness::Signed, bit_size) => format!("i{}", bit_size), - Type::Integer(Signedness::Unsigned, bit_size) => format!("u{}", bit_size), + Type::Integer(ast::Signedness::Signed, bit_size) => format!("i{}", bit_size), + Type::Integer(ast::Signedness::Unsigned, bit_size) => format!("u{}", bit_size), Type::FieldElement => "Field".to_owned(), Type::Bool => "bool".to_owned(), Type::Array(len, typ) => { @@ -165,7 +166,7 @@ pub fn get_contract_module_data( pub fn inject_fn( crate_id: &CrateId, context: &mut HirContext, - func: NoirFunction, + func: ast::NoirFunction, location: Location, module_id: LocalModuleId, file_id: FileId, @@ -179,7 +180,7 @@ pub fn inject_fn( ); context.def_map_mut(crate_id).unwrap().modules_mut()[module_id.0] - .declare_function(func.name_ident().clone(), ItemVisibility::Public, func_id) + .declare_function(func.name_ident().clone(), ast::ItemVisibility::Public, func_id) .map_err(|err| MacroError { primary_message: format!("Failed to declare autogenerated {} function", func.name()), secondary_message: Some(format!("Duplicate definition found {}", err.0)), @@ -214,7 +215,7 @@ pub fn inject_fn( pub fn inject_global( crate_id: &CrateId, context: &mut HirContext, - global: LetStatement, + global: ast::LetStatement, module_id: LocalModuleId, file_id: FileId, ) { diff --git a/noir/noir-repo/compiler/fm/Cargo.toml b/noir/noir-repo/compiler/fm/Cargo.toml index 42e4b0c25d7..f556f305c78 100644 --- a/noir/noir-repo/compiler/fm/Cargo.toml +++ b/noir/noir-repo/compiler/fm/Cargo.toml @@ -3,6 +3,7 @@ name = "fm" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/compiler/noirc_driver/Cargo.toml b/noir/noir-repo/compiler/noirc_driver/Cargo.toml index a7fe0b4b610..495410b7b06 100644 --- a/noir/noir-repo/compiler/noirc_driver/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_driver/Cargo.toml @@ -3,6 +3,7 @@ name = "noirc_driver" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs index 86f10818dbc..51fe4986845 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/abi_gen.rs @@ -3,12 +3,12 @@ use std::collections::BTreeMap; use acvm::acir::native_types::Witness; use iter_extended::{btree_map, vecmap}; use noirc_abi::{Abi, AbiParameter, AbiReturnType, AbiType, AbiValue}; +use noirc_frontend::ast::Visibility; use noirc_frontend::{ hir::Context, hir_def::{expr::HirArrayLiteral, function::Param, stmt::HirPattern}, macros_api::{HirExpression, HirLiteral}, node_interner::{FuncId, NodeInterner}, - Visibility, }; use std::ops::Range; diff --git a/noir/noir-repo/compiler/noirc_errors/Cargo.toml b/noir/noir-repo/compiler/noirc_errors/Cargo.toml index da18399971e..c9cb7e2709f 100644 --- a/noir/noir-repo/compiler/noirc_errors/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_errors/Cargo.toml @@ -3,6 +3,7 @@ name = "noirc_errors" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml index fad7c3c309e..aa30eef9156 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml @@ -3,6 +3,7 @@ name = "noirc_evaluator" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,7 +16,7 @@ fxhash.workspace = true iter-extended.workspace = true thiserror.workspace = true num-bigint = "0.4" -im = { version = "15.1", features = ["serde"] } +im.workspace = true serde.workspace = true tracing.workspace = true -chrono = "0.4.37" \ No newline at end of file +chrono = "0.4.37" diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index 760340f1a88..3482a8c25c7 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -22,9 +22,8 @@ use acvm::acir::{ use noirc_errors::debug_info::{DebugFunctions, DebugInfo, DebugTypes, DebugVariables}; -use noirc_frontend::{ - hir_def::function::FunctionSignature, monomorphization::ast::Program, Visibility, -}; +use noirc_frontend::ast::Visibility; +use noirc_frontend::{hir_def::function::FunctionSignature, monomorphization::ast::Program}; use tracing::{span, Level}; use self::{acir_gen::GeneratedAcir, ssa_gen::Ssa}; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index b94e02e5119..3f5e4129dd0 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -1,5 +1,5 @@ use super::big_int::BigIntContext; -use super::generated_acir::GeneratedAcir; +use super::generated_acir::{BrilligStdlibFunc, GeneratedAcir, PLACEHOLDER_BRILLIG_INDEX}; use crate::brillig::brillig_gen::brillig_directive; use crate::brillig::brillig_ir::artifact::GeneratedBrillig; use crate::errors::{InternalError, RuntimeError, SsaReport}; @@ -326,13 +326,15 @@ impl AcirContext { // Compute the inverse with brillig code let inverse_code = brillig_directive::directive_invert(); - let results = self.brillig( + let results = self.brillig_call( predicate, - inverse_code, + &inverse_code, vec![AcirValue::Var(var, AcirType::field())], vec![AcirType::field()], true, false, + PLACEHOLDER_BRILLIG_INDEX, + Some(BrilligStdlibFunc::Inverse), )?; let inverted_var = Self::expect_one_var(results); @@ -711,9 +713,9 @@ impl AcirContext { } let [q_value, r_value]: [AcirValue; 2] = self - .brillig( + .brillig_call( predicate, - brillig_directive::directive_quotient(bit_size + 1), + &brillig_directive::directive_quotient(bit_size + 1), vec![ AcirValue::Var(lhs, AcirType::unsigned(bit_size)), AcirValue::Var(rhs, AcirType::unsigned(bit_size)), @@ -721,6 +723,8 @@ impl AcirContext { vec![AcirType::unsigned(max_q_bits), AcirType::unsigned(max_rhs_bits)], true, false, + PLACEHOLDER_BRILLIG_INDEX, + Some(BrilligStdlibFunc::Quotient(bit_size + 1)), )? .try_into() .expect("quotient only returns two values"); @@ -1464,97 +1468,6 @@ impl AcirContext { id } - // TODO: Delete this method once we remove the `Brillig` opcode - pub(crate) fn brillig( - &mut self, - predicate: AcirVar, - generated_brillig: GeneratedBrillig, - inputs: Vec, - outputs: Vec, - attempt_execution: bool, - unsafe_return_values: bool, - ) -> Result, RuntimeError> { - let b_inputs = try_vecmap(inputs, |i| -> Result<_, InternalError> { - match i { - AcirValue::Var(var, _) => Ok(BrilligInputs::Single(self.var_to_expression(var)?)), - AcirValue::Array(vars) => { - let mut var_expressions: Vec = Vec::new(); - for var in vars { - self.brillig_array_input(&mut var_expressions, var)?; - } - Ok(BrilligInputs::Array(var_expressions)) - } - AcirValue::DynamicArray(AcirDynamicArray { block_id, .. }) => { - Ok(BrilligInputs::MemoryArray(block_id)) - } - } - })?; - - // Optimistically try executing the brillig now, if we can complete execution they just return the results. - // This is a temporary measure pending SSA optimizations being applied to Brillig which would remove constant-input opcodes (See #2066) - // - // We do _not_ want to do this in the situation where the `main` function is unconstrained, as if execution succeeds - // the entire program will be replaced with witness constraints to its outputs. - if attempt_execution { - if let Some(brillig_outputs) = - self.execute_brillig(&generated_brillig.byte_code, &b_inputs, &outputs) - { - return Ok(brillig_outputs); - } - } - - // Otherwise we must generate ACIR for it and execute at runtime. - let mut b_outputs = Vec::new(); - let outputs_var = vecmap(outputs, |output| match output { - AcirType::NumericType(_) => { - let witness_index = self.acir_ir.next_witness_index(); - b_outputs.push(BrilligOutputs::Simple(witness_index)); - let var = self.add_data(AcirVarData::Witness(witness_index)); - AcirValue::Var(var, output.clone()) - } - AcirType::Array(element_types, size) => { - let (acir_value, witnesses) = self.brillig_array_output(&element_types, size); - b_outputs.push(BrilligOutputs::Array(witnesses)); - acir_value - } - }); - let predicate = self.var_to_expression(predicate)?; - self.acir_ir.brillig(Some(predicate), generated_brillig, b_inputs, b_outputs); - - fn range_constraint_value( - context: &mut AcirContext, - value: &AcirValue, - ) -> Result<(), RuntimeError> { - match value { - AcirValue::Var(var, typ) => { - let numeric_type = match typ { - AcirType::NumericType(numeric_type) => numeric_type, - _ => unreachable!("`AcirValue::Var` may only hold primitive values"), - }; - context.range_constrain_var(*var, numeric_type, None)?; - } - AcirValue::Array(values) => { - for value in values { - range_constraint_value(context, value)?; - } - } - AcirValue::DynamicArray(_) => { - unreachable!("Brillig opcodes cannot return dynamic arrays") - } - } - Ok(()) - } - - // This is a hack to ensure that if we're compiling a brillig entrypoint function then - // we don't also add a number of range constraints. - if !unsafe_return_values { - for output_var in &outputs_var { - range_constraint_value(self, output_var)?; - } - } - Ok(outputs_var) - } - #[allow(clippy::too_many_arguments)] pub(crate) fn brillig_call( &mut self, @@ -1565,6 +1478,7 @@ impl AcirContext { attempt_execution: bool, unsafe_return_values: bool, brillig_function_index: u32, + brillig_stdlib_func: Option, ) -> Result, RuntimeError> { let brillig_inputs = try_vecmap(inputs, |i| -> Result<_, InternalError> { match i { @@ -1618,6 +1532,7 @@ impl AcirContext { brillig_inputs, brillig_outputs, brillig_function_index, + brillig_stdlib_func, ); fn range_constraint_value( diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 999ff2ddb5d..0b04d1b63ab 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -10,7 +10,7 @@ use crate::{ use acvm::acir::{ circuit::{ - brillig::{Brillig as AcvmBrillig, BrilligInputs, BrilligOutputs}, + brillig::{BrilligInputs, BrilligOutputs}, opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, OpcodeLocation, }, @@ -24,6 +24,12 @@ use acvm::{ use iter_extended::vecmap; use num_bigint::BigUint; +/// Brillig calls such as for the Brillig std lib are resolved only after code generation is finished. +/// This index should be used when adding a Brillig call during code generation. +/// Code generation should then keep track of that unresolved call opcode which will be resolved with the +/// correct function index after code generation. +pub(crate) const PLACEHOLDER_BRILLIG_INDEX: u32 = 0; + #[derive(Debug, Default)] /// The output of the Acir-gen pass, which should only be produced for entry point Acir functions pub(crate) struct GeneratedAcir { @@ -62,6 +68,29 @@ pub(crate) struct GeneratedAcir { /// Name for the corresponding entry point represented by this Acir-gen output. /// Only used for debugging and benchmarking purposes pub(crate) name: String, + + /// Maps the opcode index to a Brillig std library function call. + /// As to avoid passing the ACIR gen shared context into each individual ACIR + /// we can instead keep this map and resolve the Brillig calls at the end of code generation. + pub(crate) brillig_stdlib_func_locations: BTreeMap, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub(crate) enum BrilligStdlibFunc { + Inverse, + // The Brillig quotient code is different depending upon the bit size. + Quotient(u32), +} + +impl BrilligStdlibFunc { + pub(crate) fn get_generated_brillig(&self) -> GeneratedBrillig { + match self { + BrilligStdlibFunc::Inverse => brillig_directive::directive_invert(), + BrilligStdlibFunc::Quotient(bit_size) => { + brillig_directive::directive_quotient(*bit_size) + } + } + } } impl GeneratedAcir { @@ -456,7 +485,14 @@ impl GeneratedAcir { let inverse_code = brillig_directive::directive_invert(); let inputs = vec![BrilligInputs::Single(expr)]; let outputs = vec![BrilligOutputs::Simple(inverted_witness)]; - self.brillig(Some(Expression::one()), inverse_code, inputs, outputs); + self.brillig_call( + Some(Expression::one()), + &inverse_code, + inputs, + outputs, + PLACEHOLDER_BRILLIG_INDEX, + Some(BrilligStdlibFunc::Inverse), + ); inverted_witness } @@ -589,35 +625,6 @@ impl GeneratedAcir { Ok(()) } - // TODO: Delete this method once we remove the `Brillig` opcode - pub(crate) fn brillig( - &mut self, - predicate: Option, - generated_brillig: GeneratedBrillig, - inputs: Vec, - outputs: Vec, - ) { - let opcode = AcirOpcode::Brillig(AcvmBrillig { - inputs, - outputs, - bytecode: generated_brillig.byte_code, - predicate, - }); - self.push_opcode(opcode); - for (brillig_index, call_stack) in generated_brillig.locations { - self.locations.insert( - OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index }, - call_stack, - ); - } - for (brillig_index, message) in generated_brillig.assert_messages { - self.assert_messages.insert( - OpcodeLocation::Brillig { acir_index: self.opcodes.len() - 1, brillig_index }, - message, - ); - } - } - pub(crate) fn brillig_call( &mut self, predicate: Option, @@ -625,10 +632,16 @@ impl GeneratedAcir { inputs: Vec, outputs: Vec, brillig_function_index: u32, + stdlib_func: Option, ) { let opcode = AcirOpcode::BrilligCall { id: brillig_function_index, inputs, outputs, predicate }; self.push_opcode(opcode); + if let Some(stdlib_func) = stdlib_func { + self.brillig_stdlib_func_locations + .insert(self.last_acir_opcode_location(), stdlib_func); + } + for (brillig_index, call_stack) in generated_brillig.locations.iter() { self.locations.insert( OpcodeLocation::Brillig { @@ -649,6 +662,22 @@ impl GeneratedAcir { } } + // We can only resolve the Brillig stdlib after having processed the entire ACIR + pub(crate) fn resolve_brillig_stdlib_call( + &mut self, + opcode_location: OpcodeLocation, + brillig_function_index: u32, + ) { + let acir_index = match opcode_location { + OpcodeLocation::Acir(index) => index, + _ => panic!("should not have brillig index"), + }; + match &mut self.opcodes[acir_index] { + AcirOpcode::BrilligCall { id, .. } => *id = brillig_function_index, + _ => panic!("expected brillig call opcode"), + } + } + pub(crate) fn last_acir_opcode_location(&self) -> OpcodeLocation { OpcodeLocation::Acir(self.opcodes.len() - 1) } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 02b381d79fc..ee236df8eac 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -5,6 +5,7 @@ use std::collections::{BTreeMap, HashSet}; use std::fmt::Debug; use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; +use self::acir_ir::generated_acir::BrilligStdlibFunc; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; use super::ir::function::FunctionId; @@ -30,6 +31,7 @@ use crate::ssa::ir::function::InlineType; pub(crate) use acir_ir::generated_acir::GeneratedAcir; use acvm::acir::circuit::brillig::BrilligBytecode; +use acvm::acir::circuit::OpcodeLocation; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{ @@ -39,7 +41,7 @@ use acvm::{ use fxhash::FxHashMap as HashMap; use im::Vector; use iter_extended::{try_vecmap, vecmap}; -use noirc_frontend::Distinctness; +use noirc_frontend::ast::Distinctness; #[derive(Default)] struct SharedContext { @@ -55,6 +57,14 @@ struct SharedContext { /// This uses the brillig parameters in the map since using slices with different lengths /// needs to create different brillig entrypoints brillig_generated_func_pointers: BTreeMap<(FunctionId, Vec), u32>, + + /// Maps a Brillig std lib function (a handwritten primitive such as for inversion) -> Final generated Brillig artifact index. + /// A separate mapping from normal Brillig calls is necessary as these methods do not have an associated function id from SSA. + brillig_stdlib_func_pointer: HashMap, + + /// Keeps track of Brillig std lib calls per function that need to still be resolved + /// with the correct function pointer from the `brillig_stdlib_func_pointer` map. + brillig_stdlib_calls_to_resolve: HashMap>, } impl SharedContext { @@ -84,6 +94,47 @@ impl SharedContext { fn new_generated_pointer(&self) -> u32 { self.generated_brillig.len() as u32 } + + fn generate_brillig_calls_to_resolve( + &mut self, + brillig_stdlib_func: &BrilligStdlibFunc, + func_id: FunctionId, + opcode_location: OpcodeLocation, + ) { + if let Some(generated_pointer) = + self.brillig_stdlib_func_pointer.get(brillig_stdlib_func).copied() + { + self.add_call_to_resolve(func_id, (opcode_location, generated_pointer)); + } else { + let code = brillig_stdlib_func.get_generated_brillig(); + let generated_pointer = self.new_generated_pointer(); + self.insert_generated_brillig_stdlib( + *brillig_stdlib_func, + generated_pointer, + func_id, + opcode_location, + code, + ); + } + } + + /// Insert a newly generated Brillig stdlib function + fn insert_generated_brillig_stdlib( + &mut self, + brillig_stdlib_func: BrilligStdlibFunc, + generated_pointer: u32, + func_id: FunctionId, + opcode_location: OpcodeLocation, + code: GeneratedBrillig, + ) { + self.brillig_stdlib_func_pointer.insert(brillig_stdlib_func, generated_pointer); + self.add_call_to_resolve(func_id, (opcode_location, generated_pointer)); + self.generated_brillig.push(code); + } + + fn add_call_to_resolve(&mut self, func_id: FunctionId, call_to_resolve: (OpcodeLocation, u32)) { + self.brillig_stdlib_calls_to_resolve.entry(func_id).or_default().push(call_to_resolve); + } } /// Context struct for the acir generation pass. @@ -240,6 +291,35 @@ impl Ssa { if let Some(mut generated_acir) = context.convert_ssa_function(&self, function, brillig)? { + // We want to be able to insert Brillig stdlib functions anywhere during the ACIR generation process (e.g. such as on the `GeneratedAcir`). + // As we don't want a reference to the `SharedContext` on the generated ACIR itself, + // we instead store the opcode location at which a Brillig call to a std lib function occurred. + // We then defer resolving the function IDs of those Brillig functions to when we have generated Brillig + // for all normal Brillig calls. + for (opcode_location, brillig_stdlib_func) in + &generated_acir.brillig_stdlib_func_locations + { + shared_context.generate_brillig_calls_to_resolve( + brillig_stdlib_func, + function.id(), + *opcode_location, + ); + } + + // Fetch the Brillig stdlib calls to resolve for this function + if let Some(calls_to_resolve) = + shared_context.brillig_stdlib_calls_to_resolve.get(&function.id()) + { + // Resolve the Brillig stdlib calls + // We have to do a separate loop as the generated ACIR cannot be borrowed as mutable after an immutable borrow + for (opcode_location, brillig_function_pointer) in calls_to_resolve { + generated_acir.resolve_brillig_stdlib_call( + *opcode_location, + *brillig_function_pointer, + ); + } + } + generated_acir.name = function.name().to_owned(); acirs.push(generated_acir); } @@ -376,6 +456,7 @@ impl<'a> Context<'a> { true, // We are guaranteed to have a Brillig function pointer of `0` as main itself is marked as unconstrained 0, + None, )?; self.shared_context.insert_generated_brillig(main_func.id(), arguments, 0, code); @@ -687,6 +768,7 @@ impl<'a> Context<'a> { true, false, *generated_pointer, + None, )? } else { let code = @@ -701,6 +783,7 @@ impl<'a> Context<'a> { true, false, generated_pointer, + None, )?; self.shared_context.insert_generated_brillig( *id, @@ -2518,14 +2601,20 @@ fn can_omit_element_sizes_array(array_typ: &Type) -> bool { #[cfg(test)] mod test { + use std::collections::BTreeMap; + use acvm::{ - acir::{circuit::Opcode, native_types::Witness}, + acir::{ + circuit::{Opcode, OpcodeLocation}, + native_types::Witness, + }, FieldElement, }; use crate::{ brillig::Brillig, ssa::{ + acir_gen::acir_ir::generated_acir::BrilligStdlibFunc, function_builder::FunctionBuilder, ir::{ function::{FunctionId, InlineType}, @@ -2595,7 +2684,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _) = ssa - .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) + .into_acir(&Brillig::default(), noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // Expected result: // main f0 @@ -2691,7 +2780,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _) = ssa - .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) + .into_acir(&Brillig::default(), noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` // opcodes will be different. The changes can discerned from the checks below. @@ -2782,7 +2871,7 @@ mod test { let ssa = builder.finish(); let (acir_functions, _) = ssa - .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) + .into_acir(&Brillig::default(), noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); @@ -2848,10 +2937,12 @@ mod test { fn multiple_brillig_calls_one_bytecode() { // acir(inline) fn main f0 { // b0(v0: Field, v1: Field): - // v3 = call f1(v0, v1) // v4 = call f1(v0, v1) // v5 = call f1(v0, v1) // v6 = call f1(v0, v1) + // v7 = call f2(v0, v1) + // v8 = call f1(v0, v1) + // v9 = call f2(v0, v1) // return // } // brillig fn foo f1 { @@ -2894,15 +2985,11 @@ mod test { println!("{}", ssa); let (acir_functions, brillig_functions) = ssa - .into_acir(&brillig, noirc_frontend::Distinctness::Distinct) + .into_acir(&brillig, noirc_frontend::ast::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); - assert_eq!( - brillig_functions.len(), - 2, - "Should only have generated a single Brillig function" - ); + assert_eq!(brillig_functions.len(), 2, "Should only have generated two Brillig functions"); let main_acir = &acir_functions[0]; let main_opcodes = main_acir.opcodes(); @@ -2919,4 +3006,295 @@ mod test { } } } + + // Test that given multiple primitive operations that are represented by Brillig directives (e.g. invert/quotient), + // we will only generate one bytecode and the appropriate Brillig call opcodes are generated. + #[test] + fn multiple_brillig_stdlib_calls() { + // acir(inline) fn main f0 { + // b0(v0: u32, v1: u32, v2: u32): + // v3 = div v0, v1 + // constrain v3 == v2 + // v4 = div v1, v2 + // constrain v4 == u32 1 + // return + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::unsigned(32)); + let main_v1 = builder.add_parameter(Type::unsigned(32)); + let main_v2 = builder.add_parameter(Type::unsigned(32)); + + // Call a primitive operation that uses Brillig + let v0_div_v1 = builder.insert_binary(main_v0, BinaryOp::Div, main_v1); + builder.insert_constrain(v0_div_v1, main_v2, None); + + // Call the same primitive operation again + let v1_div_v2 = builder.insert_binary(main_v1, BinaryOp::Div, main_v2); + let one = builder.numeric_constant(1u128, Type::unsigned(32)); + builder.insert_constrain(v1_div_v2, one, None); + + builder.terminate_with_return(vec![]); + + let ssa = builder.finish(); + println!("{}", ssa); + + // The Brillig bytecode we insert for the stdlib is hardcoded so we do not need to provide any + // Brillig artifacts to the ACIR gen pass. + let (acir_functions, brillig_functions) = ssa + .into_acir(&Brillig::default(), noirc_frontend::ast::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + + assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); + // We expect two brillig functions: + // - Quotient (shared between both divisions) + // - Inversion, caused by division-by-zero check (shared between both divisions) + assert_eq!(brillig_functions.len(), 2, "Should only have generated two Brillig functions"); + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + check_brillig_calls( + &acir_functions[0].brillig_stdlib_func_locations, + main_opcodes, + 0, + 4, + 0, + ); + } + + // Test that given both hardcoded Brillig directives and calls to normal Brillig functions, + // we generate a single bytecode for the directives and a single bytecode for the normal Brillig calls. + #[test] + fn brillig_stdlib_calls_with_regular_brillig_call() { + // acir(inline) fn main f0 { + // b0(v0: u32, v1: u32, v2: u32): + // v4 = div v0, v1 + // constrain v4 == v2 + // v5 = call f1(v0, v1) + // v6 = call f1(v0, v1) + // v7 = div v1, v2 + // constrain v7 == u32 1 + // return + // } + // brillig fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::unsigned(32)); + let main_v1 = builder.add_parameter(Type::unsigned(32)); + let main_v2 = builder.add_parameter(Type::unsigned(32)); + + let foo_id = Id::test_new(1); + let foo = builder.import_function(foo_id); + + // Call a primitive operation that uses Brillig + let v0_div_v1 = builder.insert_binary(main_v0, BinaryOp::Div, main_v1); + builder.insert_constrain(v0_div_v1, main_v2, None); + + // Insert multiple calls to the same Brillig function + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + + // Call the same primitive operation again + let v1_div_v2 = builder.insert_binary(main_v1, BinaryOp::Div, main_v2); + let one = builder.numeric_constant(1u128, Type::unsigned(32)); + builder.insert_constrain(v1_div_v2, one, None); + + builder.terminate_with_return(vec![]); + + build_basic_foo_with_return(&mut builder, foo_id, true); + + let ssa = builder.finish(); + // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. + let brillig = ssa.to_brillig(false); + println!("{}", ssa); + + let (acir_functions, brillig_functions) = ssa + .into_acir(&brillig, noirc_frontend::ast::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + + assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); + // We expect 3 brillig functions: + // - Quotient (shared between both divisions) + // - Inversion, caused by division-by-zero check (shared between both divisions) + // - Custom brillig function `foo` + assert_eq!( + brillig_functions.len(), + 3, + "Should only have generated three Brillig functions" + ); + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + check_brillig_calls( + &acir_functions[0].brillig_stdlib_func_locations, + main_opcodes, + 1, + 4, + 2, + ); + } + + // Test that given both normal Brillig calls, Brillig stdlib calls, and non-inlined ACIR calls, that we accurately generate ACIR. + #[test] + fn brillig_stdlib_calls_with_multiple_acir_calls() { + // acir(inline) fn main f0 { + // b0(v0: u32, v1: u32, v2: u32): + // v4 = div v0, v1 + // constrain v4 == v2 + // v5 = call f1(v0, v1) + // v6 = call f2(v0, v1) + // v7 = div v1, v2 + // constrain v7 == u32 1 + // return + // } + // brillig fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + // acir(fold) fn foo f2 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::unsigned(32)); + let main_v1 = builder.add_parameter(Type::unsigned(32)); + let main_v2 = builder.add_parameter(Type::unsigned(32)); + + let foo_id = Id::test_new(1); + let foo = builder.import_function(foo_id); + let bar_id = Id::test_new(2); + let bar = builder.import_function(bar_id); + + // Call a primitive operation that uses Brillig + let v0_div_v1 = builder.insert_binary(main_v0, BinaryOp::Div, main_v1); + builder.insert_constrain(v0_div_v1, main_v2, None); + + // Insert multiple calls to the same Brillig function + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + + // Call the same primitive operation again + let v1_div_v2 = builder.insert_binary(main_v1, BinaryOp::Div, main_v2); + let one = builder.numeric_constant(1u128, Type::unsigned(32)); + builder.insert_constrain(v1_div_v2, one, None); + + builder.terminate_with_return(vec![]); + + build_basic_foo_with_return(&mut builder, foo_id, true); + build_basic_foo_with_return(&mut builder, bar_id, false); + + let ssa = builder.finish(); + // We need to generate Brillig artifacts for the regular Brillig function and pass them to the ACIR generation pass. + let brillig = ssa.to_brillig(false); + println!("{}", ssa); + + let (acir_functions, brillig_functions) = ssa + .into_acir(&brillig, noirc_frontend::ast::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + + assert_eq!(acir_functions.len(), 2, "Should only have two ACIR functions"); + // We expect 3 brillig functions: + // - Quotient (shared between both divisions) + // - Inversion, caused by division-by-zero check (shared between both divisions) + // - Custom brillig function `foo` + assert_eq!( + brillig_functions.len(), + 3, + "Should only have generated three Brillig functions" + ); + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + check_brillig_calls( + &acir_functions[0].brillig_stdlib_func_locations, + main_opcodes, + 1, + 4, + 2, + ); + + let foo_acir = &acir_functions[1]; + let foo_opcodes = foo_acir.opcodes(); + check_brillig_calls(&acir_functions[1].brillig_stdlib_func_locations, foo_opcodes, 1, 1, 0); + } + + fn check_brillig_calls( + brillig_stdlib_function_locations: &BTreeMap, + opcodes: &[Opcode], + num_normal_brillig_functions: u32, + expected_num_stdlib_calls: u32, + expected_num_normal_calls: u32, + ) { + // First we check calls to the Brillig stdlib + let mut num_brillig_stdlib_calls = 0; + for (i, (opcode_location, brillig_stdlib_func)) in + brillig_stdlib_function_locations.iter().enumerate() + { + // We can take just modulo 2 to determine the expected ID as we only code generated two Brillig stdlib function + let stdlib_func_index = (i % 2) as u32; + if stdlib_func_index == 0 { + assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Inverse)); + } else { + assert!(matches!(brillig_stdlib_func, BrilligStdlibFunc::Quotient(_))); + } + + match opcode_location { + OpcodeLocation::Acir(acir_index) => { + match opcodes[*acir_index] { + Opcode::BrilligCall { id, .. } => { + // Brillig stdlib function calls are only resolved at the end of ACIR generation so their + // IDs are expected to always reference Brillig bytecode at the end of the Brillig functions list. + // We have one normal Brillig call so we add one here to the std lib function's index within the std lib. + let expected_id = stdlib_func_index + num_normal_brillig_functions; + assert_eq!(id, expected_id, "Expected {expected_id} but got {id}"); + num_brillig_stdlib_calls += 1; + } + _ => panic!("Expected BrilligCall opcode"), + } + } + _ => panic!("Expected OpcodeLocation::Acir"), + } + } + + assert_eq!( + num_brillig_stdlib_calls, expected_num_stdlib_calls, + "Should have {expected_num_stdlib_calls} BrilligCall opcodes to stdlib functions but got {num_brillig_stdlib_calls}" + ); + + // Check the normal Brillig calls + // This check right now expects to only call one Brillig function. + let mut num_normal_brillig_calls = 0; + for (i, opcode) in opcodes.iter().enumerate() { + match opcode { + Opcode::BrilligCall { id, .. } => { + if brillig_stdlib_function_locations.get(&OpcodeLocation::Acir(i)).is_some() { + // We should have already checked Brillig stdlib functions and only want to check normal Brillig calls here + continue; + } + // We only generate one normal Brillig call so we should expect a function ID of `0` + let expected_id = 0u32; + assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); + num_normal_brillig_calls += 1; + } + _ => {} + } + } + + assert_eq!( + num_normal_brillig_calls, expected_num_normal_calls, + "Should have {expected_num_normal_calls} BrilligCall opcodes to normal Brillig functions but got {num_normal_brillig_calls}" + ); + } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs index e785212f8d2..5f0660f5a79 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use crate::ssa::ir::{types::Type, value::ValueId}; use acvm::FieldElement; use fxhash::FxHashMap as HashMap; +use noirc_frontend::ast; use noirc_frontend::hir_def::function::FunctionSignature; use super::FunctionBuilder; @@ -33,8 +34,8 @@ impl DataBusBuilder { for param in &main_signature.0 { let is_databus = match param.2 { - noirc_frontend::Visibility::Public | noirc_frontend::Visibility::Private => false, - noirc_frontend::Visibility::DataBus => true, + ast::Visibility::Public | ast::Visibility::Private => false, + ast::Visibility::DataBus => true, }; let len = param.1.field_count() as usize; params_is_databus.extend(vec![is_databus; len]); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs index 569e543ce40..e591a3d478c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/context.rs @@ -4,9 +4,9 @@ use std::sync::{Mutex, RwLock}; use acvm::FieldElement; use iter_extended::vecmap; use noirc_errors::Location; +use noirc_frontend::ast::{BinaryOpKind, Signedness}; use noirc_frontend::monomorphization::ast::{self, LocalId, Parameters}; use noirc_frontend::monomorphization::ast::{FuncId, Program}; -use noirc_frontend::{BinaryOpKind, Signedness}; use crate::errors::RuntimeError; use crate::ssa::function_builder::FunctionBuilder; @@ -562,7 +562,7 @@ impl<'a> FunctionContext<'a> { pub(super) fn insert_binary( &mut self, mut lhs: ValueId, - operator: noirc_frontend::BinaryOpKind, + operator: BinaryOpKind, mut rhs: ValueId, location: Location, ) -> Values { @@ -625,7 +625,7 @@ impl<'a> FunctionContext<'a> { fn insert_array_equality( &mut self, lhs: ValueId, - operator: noirc_frontend::BinaryOpKind, + operator: BinaryOpKind, rhs: ValueId, location: Location, ) -> Values { @@ -1090,23 +1090,23 @@ impl<'a> FunctionContext<'a> { /// True if the given operator cannot be encoded directly and needs /// to be represented as !(some other operator) -fn operator_requires_not(op: noirc_frontend::BinaryOpKind) -> bool { - use noirc_frontend::BinaryOpKind::*; +fn operator_requires_not(op: BinaryOpKind) -> bool { + use BinaryOpKind::*; matches!(op, NotEqual | LessEqual | GreaterEqual) } /// True if the given operator cannot be encoded directly and needs /// to have its lhs and rhs swapped to be represented with another operator. /// Example: (a > b) needs to be represented as (b < a) -fn operator_requires_swapped_operands(op: noirc_frontend::BinaryOpKind) -> bool { - use noirc_frontend::BinaryOpKind::*; +fn operator_requires_swapped_operands(op: BinaryOpKind) -> bool { + use BinaryOpKind::*; matches!(op, Greater | LessEqual) } /// If the operation requires its result to be truncated because it is an integer, the maximum /// number of bits that result may occupy is returned. fn operator_result_max_bit_size_to_truncate( - op: noirc_frontend::BinaryOpKind, + op: BinaryOpKind, lhs: ValueId, rhs: ValueId, dfg: &DataFlowGraph, @@ -1123,7 +1123,7 @@ fn operator_result_max_bit_size_to_truncate( let lhs_bit_size = get_bit_size(lhs_type)?; let rhs_bit_size = get_bit_size(rhs_type)?; - use noirc_frontend::BinaryOpKind::*; + use BinaryOpKind::*; match op { Add => Some(std::cmp::max(lhs_bit_size, rhs_bit_size) + 1), Subtract => Some(std::cmp::max(lhs_bit_size, rhs_bit_size) + 1), @@ -1173,7 +1173,7 @@ fn operator_result_max_bit_size_to_truncate( /// Take care when using this to insert a binary instruction: this requires /// checking operator_requires_not and operator_requires_swapped_operands /// to represent the full operation correctly. -fn convert_operator(op: noirc_frontend::BinaryOpKind) -> BinaryOp { +fn convert_operator(op: BinaryOpKind) -> BinaryOp { match op { BinaryOpKind::Add => BinaryOp::Add, BinaryOpKind::Subtract => BinaryOp::Sub, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 2a4b5276547..79f7cda4ae2 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -7,10 +7,8 @@ pub(crate) use program::Ssa; use context::SharedContext; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; -use noirc_frontend::{ - monomorphization::ast::{self, Expression, Program}, - Visibility, -}; +use noirc_frontend::ast::{UnaryOp, Visibility}; +use noirc_frontend::monomorphization::ast::{self, Expression, Program}; use crate::{ errors::{InternalError, RuntimeError}, @@ -306,24 +304,24 @@ impl<'a> FunctionContext<'a> { fn codegen_unary(&mut self, unary: &ast::Unary) -> Result { match unary.operator { - noirc_frontend::UnaryOp::Not => { + UnaryOp::Not => { let rhs = self.codegen_expression(&unary.rhs)?; let rhs = rhs.into_leaf().eval(self); Ok(self.builder.insert_not(rhs).into()) } - noirc_frontend::UnaryOp::Minus => { + UnaryOp::Minus => { let rhs = self.codegen_expression(&unary.rhs)?; let rhs = rhs.into_leaf().eval(self); let typ = self.builder.type_of_value(rhs); let zero = self.builder.numeric_constant(0u128, typ); Ok(self.insert_binary( zero, - noirc_frontend::BinaryOpKind::Subtract, + noirc_frontend::ast::BinaryOpKind::Subtract, rhs, unary.location, )) } - noirc_frontend::UnaryOp::MutableReference => { + UnaryOp::MutableReference => { Ok(self.codegen_reference(&unary.rhs)?.map(|rhs| { match rhs { value::Value::Normal(value) => { @@ -338,7 +336,7 @@ impl<'a> FunctionContext<'a> { } })) } - noirc_frontend::UnaryOp::Dereference { .. } => { + UnaryOp::Dereference { .. } => { let rhs = self.codegen_expression(&unary.rhs)?; Ok(self.dereference(&rhs, &unary.result_type)) } diff --git a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml index e39ab2fe106..84c9393fa37 100644 --- a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml @@ -3,6 +3,7 @@ name = "noirc_frontend" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,6 +18,7 @@ iter-extended.workspace = true chumsky.workspace = true thiserror.workspace = true smol_str.workspace = true +im.workspace = true serde_json.workspace = true serde.workspace = true rustc-hash = "1.1.0" diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index 0e5919bf7db..92c1add80a6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -1,11 +1,11 @@ use std::borrow::Cow; use std::fmt::Display; -use crate::token::{Attributes, Token}; -use crate::{ +use crate::ast::{ Distinctness, Ident, ItemVisibility, Path, Pattern, Recoverable, Statement, StatementKind, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, Visibility, }; +use crate::token::{Attributes, Token}; use acvm::FieldElement; use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; @@ -387,6 +387,9 @@ pub struct FunctionDefinition { /// True if this function was defined with the 'unconstrained' keyword pub is_unconstrained: bool, + /// True if this function was defined with the 'comptime' keyword + pub is_comptime: bool, + /// Indicate if this function was defined with the 'pub' keyword pub visibility: ItemVisibility, @@ -679,10 +682,12 @@ impl FunctionDefinition { span: ident.span().merge(unresolved_type.span.unwrap()), }) .collect(); + FunctionDefinition { name: name.clone(), attributes: Attributes::empty(), is_unconstrained: false, + is_comptime: false, visibility: ItemVisibility::Private, generics: generics.clone(), parameters: p, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs index 10d962a23f7..9816218c5f7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/function.rs @@ -3,8 +3,8 @@ use std::fmt::Display; use noirc_errors::Span; use crate::{ + ast::{FunctionReturnType, Ident, Param, Visibility}, token::{Attributes, FunctionAttribute, SecondaryAttribute}, - FunctionReturnType, Ident, Param, Visibility, }; use super::{FunctionDefinition, UnresolvedType, UnresolvedTypeData}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs index 753b5a31d32..1831a046f5b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -1,17 +1,18 @@ use std::fmt::Display; use std::sync::atomic::{AtomicU32, Ordering}; +use acvm::FieldElement; +use iter_extended::vecmap; +use noirc_errors::{Span, Spanned}; + +use super::{ + BlockExpression, Expression, ExpressionKind, IndexExpression, MemberAccessExpression, + MethodCallExpression, UnresolvedType, +}; use crate::lexer::token::SpannedToken; use crate::macros_api::SecondaryAttribute; use crate::parser::{ParserError, ParserErrorReason}; use crate::token::Token; -use crate::{ - BlockExpression, Expression, ExpressionKind, IndexExpression, MemberAccessExpression, - MethodCallExpression, UnresolvedType, -}; -use acvm::FieldElement; -use iter_extended::vecmap; -use noirc_errors::{Span, Spanned}; /// This is used when an identifier fails to parse in the parser. /// Instead of failing the parse, we can often recover using this @@ -38,6 +39,8 @@ pub enum StatementKind { For(ForLoopStatement), Break, Continue, + /// This statement should be executed at compile-time + Comptime(Box), // This is an expression with a trailing semi-colon Semi(Expression), // This statement is the result of a recovered parse error. @@ -47,6 +50,19 @@ pub enum StatementKind { } impl Statement { + pub fn add_semicolon( + mut self, + semi: Option, + span: Span, + last_statement_in_block: bool, + emit_error: &mut dyn FnMut(ParserError), + ) -> Self { + self.kind = self.kind.add_semicolon(semi, span, last_statement_in_block, emit_error); + self + } +} + +impl StatementKind { pub fn add_semicolon( self, semi: Option, @@ -57,7 +73,7 @@ impl Statement { let missing_semicolon = ParserError::with_reason(ParserErrorReason::MissingSeparatingSemi, span); - let kind = match self.kind { + match self { StatementKind::Let(_) | StatementKind::Constrain(_) | StatementKind::Assign(_) @@ -69,10 +85,15 @@ impl Statement { if semi.is_none() { emit_error(missing_semicolon); } - self.kind + self + } + StatementKind::Comptime(mut statement) => { + *statement = + statement.add_semicolon(semi, span, last_statement_in_block, emit_error); + StatementKind::Comptime(statement) } // A semicolon on a for loop is optional and does nothing - StatementKind::For(_) => self.kind, + StatementKind::For(_) => self, StatementKind::Expression(expr) => { match (&expr.kind, semi, last_statement_in_block) { @@ -92,9 +113,7 @@ impl Statement { (_, None, true) => StatementKind::Expression(expr), } } - }; - - Statement { kind, span: self.span } + } } } @@ -108,7 +127,13 @@ impl StatementKind { pub fn new_let( ((pattern, r#type), expression): ((Pattern, UnresolvedType), Expression), ) -> StatementKind { - StatementKind::Let(LetStatement { pattern, r#type, expression, attributes: vec![] }) + StatementKind::Let(LetStatement { + pattern, + r#type, + expression, + comptime: false, + attributes: vec![], + }) } /// Create a Statement::Assign value, desugaring any combined operators like += if needed. @@ -124,7 +149,7 @@ impl StatementKind { let lvalue_expr = lvalue.as_expression(); let error_msg = "Token passed to Statement::assign is not a binary operator"; - let infix = crate::InfixExpression { + let infix = crate::ast::InfixExpression { lhs: lvalue_expr, operator: operator.try_into_binary_op(span).expect(error_msg), rhs: expression, @@ -407,17 +432,9 @@ pub struct LetStatement { pub r#type: UnresolvedType, pub expression: Expression, pub attributes: Vec, -} -impl LetStatement { - pub fn new_let( - (((pattern, r#type), expression), attributes): ( - ((Pattern, UnresolvedType), Expression), - Vec, - ), - ) -> LetStatement { - LetStatement { pattern, r#type, expression, attributes } - } + // True if this should only be run during compile-time + pub comptime: bool, } #[derive(Debug, PartialEq, Eq, Clone)] @@ -505,8 +522,8 @@ impl LValue { })) } LValue::Dereference(lvalue, _span) => { - ExpressionKind::Prefix(Box::new(crate::PrefixExpression { - operator: crate::UnaryOp::Dereference { implicitly_added: false }, + ExpressionKind::Prefix(Box::new(crate::ast::PrefixExpression { + operator: crate::ast::UnaryOp::Dereference { implicitly_added: false }, rhs: lvalue.as_expression(), })) } @@ -573,6 +590,7 @@ impl ForRange { pattern: Pattern::Identifier(array_ident.clone()), r#type: UnresolvedType::unspecified(), expression: array, + comptime: false, attributes: vec![], }), span: array_span, @@ -616,6 +634,7 @@ impl ForRange { pattern: Pattern::Identifier(identifier), r#type: UnresolvedType::unspecified(), expression: Expression::new(loop_element, array_span), + comptime: false, attributes: vec![], }), span: array_span, @@ -666,6 +685,7 @@ impl Display for StatementKind { StatementKind::For(for_loop) => for_loop.fmt(f), StatementKind::Break => write!(f, "break"), StatementKind::Continue => write!(f, "continue"), + StatementKind::Comptime(statement) => write!(f, "comptime {statement}"), StatementKind::Semi(semi) => write!(f, "{semi};"), StatementKind::Error => write!(f, "Error"), } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs index 6a32fa717f3..bda6b8c0b11 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/structure.rs @@ -1,6 +1,8 @@ use std::fmt::Display; -use crate::{token::SecondaryAttribute, Ident, UnresolvedGenerics, UnresolvedType}; +use crate::ast::{Ident, UnresolvedGenerics, UnresolvedType}; +use crate::token::SecondaryAttribute; + use iter_extended::vecmap; use noirc_errors::Span; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs index 775f0a5f2b4..772675723b5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -3,10 +3,11 @@ use std::fmt::Display; use iter_extended::vecmap; use noirc_errors::Span; -use crate::{ - node_interner::TraitId, BlockExpression, Expression, FunctionReturnType, Ident, NoirFunction, - Path, UnresolvedGenerics, UnresolvedType, +use crate::ast::{ + BlockExpression, Expression, FunctionReturnType, Ident, NoirFunction, Path, UnresolvedGenerics, + UnresolvedType, }; +use crate::node_interner::TraitId; /// AST node for trait definitions: /// `trait name { ... items ... }` diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/type_alias.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/type_alias.rs index 76a1e5a7e30..3228765170e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/type_alias.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/type_alias.rs @@ -1,4 +1,4 @@ -use crate::{Ident, UnresolvedGenerics, UnresolvedType}; +use super::{Ident, UnresolvedGenerics, UnresolvedType}; use iter_extended::vecmap; use noirc_errors::Span; use std::fmt::Display; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs index 67b52071d7b..3e7d123398b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -145,6 +145,7 @@ impl DebugInstrumenter { pattern: ast::Pattern::Identifier(ident("__debug_expr", ret_expr.span)), r#type: ast::UnresolvedType::unspecified(), expression: ret_expr.clone(), + comptime: false, attributes: vec![], }), span: ret_expr.span, @@ -243,6 +244,7 @@ impl DebugInstrumenter { kind: ast::StatementKind::Let(ast::LetStatement { pattern: ast::Pattern::Tuple(vars_pattern, let_stmt.pattern.span()), r#type: ast::UnresolvedType::unspecified(), + comptime: false, expression: ast::Expression { kind: ast::ExpressionKind::Block(ast::BlockExpression { statements: block_stmts, @@ -275,6 +277,7 @@ impl DebugInstrumenter { pattern: ast::Pattern::Identifier(ident("__debug_expr", assign_stmt.expression.span)), r#type: ast::UnresolvedType::unspecified(), expression: assign_stmt.expression.clone(), + comptime: false, attributes: vec![], }); let expression_span = assign_stmt.expression.span; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs index 8ffcbce7d62..47ca7083ff0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs @@ -1,18 +1,19 @@ use iter_extended::vecmap; use noirc_errors::{Span, Spanned}; +use crate::ast::{ + ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, + ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, + IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, + MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, UnresolvedType, + UnresolvedTypeData, UnresolvedTypeExpression, +}; use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; use crate::hir_def::expr::{HirArrayLiteral, HirExpression, HirIdent}; use crate::hir_def::stmt::{HirLValue, HirPattern, HirStatement}; +use crate::hir_def::types::Type; use crate::macros_api::HirLiteral; use crate::node_interner::{ExprId, NodeInterner, StmtId}; -use crate::{ - ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, - ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, - IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, Type, - UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, -}; // TODO: // - Full path for idents & types @@ -36,6 +37,7 @@ impl StmtId { pattern, r#type, expression, + comptime: false, attributes: Vec::new(), }) } @@ -64,6 +66,9 @@ impl StmtId { HirStatement::Expression(expr) => StatementKind::Expression(expr.to_ast(interner)), HirStatement::Semi(expr) => StatementKind::Semi(expr.to_ast(interner)), HirStatement::Error => StatementKind::Error, + HirStatement::Comptime(statement) => { + StatementKind::Comptime(Box::new(statement.to_ast(interner).kind)) + } }; Statement { kind, span } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs new file mode 100644 index 00000000000..03839c2f0cd --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -0,0 +1,1294 @@ +use std::{borrow::Cow, collections::hash_map::Entry, rc::Rc}; + +use acvm::FieldElement; +use im::Vector; +use iter_extended::{try_vecmap, vecmap}; +use noirc_errors::Location; +use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; + +use crate::ast::{BinaryOpKind, BlockExpression, FunctionKind, IntegerBitSize, Signedness}; +use crate::{ + hir_def::{ + expr::{ + HirArrayLiteral, HirBlockExpression, HirCallExpression, HirCastExpression, + HirConstructorExpression, HirIdent, HirIfExpression, HirIndexExpression, + HirInfixExpression, HirLambda, HirMemberAccess, HirMethodCallExpression, + HirPrefixExpression, + }, + stmt::{ + HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, + HirPattern, + }, + }, + macros_api::{HirExpression, HirLiteral, HirStatement, NodeInterner}, + node_interner::{DefinitionId, DefinitionKind, ExprId, FuncId, StmtId}, +}; +use crate::{Shared, Type, TypeBinding, TypeBindings, TypeVariableKind}; +#[allow(unused)] +pub(crate) struct Interpreter<'interner> { + /// To expand macros the Interpreter may mutate hir nodes within the NodeInterner + interner: &'interner mut NodeInterner, + + /// Each value currently in scope in the interpreter. + /// Each element of the Vec represents a scope with every scope together making + /// up all currently visible definitions. + scopes: Vec>, + + /// True if we've expanded any macros into any functions and will need + /// to redo name resolution & type checking for that function. + changed_functions: HashSet, + + /// True if we've expanded any macros into global scope and will need + /// to redo name resolution & type checking for everything. + changed_globally: bool, + + in_loop: bool, + + /// True if we're currently in a compile-time context. + /// If this is false code is skipped over instead of executed. + in_comptime_context: bool, +} + +#[allow(unused)] +#[derive(Debug, Clone, PartialEq, Eq)] +pub(crate) enum Value { + Unit, + Bool(bool), + Field(FieldElement), + I8(i8), + I32(i32), + I64(i64), + U8(u8), + U32(u32), + U64(u64), + String(Rc), + Function(FuncId, Type), + Closure(HirLambda, Vec, Type), + Tuple(Vec), + Struct(HashMap, Value>, Type), + Pointer(Shared), + Array(Vector, Type), + Slice(Vector, Type), + Code(Rc), +} + +/// The possible errors that can halt the interpreter. +#[allow(unused)] +#[derive(Debug)] +pub(crate) enum InterpreterError { + ArgumentCountMismatch { expected: usize, actual: usize, call_location: Location }, + TypeMismatch { expected: Type, value: Value, location: Location }, + NoValueForId { id: DefinitionId, location: Location }, + IntegerOutOfRangeForType { value: FieldElement, typ: Type, location: Location }, + ErrorNodeEncountered { location: Location }, + NonFunctionCalled { value: Value, location: Location }, + NonBoolUsedInIf { value: Value, location: Location }, + NonBoolUsedInConstrain { value: Value, location: Location }, + FailingConstraint { message: Option, location: Location }, + NoMethodFound { object: Value, typ: Type, location: Location }, + NonIntegerUsedInLoop { value: Value, location: Location }, + NonPointerDereferenced { value: Value, location: Location }, + NonTupleOrStructInMemberAccess { value: Value, location: Location }, + NonArrayIndexed { value: Value, location: Location }, + NonIntegerUsedAsIndex { value: Value, location: Location }, + NonIntegerIntegerLiteral { typ: Type, location: Location }, + NonIntegerArrayLength { typ: Type, location: Location }, + NonNumericCasted { value: Value, location: Location }, + IndexOutOfBounds { index: usize, length: usize, location: Location }, + ExpectedStructToHaveField { value: Value, field_name: String, location: Location }, + TypeUnsupported { typ: Type, location: Location }, + InvalidValueForUnary { value: Value, operator: &'static str, location: Location }, + InvalidValuesForBinary { lhs: Value, rhs: Value, operator: &'static str, location: Location }, + CastToNonNumericType { typ: Type, location: Location }, + + // Perhaps this should be unreachable! due to type checking also preventing this error? + // Currently it and the Continue variant are the only interpreter errors without a Location field + BreakNotInLoop, + ContinueNotInLoop, + + // These cases are not errors but prevent us from running more code + // until the loop can be resumed properly. + Break, + Continue, +} + +#[allow(unused)] +type IResult = std::result::Result; + +#[allow(unused)] +impl<'a> Interpreter<'a> { + pub(crate) fn new(interner: &'a mut NodeInterner) -> Self { + Self { + interner, + scopes: vec![HashMap::default()], + changed_functions: HashSet::default(), + changed_globally: false, + in_loop: false, + in_comptime_context: false, + } + } + + pub(crate) fn call_function( + &mut self, + function: FuncId, + arguments: Vec<(Value, Location)>, + call_location: Location, + ) -> IResult { + let previous_state = self.enter_function(); + + let meta = self.interner.function_meta(&function); + if meta.kind != FunctionKind::Normal { + todo!("Evaluation for {:?} is unimplemented", meta.kind); + } + + if meta.parameters.len() != arguments.len() { + return Err(InterpreterError::ArgumentCountMismatch { + expected: meta.parameters.len(), + actual: arguments.len(), + call_location, + }); + } + + let parameters = meta.parameters.0.clone(); + for ((parameter, typ, _), (argument, arg_location)) in parameters.iter().zip(arguments) { + self.define_pattern(parameter, typ, argument, arg_location)?; + } + + let function_body = self.interner.function(&function).as_expr(); + let result = self.evaluate(function_body)?; + + self.exit_function(previous_state); + Ok(result) + } + + fn call_closure( + &mut self, + closure: HirLambda, + // TODO: How to define environment here? + _environment: Vec, + arguments: Vec<(Value, Location)>, + call_location: Location, + ) -> IResult { + let previous_state = self.enter_function(); + + if closure.parameters.len() != arguments.len() { + return Err(InterpreterError::ArgumentCountMismatch { + expected: closure.parameters.len(), + actual: arguments.len(), + call_location, + }); + } + + let parameters = closure.parameters.iter().zip(arguments); + for ((parameter, typ), (argument, arg_location)) in parameters { + self.define_pattern(parameter, typ, argument, arg_location)?; + } + + let result = self.evaluate(closure.body)?; + + self.exit_function(previous_state); + Ok(result) + } + + /// Enters a function, pushing a new scope and resetting any required state. + /// Returns the previous values of the internal state, to be reset when + /// `exit_function` is called. + fn enter_function(&mut self) -> (bool, Vec>) { + // Drain every scope except the global scope + let scope = self.scopes.drain(1..).collect(); + self.push_scope(); + (std::mem::take(&mut self.in_loop), scope) + } + + fn exit_function(&mut self, mut state: (bool, Vec>)) { + self.in_loop = state.0; + + // Keep only the global scope + self.scopes.truncate(1); + self.scopes.append(&mut state.1); + } + + fn push_scope(&mut self) { + self.scopes.push(HashMap::default()); + } + + fn pop_scope(&mut self) { + self.scopes.pop(); + } + + fn current_scope_mut(&mut self) -> &mut HashMap { + // the global scope is always at index zero, so this is always Some + self.scopes.last_mut().unwrap() + } + + fn define_pattern( + &mut self, + pattern: &HirPattern, + typ: &Type, + argument: Value, + location: Location, + ) -> IResult<()> { + match pattern { + HirPattern::Identifier(identifier) => { + self.define(identifier.id, typ, argument, location) + } + HirPattern::Mutable(pattern, _) => { + self.define_pattern(pattern, typ, argument, location) + } + HirPattern::Tuple(pattern_fields, _) => match (argument, typ) { + (Value::Tuple(fields), Type::Tuple(type_fields)) + if fields.len() == pattern_fields.len() => + { + for ((pattern, typ), argument) in + pattern_fields.iter().zip(type_fields).zip(fields) + { + self.define_pattern(pattern, typ, argument, location)?; + } + Ok(()) + } + (value, _) => { + Err(InterpreterError::TypeMismatch { expected: typ.clone(), value, location }) + } + }, + HirPattern::Struct(struct_type, pattern_fields, _) => { + self.type_check(typ, &argument, location)?; + self.type_check(struct_type, &argument, location)?; + + match argument { + Value::Struct(fields, struct_type) if fields.len() == pattern_fields.len() => { + for (field_name, field_pattern) in pattern_fields { + let field = fields.get(&field_name.0.contents).ok_or_else(|| { + InterpreterError::ExpectedStructToHaveField { + value: Value::Struct(fields.clone(), struct_type.clone()), + field_name: field_name.0.contents.clone(), + location, + } + })?; + + let field_type = field.get_type().into_owned(); + self.define_pattern( + field_pattern, + &field_type, + field.clone(), + location, + )?; + } + Ok(()) + } + value => Err(InterpreterError::TypeMismatch { + expected: typ.clone(), + value, + location, + }), + } + } + } + } + + /// Define a new variable in the current scope + fn define( + &mut self, + id: DefinitionId, + typ: &Type, + argument: Value, + location: Location, + ) -> IResult<()> { + self.type_check(typ, &argument, location)?; + self.current_scope_mut().insert(id, argument); + Ok(()) + } + + /// Mutate an existing variable, potentially from a prior scope. + /// Also type checks the value being assigned + fn checked_mutate( + &mut self, + id: DefinitionId, + typ: &Type, + argument: Value, + location: Location, + ) -> IResult<()> { + self.type_check(typ, &argument, location)?; + for scope in self.scopes.iter_mut().rev() { + if let Entry::Occupied(mut entry) = scope.entry(id) { + entry.insert(argument); + return Ok(()); + } + } + Err(InterpreterError::NoValueForId { id, location }) + } + + /// Mutate an existing variable, potentially from a prior scope + fn mutate(&mut self, id: DefinitionId, argument: Value, location: Location) -> IResult<()> { + for scope in self.scopes.iter_mut().rev() { + if let Entry::Occupied(mut entry) = scope.entry(id) { + entry.insert(argument); + return Ok(()); + } + } + Err(InterpreterError::NoValueForId { id, location }) + } + + fn lookup(&self, ident: &HirIdent) -> IResult { + for scope in self.scopes.iter().rev() { + if let Some(value) = scope.get(&ident.id) { + return Ok(value.clone()); + } + } + + Err(InterpreterError::NoValueForId { id: ident.id, location: ident.location }) + } + + fn lookup_id(&self, id: DefinitionId, location: Location) -> IResult { + for scope in self.scopes.iter().rev() { + if let Some(value) = scope.get(&id) { + return Ok(value.clone()); + } + } + + Err(InterpreterError::NoValueForId { id, location }) + } + + fn type_check(&self, typ: &Type, value: &Value, location: Location) -> IResult<()> { + let typ = typ.follow_bindings(); + let value_type = value.get_type(); + + typ.try_unify(&value_type, &mut TypeBindings::new()).map_err(|_| { + InterpreterError::TypeMismatch { expected: typ, value: value.clone(), location } + }) + } + + /// Evaluate an expression and return the result + fn evaluate(&mut self, id: ExprId) -> IResult { + match self.interner.expression(&id) { + HirExpression::Ident(ident) => self.evaluate_ident(ident, id), + HirExpression::Literal(literal) => self.evaluate_literal(literal, id), + HirExpression::Block(block) => self.evaluate_block(block), + HirExpression::Prefix(prefix) => self.evaluate_prefix(prefix, id), + HirExpression::Infix(infix) => self.evaluate_infix(infix, id), + HirExpression::Index(index) => self.evaluate_index(index, id), + HirExpression::Constructor(constructor) => self.evaluate_constructor(constructor, id), + HirExpression::MemberAccess(access) => self.evaluate_access(access, id), + HirExpression::Call(call) => self.evaluate_call(call, id), + HirExpression::MethodCall(call) => self.evaluate_method_call(call, id), + HirExpression::Cast(cast) => self.evaluate_cast(cast, id), + HirExpression::If(if_) => self.evaluate_if(if_, id), + HirExpression::Tuple(tuple) => self.evaluate_tuple(tuple), + HirExpression::Lambda(lambda) => self.evaluate_lambda(lambda, id), + HirExpression::Quote(block) => Ok(Value::Code(Rc::new(block))), + HirExpression::Error => { + let location = self.interner.expr_location(&id); + Err(InterpreterError::ErrorNodeEncountered { location }) + } + } + } + + fn evaluate_ident(&mut self, ident: HirIdent, id: ExprId) -> IResult { + let definition = self.interner.definition(ident.id); + + match &definition.kind { + DefinitionKind::Function(function_id) => { + let typ = self.interner.id_type(id); + Ok(Value::Function(*function_id, typ)) + } + DefinitionKind::Local(_) => dbg!(self.lookup(&ident)), + DefinitionKind::Global(global_id) => { + let let_ = self.interner.get_global_let_statement(*global_id).unwrap(); + self.evaluate_let(let_)?; + self.lookup(&ident) + } + DefinitionKind::GenericType(type_variable) => { + let value = match &*type_variable.borrow() { + TypeBinding::Unbound(_) => None, + TypeBinding::Bound(binding) => binding.evaluate_to_u64(), + }; + + if let Some(value) = value { + let typ = self.interner.id_type(id); + self.evaluate_integer((value as u128).into(), false, id) + } else { + let location = self.interner.expr_location(&id); + let typ = Type::TypeVariable(type_variable.clone(), TypeVariableKind::Normal); + Err(InterpreterError::NonIntegerArrayLength { typ, location }) + } + } + } + } + + fn evaluate_literal(&mut self, literal: HirLiteral, id: ExprId) -> IResult { + match literal { + HirLiteral::Unit => Ok(Value::Unit), + HirLiteral::Bool(value) => Ok(Value::Bool(value)), + HirLiteral::Integer(value, is_negative) => { + self.evaluate_integer(value, is_negative, id) + } + HirLiteral::Str(string) => Ok(Value::String(Rc::new(string))), + HirLiteral::FmtStr(_, _) => todo!("Evaluate format strings"), + HirLiteral::Array(array) => self.evaluate_array(array, id), + HirLiteral::Slice(array) => self.evaluate_slice(array, id), + } + } + + fn evaluate_integer( + &self, + value: FieldElement, + is_negative: bool, + id: ExprId, + ) -> IResult { + let typ = self.interner.id_type(id).follow_bindings(); + let location = self.interner.expr_location(&id); + + if let Type::FieldElement = &typ { + Ok(Value::Field(value)) + } else if let Type::Integer(sign, bit_size) = &typ { + match (sign, bit_size) { + (Signedness::Unsigned, IntegerBitSize::One) => { + return Err(InterpreterError::TypeUnsupported { typ, location }); + } + (Signedness::Unsigned, IntegerBitSize::Eight) => { + let value: u8 = + value.try_to_u64().and_then(|value| value.try_into().ok()).ok_or( + InterpreterError::IntegerOutOfRangeForType { value, typ, location }, + )?; + let value = if is_negative { 0u8.wrapping_sub(value) } else { value }; + Ok(Value::U8(value)) + } + (Signedness::Unsigned, IntegerBitSize::ThirtyTwo) => { + let value: u32 = + value.try_to_u64().and_then(|value| value.try_into().ok()).ok_or( + InterpreterError::IntegerOutOfRangeForType { value, typ, location }, + )?; + let value = if is_negative { 0u32.wrapping_sub(value) } else { value }; + Ok(Value::U32(value)) + } + (Signedness::Unsigned, IntegerBitSize::SixtyFour) => { + let value: u64 = + value.try_to_u64().ok_or(InterpreterError::IntegerOutOfRangeForType { + value, + typ, + location, + })?; + let value = if is_negative { 0u64.wrapping_sub(value) } else { value }; + Ok(Value::U64(value)) + } + (Signedness::Signed, IntegerBitSize::One) => { + return Err(InterpreterError::TypeUnsupported { typ, location }); + } + (Signedness::Signed, IntegerBitSize::Eight) => { + let value: i8 = + value.try_to_u64().and_then(|value| value.try_into().ok()).ok_or( + InterpreterError::IntegerOutOfRangeForType { value, typ, location }, + )?; + let value = if is_negative { -value } else { value }; + Ok(Value::I8(value)) + } + (Signedness::Signed, IntegerBitSize::ThirtyTwo) => { + let value: i32 = + value.try_to_u64().and_then(|value| value.try_into().ok()).ok_or( + InterpreterError::IntegerOutOfRangeForType { value, typ, location }, + )?; + let value = if is_negative { -value } else { value }; + Ok(Value::I32(value)) + } + (Signedness::Signed, IntegerBitSize::SixtyFour) => { + let value: i64 = + value.try_to_u64().and_then(|value| value.try_into().ok()).ok_or( + InterpreterError::IntegerOutOfRangeForType { value, typ, location }, + )?; + let value = if is_negative { -value } else { value }; + Ok(Value::I64(value)) + } + } + } else { + Err(InterpreterError::NonIntegerIntegerLiteral { typ, location }) + } + } + + fn evaluate_block(&mut self, mut block: HirBlockExpression) -> IResult { + let last_statement = block.statements.pop(); + self.push_scope(); + + for statement in block.statements { + self.evaluate_statement(statement)?; + } + + let result = if let Some(statement) = last_statement { + self.evaluate_statement(statement) + } else { + Ok(Value::Unit) + }; + + self.pop_scope(); + result + } + + fn evaluate_array(&mut self, array: HirArrayLiteral, id: ExprId) -> IResult { + let typ = self.interner.id_type(id); + + match array { + HirArrayLiteral::Standard(elements) => { + let elements = elements + .into_iter() + .map(|id| self.evaluate(id)) + .collect::>>()?; + + Ok(Value::Array(elements, typ)) + } + HirArrayLiteral::Repeated { repeated_element, length } => { + let element = self.evaluate(repeated_element)?; + + if let Some(length) = length.evaluate_to_u64() { + let elements = (0..length).map(|_| element.clone()).collect(); + Ok(Value::Array(elements, typ)) + } else { + let location = self.interner.expr_location(&id); + Err(InterpreterError::NonIntegerArrayLength { typ: length, location }) + } + } + } + } + + fn evaluate_slice(&mut self, array: HirArrayLiteral, id: ExprId) -> IResult { + self.evaluate_array(array, id).map(|value| match value { + Value::Array(array, typ) => Value::Slice(array, typ), + other => unreachable!("Non-array value returned from evaluate array: {other:?}"), + }) + } + + fn evaluate_prefix(&mut self, prefix: HirPrefixExpression, id: ExprId) -> IResult { + let rhs = self.evaluate(prefix.rhs)?; + match prefix.operator { + crate::ast::UnaryOp::Minus => match rhs { + Value::Field(value) => Ok(Value::Field(FieldElement::zero() - value)), + Value::I8(value) => Ok(Value::I8(-value)), + Value::I32(value) => Ok(Value::I32(-value)), + Value::I64(value) => Ok(Value::I64(-value)), + Value::U8(value) => Ok(Value::U8(0 - value)), + Value::U32(value) => Ok(Value::U32(0 - value)), + Value::U64(value) => Ok(Value::U64(0 - value)), + value => { + let location = self.interner.expr_location(&id); + Err(InterpreterError::InvalidValueForUnary { + value, + location, + operator: "minus", + }) + } + }, + crate::ast::UnaryOp::Not => match rhs { + Value::Bool(value) => Ok(Value::Bool(!value)), + Value::I8(value) => Ok(Value::I8(!value)), + Value::I32(value) => Ok(Value::I32(!value)), + Value::I64(value) => Ok(Value::I64(!value)), + Value::U8(value) => Ok(Value::U8(!value)), + Value::U32(value) => Ok(Value::U32(!value)), + Value::U64(value) => Ok(Value::U64(!value)), + value => { + let location = self.interner.expr_location(&id); + Err(InterpreterError::InvalidValueForUnary { value, location, operator: "not" }) + } + }, + crate::ast::UnaryOp::MutableReference => Ok(Value::Pointer(Shared::new(rhs))), + crate::ast::UnaryOp::Dereference { implicitly_added: _ } => match rhs { + Value::Pointer(element) => Ok(element.borrow().clone()), + value => { + let location = self.interner.expr_location(&id); + Err(InterpreterError::NonPointerDereferenced { value, location }) + } + }, + } + } + + fn evaluate_infix(&mut self, infix: HirInfixExpression, id: ExprId) -> IResult { + let lhs = self.evaluate(infix.lhs)?; + let rhs = self.evaluate(infix.rhs)?; + + // TODO: Need to account for operator overloading + assert!( + self.interner.get_selected_impl_for_expression(id).is_none(), + "Operator overloading is unimplemented in the interpreter" + ); + + use InterpreterError::InvalidValuesForBinary; + match infix.operator.kind { + BinaryOpKind::Add => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs + rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs + rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs + rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs + rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs + rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs + rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs + rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "+" }) + } + }, + BinaryOpKind::Subtract => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs - rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs - rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs - rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs - rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs - rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs - rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs - rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "-" }) + } + }, + BinaryOpKind::Multiply => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs * rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs * rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs * rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs * rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs * rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs * rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs * rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "*" }) + } + }, + BinaryOpKind::Divide => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Field(lhs / rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs / rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs / rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs / rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs / rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs / rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs / rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "/" }) + } + }, + BinaryOpKind::Equal => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs == rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs == rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::Bool(lhs == rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::Bool(lhs == rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::Bool(lhs == rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs == rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs == rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "==" }) + } + }, + BinaryOpKind::NotEqual => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs != rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs != rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::Bool(lhs != rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::Bool(lhs != rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::Bool(lhs != rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs != rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs != rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "!=" }) + } + }, + BinaryOpKind::Less => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs < rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs < rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::Bool(lhs < rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::Bool(lhs < rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::Bool(lhs < rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs < rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs < rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<" }) + } + }, + BinaryOpKind::LessEqual => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs <= rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs <= rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::Bool(lhs <= rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::Bool(lhs <= rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::Bool(lhs <= rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs <= rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs <= rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<=" }) + } + }, + BinaryOpKind::Greater => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs > rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs > rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::Bool(lhs > rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::Bool(lhs > rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::Bool(lhs > rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs > rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs > rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">" }) + } + }, + BinaryOpKind::GreaterEqual => match (lhs, rhs) { + (Value::Field(lhs), Value::Field(rhs)) => Ok(Value::Bool(lhs >= rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::Bool(lhs >= rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::Bool(lhs >= rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::Bool(lhs >= rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::Bool(lhs >= rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::Bool(lhs >= rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::Bool(lhs >= rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">=" }) + } + }, + BinaryOpKind::And => match (lhs, rhs) { + (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs & rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs & rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs & rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs & rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs & rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs & rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs & rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "&" }) + } + }, + BinaryOpKind::Or => match (lhs, rhs) { + (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs | rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs | rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs | rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs | rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs | rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs | rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs | rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "|" }) + } + }, + BinaryOpKind::Xor => match (lhs, rhs) { + (Value::Bool(lhs), Value::Bool(rhs)) => Ok(Value::Bool(lhs ^ rhs)), + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs ^ rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs ^ rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs ^ rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs ^ rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs ^ rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs ^ rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "^" }) + } + }, + BinaryOpKind::ShiftRight => match (lhs, rhs) { + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs >> rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs >> rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs >> rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs >> rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs >> rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs >> rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: ">>" }) + } + }, + BinaryOpKind::ShiftLeft => match (lhs, rhs) { + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs << rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs << rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs << rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs << rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs << rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs << rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "<<" }) + } + }, + BinaryOpKind::Modulo => match (lhs, rhs) { + (Value::I8(lhs), Value::I8(rhs)) => Ok(Value::I8(lhs % rhs)), + (Value::I32(lhs), Value::I32(rhs)) => Ok(Value::I32(lhs % rhs)), + (Value::I64(lhs), Value::I64(rhs)) => Ok(Value::I64(lhs % rhs)), + (Value::U8(lhs), Value::U8(rhs)) => Ok(Value::U8(lhs % rhs)), + (Value::U32(lhs), Value::U32(rhs)) => Ok(Value::U32(lhs % rhs)), + (Value::U64(lhs), Value::U64(rhs)) => Ok(Value::U64(lhs % rhs)), + (lhs, rhs) => { + let location = self.interner.expr_location(&id); + Err(InvalidValuesForBinary { lhs, rhs, location, operator: "%" }) + } + }, + } + } + + fn evaluate_index(&mut self, index: HirIndexExpression, id: ExprId) -> IResult { + let array = self.evaluate(index.collection)?; + let index = self.evaluate(index.index)?; + + let location = self.interner.expr_location(&id); + let (array, index) = self.bounds_check(array, index, location)?; + + Ok(array[index].clone()) + } + + /// Bounds check the given array and index pair. + /// This will also ensure the given arguments are in fact an array and integer. + fn bounds_check( + &self, + array: Value, + index: Value, + location: Location, + ) -> IResult<(Vector, usize)> { + let collection = match array { + Value::Array(array, _) => array, + Value::Slice(array, _) => array, + value => { + return Err(InterpreterError::NonArrayIndexed { value, location }); + } + }; + + let index = match index { + Value::Field(value) => { + value.try_to_u64().expect("index could not fit into u64") as usize + } + Value::I8(value) => value as usize, + Value::I32(value) => value as usize, + Value::I64(value) => value as usize, + Value::U8(value) => value as usize, + Value::U32(value) => value as usize, + Value::U64(value) => value as usize, + value => { + return Err(InterpreterError::NonIntegerUsedAsIndex { value, location }); + } + }; + + if index >= collection.len() { + use InterpreterError::IndexOutOfBounds; + return Err(IndexOutOfBounds { index, location, length: collection.len() }); + } + + Ok((collection, index)) + } + + fn evaluate_constructor( + &mut self, + constructor: HirConstructorExpression, + id: ExprId, + ) -> IResult { + let fields = constructor + .fields + .into_iter() + .map(|(name, expr)| { + let field_value = self.evaluate(expr)?; + Ok((Rc::new(name.0.contents), field_value)) + }) + .collect::>()?; + + let typ = self.interner.id_type(id); + Ok(Value::Struct(fields, typ)) + } + + fn evaluate_access(&mut self, access: HirMemberAccess, id: ExprId) -> IResult { + let (fields, struct_type) = match self.evaluate(access.lhs)? { + Value::Struct(fields, typ) => (fields, typ), + value => { + let location = self.interner.expr_location(&id); + return Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }); + } + }; + + fields.get(&access.rhs.0.contents).cloned().ok_or_else(|| { + let location = self.interner.expr_location(&id); + let value = Value::Struct(fields, struct_type); + let field_name = access.rhs.0.contents; + InterpreterError::ExpectedStructToHaveField { value, field_name, location } + }) + } + + fn evaluate_call(&mut self, call: HirCallExpression, id: ExprId) -> IResult { + let function = self.evaluate(call.func)?; + let arguments = try_vecmap(call.arguments, |arg| { + Ok((self.evaluate(arg)?, self.interner.expr_location(&arg))) + })?; + let location = self.interner.expr_location(&id); + + match function { + Value::Function(function_id, _) => self.call_function(function_id, arguments, location), + Value::Closure(closure, env, _) => self.call_closure(closure, env, arguments, location), + value => Err(InterpreterError::NonFunctionCalled { value, location }), + } + } + + fn evaluate_method_call( + &mut self, + call: HirMethodCallExpression, + id: ExprId, + ) -> IResult { + let object = self.evaluate(call.object)?; + let arguments = try_vecmap(call.arguments, |arg| { + Ok((self.evaluate(arg)?, self.interner.expr_location(&arg))) + })?; + let location = self.interner.expr_location(&id); + + let typ = object.get_type().follow_bindings(); + let method_name = &call.method.0.contents; + + // TODO: Traits + let method = match &typ { + Type::Struct(struct_def, _) => { + self.interner.lookup_method(&typ, struct_def.borrow().id, method_name, false) + } + _ => self.interner.lookup_primitive_method(&typ, method_name), + }; + + if let Some(method) = method { + self.call_function(method, arguments, location) + } else { + Err(InterpreterError::NoMethodFound { object, typ, location }) + } + } + + fn evaluate_cast(&mut self, cast: HirCastExpression, id: ExprId) -> IResult { + macro_rules! signed_int_to_field { + ($x:expr) => {{ + // Need to convert the signed integer to an i128 before + // we negate it to preserve the MIN value. + let mut value = $x as i128; + let is_negative = value < 0; + if is_negative { + value = -value; + } + ((value as u128).into(), is_negative) + }}; + } + + let (mut lhs, lhs_is_negative) = match self.evaluate(cast.lhs)? { + Value::Field(value) => (value, false), + Value::U8(value) => ((value as u128).into(), false), + Value::U32(value) => ((value as u128).into(), false), + Value::U64(value) => ((value as u128).into(), false), + Value::I8(value) => signed_int_to_field!(value), + Value::I32(value) => signed_int_to_field!(value), + Value::I64(value) => signed_int_to_field!(value), + Value::Bool(value) => { + (if value { FieldElement::one() } else { FieldElement::zero() }, false) + } + value => { + let location = self.interner.expr_location(&id); + return Err(InterpreterError::NonNumericCasted { value, location }); + } + }; + + macro_rules! cast_to_int { + ($x:expr, $method:ident, $typ:ty, $f:ident) => {{ + let mut value = $x.$method() as $typ; + if lhs_is_negative { + value = 0 - value; + } + Ok(Value::$f(value)) + }}; + } + + // Now actually cast the lhs, bit casting and wrapping as necessary + match cast.r#type.follow_bindings() { + Type::FieldElement => { + if lhs_is_negative { + lhs = FieldElement::zero() - lhs; + } + Ok(Value::Field(lhs)) + } + Type::Integer(sign, bit_size) => match (sign, bit_size) { + (Signedness::Unsigned, IntegerBitSize::One) => { + let location = self.interner.expr_location(&id); + Err(InterpreterError::TypeUnsupported { typ: cast.r#type, location }) + } + (Signedness::Unsigned, IntegerBitSize::Eight) => cast_to_int!(lhs, to_u128, u8, U8), + (Signedness::Unsigned, IntegerBitSize::ThirtyTwo) => { + cast_to_int!(lhs, to_u128, u32, U32) + } + (Signedness::Unsigned, IntegerBitSize::SixtyFour) => { + cast_to_int!(lhs, to_u128, u64, U64) + } + (Signedness::Signed, IntegerBitSize::One) => { + let location = self.interner.expr_location(&id); + Err(InterpreterError::TypeUnsupported { typ: cast.r#type, location }) + } + (Signedness::Signed, IntegerBitSize::Eight) => cast_to_int!(lhs, to_i128, i8, I8), + (Signedness::Signed, IntegerBitSize::ThirtyTwo) => { + cast_to_int!(lhs, to_i128, i32, I32) + } + (Signedness::Signed, IntegerBitSize::SixtyFour) => { + cast_to_int!(lhs, to_i128, i64, I64) + } + }, + Type::Bool => Ok(Value::Bool(!lhs.is_zero() || lhs_is_negative)), + typ => { + let location = self.interner.expr_location(&id); + Err(InterpreterError::CastToNonNumericType { typ, location }) + } + } + } + + fn evaluate_if(&mut self, if_: HirIfExpression, id: ExprId) -> IResult { + let condition = match self.evaluate(if_.condition)? { + Value::Bool(value) => value, + value => { + let location = self.interner.expr_location(&id); + return Err(InterpreterError::NonBoolUsedInIf { value, location }); + } + }; + + self.push_scope(); + + let result = if condition { + if if_.alternative.is_some() { + self.evaluate(if_.consequence) + } else { + self.evaluate(if_.consequence)?; + Ok(Value::Unit) + } + } else { + match if_.alternative { + Some(alternative) => self.evaluate(alternative), + None => Ok(Value::Unit), + } + }; + + self.pop_scope(); + result + } + + fn evaluate_tuple(&mut self, tuple: Vec) -> IResult { + let fields = try_vecmap(tuple, |field| self.evaluate(field))?; + Ok(Value::Tuple(fields)) + } + + fn evaluate_lambda(&mut self, lambda: HirLambda, id: ExprId) -> IResult { + let location = self.interner.expr_location(&id); + let environment = + try_vecmap(&lambda.captures, |capture| self.lookup_id(capture.ident.id, location))?; + + let typ = self.interner.id_type(id); + Ok(Value::Closure(lambda, environment, typ)) + } + + fn evaluate_statement(&mut self, statement: StmtId) -> IResult { + match self.interner.statement(&statement) { + HirStatement::Let(let_) => self.evaluate_let(let_), + HirStatement::Constrain(constrain) => self.evaluate_constrain(constrain), + HirStatement::Assign(assign) => self.evaluate_assign(assign), + HirStatement::For(for_) => self.evaluate_for(for_), + HirStatement::Break => self.evaluate_break(), + HirStatement::Continue => self.evaluate_continue(), + HirStatement::Expression(expression) => self.evaluate(expression), + HirStatement::Comptime(statement) => self.evaluate_comptime(statement), + HirStatement::Semi(expression) => { + self.evaluate(expression)?; + Ok(Value::Unit) + } + HirStatement::Error => { + let location = self.interner.id_location(statement); + Err(InterpreterError::ErrorNodeEncountered { location }) + } + } + } + + fn evaluate_let(&mut self, let_: HirLetStatement) -> IResult { + let rhs = self.evaluate(let_.expression)?; + let location = self.interner.expr_location(&let_.expression); + self.define_pattern(&let_.pattern, &let_.r#type, rhs, location)?; + Ok(Value::Unit) + } + + fn evaluate_constrain(&mut self, constrain: HirConstrainStatement) -> IResult { + match self.evaluate(constrain.0)? { + Value::Bool(true) => Ok(Value::Unit), + Value::Bool(false) => { + let location = self.interner.expr_location(&constrain.0); + let message = constrain.2.and_then(|expr| self.evaluate(expr).ok()); + Err(InterpreterError::FailingConstraint { location, message }) + } + value => { + let location = self.interner.expr_location(&constrain.0); + Err(InterpreterError::NonBoolUsedInConstrain { value, location }) + } + } + } + + fn evaluate_assign(&mut self, assign: HirAssignStatement) -> IResult { + let rhs = self.evaluate(assign.expression)?; + self.store_lvalue(assign.lvalue, rhs)?; + Ok(Value::Unit) + } + + fn store_lvalue(&mut self, lvalue: HirLValue, rhs: Value) -> IResult<()> { + match lvalue { + HirLValue::Ident(ident, typ) => { + self.checked_mutate(ident.id, &typ, rhs, ident.location) + } + HirLValue::Dereference { lvalue, element_type: _, location } => { + match self.evaluate_lvalue(&lvalue)? { + Value::Pointer(value) => { + *value.borrow_mut() = rhs; + Ok(()) + } + value => Err(InterpreterError::NonPointerDereferenced { value, location }), + } + } + HirLValue::MemberAccess { object, field_name, field_index, typ: _, location } => { + let index = field_index.expect("The field index should be set after type checking"); + match self.evaluate_lvalue(&object)? { + Value::Tuple(mut fields) => { + fields[index] = rhs; + self.store_lvalue(*object, Value::Tuple(fields)) + } + Value::Struct(mut fields, typ) => { + fields.insert(Rc::new(field_name.0.contents), rhs); + self.store_lvalue(*object, Value::Struct(fields, typ)) + } + value => { + Err(InterpreterError::NonTupleOrStructInMemberAccess { value, location }) + } + } + } + HirLValue::Index { array, index, typ: _, location } => { + let array_value = self.evaluate_lvalue(&array)?; + let index = self.evaluate(index)?; + + let constructor = match &array_value { + Value::Array(..) => Value::Array, + _ => Value::Slice, + }; + + let typ = array_value.get_type().into_owned(); + let (elements, index) = self.bounds_check(array_value, index, location)?; + + let new_array = constructor(elements.update(index, rhs), typ); + self.store_lvalue(*array, new_array) + } + } + } + + fn evaluate_lvalue(&mut self, lvalue: &HirLValue) -> IResult { + match lvalue { + HirLValue::Ident(ident, _) => self.lookup(ident), + HirLValue::Dereference { lvalue, element_type: _, location } => { + match self.evaluate_lvalue(lvalue)? { + Value::Pointer(value) => Ok(value.borrow().clone()), + value => { + Err(InterpreterError::NonPointerDereferenced { value, location: *location }) + } + } + } + HirLValue::MemberAccess { object, field_name, field_index, typ: _, location } => { + let index = field_index.expect("The field index should be set after type checking"); + + match self.evaluate_lvalue(object)? { + Value::Tuple(mut values) => Ok(values.swap_remove(index)), + Value::Struct(fields, _) => Ok(fields[&field_name.0.contents].clone()), + value => Err(InterpreterError::NonTupleOrStructInMemberAccess { + value, + location: *location, + }), + } + } + HirLValue::Index { array, index, typ: _, location } => { + let array = self.evaluate_lvalue(array)?; + let index = self.evaluate(*index)?; + let (elements, index) = self.bounds_check(array, index, *location)?; + Ok(elements[index].clone()) + } + } + } + + fn evaluate_for(&mut self, for_: HirForStatement) -> IResult { + // i128 can store all values from i8 - u64 + let get_index = |this: &mut Self, expr| -> IResult<(_, fn(_) -> _)> { + match this.evaluate(expr)? { + Value::I8(value) => Ok((value as i128, |i| Value::I8(i as i8))), + Value::I32(value) => Ok((value as i128, |i| Value::I32(i as i32))), + Value::I64(value) => Ok((value as i128, |i| Value::I64(i as i64))), + Value::U8(value) => Ok((value as i128, |i| Value::U8(i as u8))), + Value::U32(value) => Ok((value as i128, |i| Value::U32(i as u32))), + Value::U64(value) => Ok((value as i128, |i| Value::U64(i as u64))), + value => { + let location = this.interner.expr_location(&expr); + Err(InterpreterError::NonIntegerUsedInLoop { value, location }) + } + } + }; + + let (start, make_value) = get_index(self, for_.start_range)?; + let (end, _) = get_index(self, for_.end_range)?; + let was_in_loop = std::mem::replace(&mut self.in_loop, true); + + for i in start..end { + self.push_scope(); + self.current_scope_mut().insert(for_.identifier.id, make_value(i)); + + match self.evaluate(for_.block) { + Ok(_) => (), + Err(InterpreterError::Break) => break, + Err(InterpreterError::Continue) => continue, + Err(other) => return Err(other), + } + self.pop_scope(); + } + + self.in_loop = was_in_loop; + Ok(Value::Unit) + } + + fn evaluate_break(&mut self) -> IResult { + if self.in_loop { + Err(InterpreterError::Break) + } else { + Err(InterpreterError::BreakNotInLoop) + } + } + + fn evaluate_continue(&mut self) -> IResult { + if self.in_loop { + Err(InterpreterError::Continue) + } else { + Err(InterpreterError::ContinueNotInLoop) + } + } + + fn evaluate_comptime(&mut self, statement: StmtId) -> IResult { + let was_in_comptime = std::mem::replace(&mut self.in_comptime_context, true); + let result = self.evaluate_statement(statement); + self.in_comptime_context = was_in_comptime; + result + } +} + +impl Value { + fn get_type(&self) -> Cow { + Cow::Owned(match self { + Value::Unit => Type::Unit, + Value::Bool(_) => Type::Bool, + Value::Field(_) => Type::FieldElement, + Value::I8(_) => Type::Integer(Signedness::Signed, IntegerBitSize::Eight), + Value::I32(_) => Type::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo), + Value::I64(_) => Type::Integer(Signedness::Signed, IntegerBitSize::SixtyFour), + Value::U8(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), + Value::U32(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), + Value::U64(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::SixtyFour), + Value::String(value) => { + let length = Type::Constant(value.len() as u64); + Type::String(Box::new(length)) + } + Value::Function(_, typ) => return Cow::Borrowed(typ), + Value::Closure(_, _, typ) => return Cow::Borrowed(typ), + Value::Tuple(fields) => { + Type::Tuple(vecmap(fields, |field| field.get_type().into_owned())) + } + Value::Struct(_, typ) => return Cow::Borrowed(typ), + Value::Array(_, typ) => return Cow::Borrowed(typ), + Value::Slice(_, typ) => return Cow::Borrowed(typ), + Value::Code(_) => Type::Code, + Value::Pointer(element) => { + let element = element.borrow().get_type().into_owned(); + Type::MutableReference(Box::new(element)) + } + }) + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs index 91621c857cf..83aaddaa405 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/mod.rs @@ -1 +1,3 @@ mod hir_to_ast; +mod interpreter; +mod tests; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs new file mode 100644 index 00000000000..016e7079886 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -0,0 +1,166 @@ +#![cfg(test)] + +use noirc_errors::Location; + +use super::interpreter::{Interpreter, InterpreterError, Value}; +use crate::hir::type_check::test::type_check_src_code; + +fn interpret_helper(src: &str, func_namespace: Vec) -> Result { + let (mut interner, main_id) = type_check_src_code(src, func_namespace); + let mut interpreter = Interpreter::new(&mut interner); + + let no_location = Location::dummy(); + interpreter.call_function(main_id, Vec::new(), no_location) +} + +fn interpret(src: &str, func_namespace: Vec) -> Value { + interpret_helper(src, func_namespace).unwrap_or_else(|error| { + panic!("Expected interpreter to exit successfully, but found {error:?}") + }) +} + +fn interpret_expect_error(src: &str, func_namespace: Vec) -> InterpreterError { + interpret_helper(src, func_namespace).expect_err("Expected interpreter to error") +} + +#[test] +fn interpreter_works() { + let program = "fn main() -> pub Field { 3 }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::Field(3u128.into())); +} + +#[test] +fn mutation_works() { + let program = "fn main() -> pub i8 { + let mut x = 3; + x = 4; + x + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::I8(4)); +} + +#[test] +fn mutating_references() { + let program = "fn main() -> pub i32 { + let x = &mut 3; + *x = 4; + *x + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::I32(4)); +} + +#[test] +fn mutating_mutable_references() { + let program = "fn main() -> pub i64 { + let mut x = &mut 3; + *x = 4; + *x + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::I64(4)); +} + +#[test] +fn mutating_arrays() { + let program = "fn main() -> pub u8 { + let mut a1 = [1, 2, 3, 4]; + a1[1] = 22; + a1[1] + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::U8(22)); +} + +#[test] +fn for_loop() { + let program = "fn main() -> pub u8 { + let mut x = 0; + for i in 0 .. 6 { + x += i; + } + x + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::U8(15)); +} + +#[test] +fn for_loop_with_break() { + let program = "unconstrained fn main() -> pub u32 { + let mut x = 0; + for i in 0 .. 6 { + if i == 4 { + break; + } + x += i; + } + x + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::U32(6)); +} + +#[test] +fn for_loop_with_continue() { + let program = "unconstrained fn main() -> pub u64 { + let mut x = 0; + for i in 0 .. 6 { + if i == 4 { + continue; + } + x += i; + } + x + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::U64(11)); +} + +#[test] +fn assert() { + let program = "fn main() { + assert(1 == 1); + }"; + let result = interpret(program, vec!["main".into()]); + assert_eq!(result, Value::Unit); +} + +#[test] +fn assert_fail() { + let program = "fn main() { + assert(1 == 2); + }"; + let result = interpret_expect_error(program, vec!["main".into()]); + assert!(matches!(result, InterpreterError::FailingConstraint { .. })); +} + +#[test] +fn lambda() { + let program = "fn main() -> pub u8 { + let f = |x: u8| x + 1; + f(1) + }"; + let result = interpret(program, vec!["main".into()]); + assert!(matches!(result, Value::U8(2))); +} + +#[test] +fn non_deterministic_recursion() { + let program = " + fn main() -> pub u64 { + fib(10) + } + + fn fib(x: u64) -> u64 { + if x <= 1 { + x + } else { + fib(x - 1) + fib(x - 2) + } + }"; + let result = interpret(program, vec!["main".into(), "fib".into()]); + assert_eq!(result, Value::U64(55)); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 463b8a4b329..4c6b5ab5885 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -18,11 +18,11 @@ use crate::hir::Context; use crate::macros_api::{MacroError, MacroProcessor}; use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId, TraitId, TypeAliasId}; -use crate::parser::{ParserError, SortedModule}; -use crate::{ +use crate::ast::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, NoirTypeAlias, Path, PathKind, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, }; +use crate::parser::{ParserError, SortedModule}; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{CustomDiagnostic, Span}; @@ -398,11 +398,11 @@ fn inject_prelude( ) { let segments: Vec<_> = "std::prelude" .split("::") - .map(|segment| crate::Ident::new(segment.into(), Span::default())) + .map(|segment| crate::ast::Ident::new(segment.into(), Span::default())) .collect(); let path = - Path { segments: segments.clone(), kind: crate::PathKind::Dep, span: Span::default() }; + Path { segments: segments.clone(), kind: crate::ast::PathKind::Dep, span: Span::default() }; if !crate_id.is_stdlib() { if let Ok(PathResolution { module_def_id, error }) = path_resolver::resolve_path( diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 6fbb3b67546..baedb109967 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -4,14 +4,16 @@ use acvm::acir::acir_field::FieldOptions; use fm::{FileId, FileManager, FILE_EXTENSION}; use noirc_errors::Location; +use crate::ast::{ + FunctionDefinition, Ident, ItemVisibility, LetStatement, ModuleDeclaration, NoirFunction, + NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, TraitImplItem, TraitItem, TypeImpl, +}; use crate::{ graph::CrateId, hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, macros_api::MacroProcessor, node_interner::{FunctionModifiers, TraitId, TypeAliasId}, parser::{SortedModule, SortedSubModule}, - FunctionDefinition, Ident, ItemVisibility, LetStatement, ModuleDeclaration, NoirFunction, - NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, TraitImplItem, TraitItem, TypeImpl, }; use super::{ @@ -416,6 +418,7 @@ impl<'a> ModCollector<'a> { // TODO(Maddiaa): Investigate trait implementations with attributes see: https://github.com/noir-lang/noir/issues/2629 attributes: crate::token::Attributes::empty(), is_unconstrained: false, + is_comptime: false, }; let location = Location::new(name.span(), self.file_id); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs index 29daf5d6369..59a3051ac70 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -1,6 +1,5 @@ +use crate::ast::{Ident, Path}; use crate::hir::resolution::import::PathResolutionError; -use crate::Ident; -use crate::Path; use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::FileDiagnostic; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/item_scope.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/item_scope.rs index cd4eafdf669..3ca89e56bbc 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/item_scope.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/item_scope.rs @@ -1,8 +1,7 @@ use super::{namespace::PerNs, ModuleDefId, ModuleId}; -use crate::{ - node_interner::{FuncId, TraitId}, - Ident, ItemVisibility, -}; +use crate::ast::{Ident, ItemVisibility}; +use crate::node_interner::{FuncId, TraitId}; + use std::collections::{hash_map::Entry, HashMap}; type Scope = HashMap, (ModuleDefId, ItemVisibility, bool /*is_prelude*/)>; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs index 4dd38f0e3e5..488ccc476d7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/module_data.rs @@ -2,12 +2,9 @@ use std::collections::HashMap; use noirc_errors::Location; -use crate::{ - node_interner::{FuncId, GlobalId, StructId, TraitId, TypeAliasId}, - Ident, ItemVisibility, -}; - use super::{ItemScope, LocalModuleId, ModuleDefId, ModuleId, PerNs}; +use crate::ast::{Ident, ItemVisibility}; +use crate::node_interner::{FuncId, GlobalId, StructId, TraitId, TypeAliasId}; /// Contains the actual contents of a module: its parent (if one exists), /// children, and scope with all definitions defined within the scope. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/namespace.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/namespace.rs index 5e349f46e14..6fac6d2b991 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/namespace.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/namespace.rs @@ -1,5 +1,5 @@ use super::ModuleDefId; -use crate::ItemVisibility; +use crate::ast::ItemVisibility; // This works exactly the same as in r-a, just simplified #[derive(Debug, PartialEq, Eq, Copy, Clone)] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs index 71e3f3482fc..0fac6f96086 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -2,7 +2,7 @@ pub use noirc_errors::Span; use noirc_errors::{CustomDiagnostic as Diagnostic, FileDiagnostic}; use thiserror::Error; -use crate::{parser::ParserError, Ident, Type}; +use crate::{ast::Ident, parser::ParserError, Type}; use super::import::PathResolutionError; @@ -49,7 +49,7 @@ pub enum ResolverError { #[error("Integer too large to be evaluated in an array length context")] IntegerTooLarge { span: Span }, #[error("No global or generic type parameter found with the given name")] - NoSuchNumericTypeVariable { path: crate::Path }, + NoSuchNumericTypeVariable { path: crate::ast::Path }, #[error("Closures cannot capture mutable variables")] CapturedMutableVariable { span: Span }, #[error("Test functions are not allowed to have any parameters")] diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/impls.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/impls.rs index 72f6adc3770..7efd1eed86e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/impls.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/impls.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use fm::FileId; +use crate::ast::ItemVisibility; use crate::{ graph::CrateId, hir::{ @@ -13,7 +14,7 @@ use crate::{ Context, }, node_interner::{FuncId, NodeInterner}, - ItemVisibility, Type, + Type, }; use super::{ diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index ade97e2cf42..77e67567f8c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -4,8 +4,8 @@ use thiserror::Error; use crate::graph::CrateId; use std::collections::BTreeMap; +use crate::ast::{Ident, ItemVisibility, Path, PathKind}; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId, PerNs}; -use crate::{Ident, ItemVisibility, Path, PathKind}; #[derive(Debug, Clone)] pub struct ImportDirective { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs index e19af3c732f..e423e10b712 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs @@ -1,5 +1,5 @@ use super::import::{resolve_import, ImportDirective, PathResolution, PathResolutionResult}; -use crate::Path; +use crate::ast::Path; use std::collections::BTreeMap; use crate::graph::CrateId; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 9180201fe17..0e69b3bdeba 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -25,25 +25,22 @@ use regex::Regex; use std::collections::{BTreeMap, HashSet}; use std::rc::Rc; +use crate::ast::{ + ArrayLiteral, BinaryOpKind, BlockExpression, Distinctness, Expression, ExpressionKind, + ForRange, FunctionDefinition, FunctionKind, FunctionReturnType, Ident, ItemVisibility, LValue, + LetStatement, Literal, NoirFunction, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, + Statement, StatementKind, UnaryOp, UnresolvedGenerics, UnresolvedTraitConstraint, + UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, Visibility, ERROR_IDENT, +}; use crate::graph::CrateId; use crate::hir::def_map::{ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; +use crate::hir::{def_map::CrateDefMap, resolution::path_resolver::PathResolver}; use crate::hir_def::stmt::{HirAssignStatement, HirForStatement, HirLValue, HirPattern}; use crate::node_interner::{ DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, NodeInterner, StmtId, StructId, TraitId, TraitImplId, TraitMethodId, TypeAliasId, }; -use crate::{ - hir::{def_map::CrateDefMap, resolution::path_resolver::PathResolver}, - BlockExpression, Expression, ExpressionKind, FunctionKind, Ident, Literal, NoirFunction, - StatementKind, -}; -use crate::{ - ArrayLiteral, BinaryOpKind, Distinctness, ForRange, FunctionDefinition, FunctionReturnType, - Generics, ItemVisibility, LValue, NoirStruct, NoirTypeAlias, Param, Path, PathKind, Pattern, - Shared, Statement, StructType, Type, TypeAlias, TypeVariable, TypeVariableKind, UnaryOp, - UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, UnresolvedTypeData, - UnresolvedTypeExpression, Visibility, ERROR_IDENT, -}; +use crate::{Generics, Shared, StructType, Type, TypeAlias, TypeVariable, TypeVariableKind}; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span, Spanned}; @@ -246,6 +243,7 @@ impl<'a> Resolver<'a> { name: name.clone(), attributes: Attributes::empty(), is_unconstrained: false, + is_comptime: false, visibility: ItemVisibility::Public, // Trait functions are always public generics: generics.clone(), parameters: vecmap(parameters, |(name, typ)| Param { @@ -477,7 +475,7 @@ impl<'a> Resolver<'a> { /// Translates an UnresolvedType into a Type and appends any /// freshly created TypeVariables created to new_variables. fn resolve_type_inner(&mut self, typ: UnresolvedType, new_variables: &mut Generics) -> Type { - use UnresolvedTypeData::*; + use crate::ast::UnresolvedTypeData::*; let resolved_type = match typ.typ { FieldElement => Type::FieldElement, @@ -928,6 +926,7 @@ impl<'a> Resolver<'a> { let name_ident = HirIdent::non_trait_method(id, location); let attributes = func.attributes().clone(); + let should_fold = attributes.is_foldable(); let mut generics = vecmap(&self.generics, |(_, typevar, _)| typevar.clone()); let mut parameters = vec![]; @@ -1008,8 +1007,6 @@ impl<'a> Resolver<'a> { .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) .collect(); - let should_fold = attributes.is_foldable(); - FuncMeta { name: name_ident, kind: func.kind, @@ -1038,7 +1035,7 @@ impl<'a> Resolver<'a> { /// True if the 'pub' keyword is allowed on parameters in this function /// 'pub' on function parameters is only allowed for entry point functions fn pub_allowed(&self, func: &NoirFunction) -> bool { - self.is_entry_point_function(func) + self.is_entry_point_function(func) || func.attributes().is_foldable() } fn is_entry_point_function(&self, func: &NoirFunction) -> bool { @@ -1171,7 +1168,7 @@ impl<'a> Resolver<'a> { pub fn resolve_global_let( &mut self, - let_stmt: crate::LetStatement, + let_stmt: LetStatement, global_id: GlobalId, ) -> HirStatement { self.current_item = Some(DependencyId::Global(global_id)); @@ -1275,6 +1272,10 @@ impl<'a> Resolver<'a> { HirStatement::Continue } StatementKind::Error => HirStatement::Error, + StatementKind::Comptime(statement) => { + let statement = self.resolve_stmt(*statement, span); + HirStatement::Comptime(self.interner.push_stmt(statement)) + } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/structs.rs index ed7aa86e718..f62e5589d74 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/structs.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/structs.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use fm::FileId; use iter_extended::vecmap; +use crate::ast::Ident; use crate::{ graph::CrateId, hir::{ @@ -11,7 +12,7 @@ use crate::{ Context, }, node_interner::StructId, - Generics, Ident, Type, + Generics, Type, }; use super::{errors::ResolverError, path_resolver::StandardPathResolver, resolver::Resolver}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs index a7669f57e33..ccae8c6dd03 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs @@ -4,6 +4,7 @@ use fm::FileId; use iter_extended::vecmap; use noirc_errors::Location; +use crate::ast::{ItemVisibility, Path, TraitItem}; use crate::{ graph::CrateId, hir::{ @@ -16,7 +17,7 @@ use crate::{ }, hir_def::traits::{TraitConstant, TraitFunction, TraitImpl, TraitType}, node_interner::{FuncId, NodeInterner, TraitId}, - Generics, ItemVisibility, Path, Shared, TraitItem, Type, TypeVariable, TypeVariableKind, + Generics, Shared, Type, TypeVariable, TypeVariableKind, }; use super::{ diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs index 6c28aabe0fb..6c2a1945283 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/errors.rs @@ -3,13 +3,10 @@ use noirc_errors::CustomDiagnostic as Diagnostic; use noirc_errors::Span; use thiserror::Error; +use crate::ast::{BinaryOpKind, FunctionReturnType, IntegerBitSize, Signedness}; use crate::hir::resolution::errors::ResolverError; use crate::hir_def::expr::HirBinaryOp; use crate::hir_def::types::Type; -use crate::BinaryOpKind; -use crate::FunctionReturnType; -use crate::IntegerBitSize; -use crate::Signedness; #[derive(Error, Debug, Clone, PartialEq, Eq)] pub enum Source { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs index b56e2dce2a9..62330732be4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -1,6 +1,7 @@ use iter_extended::vecmap; use noirc_errors::Span; +use crate::ast::{BinaryOpKind, UnaryOp}; use crate::{ hir::{resolution::resolver::verify_mutable_reference, type_check::errors::Source}, hir_def::{ @@ -11,7 +12,7 @@ use crate::{ types::Type, }, node_interner::{DefinitionKind, ExprId, FuncId, TraitId, TraitImplKind, TraitMethodId}, - BinaryOpKind, TypeBinding, TypeBindings, TypeVariableKind, UnaryOp, + TypeBinding, TypeBindings, TypeVariableKind, }; use super::{errors::TypeCheckError, TypeChecker}; @@ -717,7 +718,7 @@ impl<'interner> TypeChecker<'interner> { let dereference_lhs = |this: &mut Self, lhs_type, element| { let old_lhs = *access_lhs; *access_lhs = this.interner.push_expr(HirExpression::Prefix(HirPrefixExpression { - operator: crate::UnaryOp::Dereference { implicitly_added: true }, + operator: crate::ast::UnaryOp::Dereference { implicitly_added: true }, rhs: old_lhs, })); this.interner.push_expr_type(old_lhs, lhs_type); @@ -1110,7 +1111,7 @@ impl<'interner> TypeChecker<'interner> { if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { let target = Type::polymorphic_integer(self.interner); - use BinaryOpKind::*; + use crate::ast::BinaryOpKind::*; use TypeCheckError::*; self.unify(lhs_type, &target, || match op.kind { Less | LessEqual | Greater | GreaterEqual => FieldComparison { span }, @@ -1202,7 +1203,7 @@ impl<'interner> TypeChecker<'interner> { fn type_check_prefix_operand( &mut self, - op: &crate::UnaryOp, + op: &crate::ast::UnaryOp, rhs_type: &Type, span: Span, ) -> Type { @@ -1216,7 +1217,7 @@ impl<'interner> TypeChecker<'interner> { }; match op { - crate::UnaryOp::Minus => { + crate::ast::UnaryOp::Minus => { if rhs_type.is_unsigned() { self.errors .push(TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span }); @@ -1228,7 +1229,7 @@ impl<'interner> TypeChecker<'interner> { }); expected } - crate::UnaryOp::Not => { + crate::ast::UnaryOp::Not => { let rhs_type = rhs_type.follow_bindings(); // `!` can work on booleans or integers @@ -1238,10 +1239,10 @@ impl<'interner> TypeChecker<'interner> { unify(Type::Bool) } - crate::UnaryOp::MutableReference => { + crate::ast::UnaryOp::MutableReference => { Type::MutableReference(Box::new(rhs_type.follow_bindings())) } - crate::UnaryOp::Dereference { implicitly_added: _ } => { + crate::ast::UnaryOp::Dereference { implicitly_added: _ } => { let element_type = self.interner.next_type_variable(); unify(Type::MutableReference(Box::new(element_type.clone()))); element_type diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs index cdfc19b3a33..44dab6dee3d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -51,8 +51,7 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec, ) { let meta = type_checker.interner.function_meta(&func_id); - if (meta.is_entry_point || meta.should_fold) && !param.1.is_valid_for_program_input() { + if (meta.is_entry_point && !param.1.is_valid_for_program_input()) + || (meta.should_fold && !param.1.is_valid_non_inlined_function_input()) + { let span = param.0.span(); errors.push(TypeCheckError::InvalidTypeForEntryPoint { span }); } @@ -424,14 +425,17 @@ impl<'interner> TypeChecker<'interner> { // XXX: These tests are all manual currently. /// We can either build a test apparatus or pass raw code through the resolver #[cfg(test)] -mod test { +pub mod test { use std::collections::{BTreeMap, HashMap}; use std::vec; use fm::FileId; - use iter_extended::vecmap; + use iter_extended::btree_map; use noirc_errors::{Location, Span}; + use crate::ast::{ + BinaryOpKind, Distinctness, FunctionKind, FunctionReturnType, Path, Visibility, + }; use crate::graph::CrateId; use crate::hir::def_map::{ModuleData, ModuleId}; use crate::hir::resolution::import::{ @@ -452,9 +456,8 @@ mod test { def_map::{CrateDefMap, LocalModuleId, ModuleDefId}, resolution::{path_resolver::PathResolver, resolver::Resolver}, }, - parse_program, FunctionKind, Path, + parse_program, }; - use crate::{BinaryOpKind, Distinctness, FunctionReturnType, Visibility}; #[test] fn basic_let() { @@ -601,7 +604,7 @@ mod test { "#; - type_check_src_code(src, vec![String::from("main"), String::from("foo")]); + type_check_src_code(src, vec![String::from("main")]); } #[test] fn basic_closure() { @@ -612,7 +615,7 @@ mod test { } "#; - type_check_src_code(src, vec![String::from("main"), String::from("foo")]); + type_check_src_code(src, vec![String::from("main")]); } #[test] @@ -633,12 +636,23 @@ mod test { #[fold] fn fold(x: &mut Field) -> Field { *x - } + } "#; type_check_src_code_errors_expected(src, vec![String::from("fold")], 1); } + #[test] + fn fold_numeric_generic() { + let src = r#" + #[fold] + fn fold(x: T) -> T { + x + } + "#; + + type_check_src_code(src, vec![String::from("fold")]); + } // This is the same Stub that is in the resolver, maybe we can pull this out into a test module and re-use? struct TestPathResolver(HashMap); @@ -672,8 +686,8 @@ mod test { } } - fn type_check_src_code(src: &str, func_namespace: Vec) { - type_check_src_code_errors_expected(src, func_namespace, 0); + pub fn type_check_src_code(src: &str, func_namespace: Vec) -> (NodeInterner, FuncId) { + type_check_src_code_errors_expected(src, func_namespace, 0) } // This function assumes that there is only one function and this is the @@ -682,7 +696,7 @@ mod test { src: &str, func_namespace: Vec, expected_num_type_check_errs: usize, - ) { + ) -> (NodeInterner, FuncId) { let (program, errors) = parse_program(src); let mut interner = NodeInterner::default(); interner.populate_dummy_operator_traits(); @@ -695,14 +709,16 @@ mod test { errors ); - let main_id = interner.push_test_function_definition("main".into()); + let func_ids = btree_map(&func_namespace, |name| { + (name.to_string(), interner.push_test_function_definition(name.into())) + }); - let func_ids = - vecmap(&func_namespace, |name| interner.push_test_function_definition(name.into())); + let main_id = + *func_ids.get("main").unwrap_or_else(|| func_ids.first_key_value().unwrap().1); let mut path_resolver = TestPathResolver(HashMap::new()); - for (name, id) in func_namespace.into_iter().zip(func_ids.clone()) { - path_resolver.insert_func(name.to_owned(), id); + for (name, id) in func_ids.iter() { + path_resolver.insert_func(name.to_owned(), *id); } let mut def_maps = BTreeMap::new(); @@ -722,20 +738,24 @@ mod test { }, ); - let func_meta = vecmap(program.into_sorted().functions, |nf| { + for nf in program.into_sorted().functions { let resolver = Resolver::new(&mut interner, &path_resolver, &def_maps, file); - let (hir_func, func_meta, resolver_errors) = resolver.resolve_function(nf, main_id); - assert_eq!(resolver_errors, vec![]); - (hir_func, func_meta) - }); - for ((hir_func, meta), func_id) in func_meta.into_iter().zip(func_ids.clone()) { - interner.update_fn(func_id, hir_func); - interner.push_fn_meta(meta, func_id); + let function_id = *func_ids.get(nf.name()).unwrap(); + let (hir_func, func_meta, resolver_errors) = resolver.resolve_function(nf, function_id); + + interner.push_fn_meta(func_meta, function_id); + interner.update_fn(function_id, hir_func); + assert_eq!(resolver_errors, vec![]); } // Type check section - let errors = super::type_check_func(&mut interner, func_ids.first().cloned().unwrap()); + let mut errors = Vec::new(); + + for function in func_ids.values() { + errors.extend(super::type_check_func(&mut interner, *function)); + } + assert_eq!( errors.len(), expected_num_type_check_errs, @@ -744,5 +764,7 @@ mod test { errors.len(), errors ); + + (interner, main_id) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs index fb57aa75f89..f5f6e1e8180 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -1,6 +1,7 @@ use iter_extended::vecmap; use noirc_errors::Span; +use crate::ast::UnaryOp; use crate::hir_def::expr::{HirExpression, HirIdent, HirLiteral}; use crate::hir_def::stmt::{ HirAssignStatement, HirConstrainStatement, HirForStatement, HirLValue, HirLetStatement, @@ -8,7 +9,6 @@ use crate::hir_def::stmt::{ }; use crate::hir_def::types::Type; use crate::node_interner::{DefinitionId, ExprId, StmtId}; -use crate::UnaryOp; use super::errors::{Source, TypeCheckError}; use super::TypeChecker; @@ -51,6 +51,7 @@ impl<'interner> TypeChecker<'interner> { HirStatement::Constrain(constrain_stmt) => self.check_constrain_stmt(constrain_stmt), HirStatement::Assign(assign_stmt) => self.check_assign_stmt(assign_stmt, stmt_id), HirStatement::For(for_loop) => self.check_for_loop(for_loop), + HirStatement::Comptime(statement) => return self.check_statement(&statement), HirStatement::Break | HirStatement::Continue | HirStatement::Error => (), } Type::Unit diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs index c2f6031bf6d..d88b65d1fce 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/expr.rs @@ -2,8 +2,9 @@ use acvm::FieldElement; use fm::FileId; use noirc_errors::Location; +use crate::ast::{BinaryOp, BinaryOpKind, Ident, UnaryOp}; use crate::node_interner::{DefinitionId, ExprId, FuncId, NodeInterner, StmtId, TraitMethodId}; -use crate::{BinaryOp, BinaryOpKind, Ident, Shared, UnaryOp}; +use crate::Shared; use super::stmt::HirPattern; use super::traits::TraitConstraint; @@ -31,7 +32,7 @@ pub enum HirExpression { Tuple(Vec), Lambda(HirLambda), Error, - Quote(crate::BlockExpression), + Quote(crate::ast::BlockExpression), } impl HirExpression { @@ -260,7 +261,7 @@ impl HirBlockExpression { } /// A variable captured inside a closure -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct HirCapturedVar { pub ident: HirIdent, @@ -274,7 +275,7 @@ pub struct HirCapturedVar { pub transitive_capture_index: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct HirLambda { pub parameters: Vec<(HirPattern, Type)>, pub return_type: Type, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs index a3bbc9445a8..67b6412a21c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs @@ -6,9 +6,9 @@ use std::rc::Rc; use super::expr::{HirBlockExpression, HirExpression, HirIdent}; use super::stmt::HirPattern; use super::traits::TraitConstraint; +use crate::ast::{Distinctness, FunctionKind, FunctionReturnType, Visibility}; use crate::node_interner::{ExprId, NodeInterner, TraitImplId}; -use crate::FunctionKind; -use crate::{Distinctness, FunctionReturnType, Type, TypeVariable, Visibility}; +use crate::{Type, TypeVariable}; /// A Hir function is a block expression /// with a list of statements @@ -24,8 +24,8 @@ impl HirFunction { HirFunction(expr_id) } - pub const fn as_expr(&self) -> &ExprId { - &self.0 + pub const fn as_expr(&self) -> ExprId { + self.0 } pub fn block(&self, interner: &NodeInterner) -> HirBlockExpression { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs index 4c9a33d3dc0..7e22e5ee9c0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/stmt.rs @@ -1,7 +1,8 @@ use super::expr::HirIdent; +use crate::ast::Ident; use crate::macros_api::SecondaryAttribute; -use crate::node_interner::ExprId; -use crate::{Ident, Type}; +use crate::node_interner::{ExprId, StmtId}; +use crate::Type; use fm::FileId; use noirc_errors::{Location, Span}; @@ -19,6 +20,7 @@ pub enum HirStatement { Continue, Expression(ExprId), Semi(ExprId), + Comptime(StmtId), Error, } @@ -61,7 +63,7 @@ pub struct HirAssignStatement { #[derive(Debug, Clone)] pub struct HirConstrainStatement(pub ExprId, pub FileId, pub Option); -#[derive(Debug, Clone, Hash)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum HirPattern { Identifier(HirIdent), Mutable(Box, Location), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs index 16b9899039f..e4959cb3dd9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/traits.rs @@ -1,9 +1,10 @@ use std::collections::HashMap; +use crate::ast::{Ident, NoirFunction}; use crate::{ graph::CrateId, node_interner::{FuncId, TraitId, TraitMethodId}, - Generics, Ident, NoirFunction, Type, TypeBindings, TypeVariable, TypeVariableId, + Generics, Type, TypeBindings, TypeVariable, TypeVariableId, }; use fm::FileId; use noirc_errors::{Location, Span}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs index ec8b54c33b8..8c6e3d48fca 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs @@ -6,15 +6,18 @@ use std::{ }; use crate::{ + ast::IntegerBitSize, hir::type_check::TypeCheckError, node_interner::{ExprId, NodeInterner, TraitId, TypeAliasId}, - IntegerBitSize, }; use iter_extended::vecmap; use noirc_errors::{Location, Span}; use noirc_printable_type::PrintableType; -use crate::{node_interner::StructId, Ident, Signedness}; +use crate::{ + ast::{Ident, Signedness}, + node_interner::StructId, +}; use super::expr::{HirCallExpression, HirExpression, HirIdent}; @@ -726,6 +729,54 @@ impl Type { } } + /// True if this type can be used as a parameter to an ACIR function that is not `main` or a contract function. + /// This encapsulates functions for which we may not want to inline during compilation. + /// + /// The inputs allowed for a function entry point differ from those allowed as input to a program as there are + /// certain types which through compilation we know what their size should be. + /// This includes types such as numeric generics. + pub(crate) fn is_valid_non_inlined_function_input(&self) -> bool { + match self { + // Type::Error is allowed as usual since it indicates an error was already issued and + // we don't need to issue further errors about this likely unresolved type + Type::FieldElement + | Type::Integer(_, _) + | Type::Bool + | Type::Unit + | Type::Constant(_) + | Type::TypeVariable(_, _) + | Type::NamedGeneric(_, _) + | Type::Error => true, + + Type::FmtString(_, _) + // To enable this we would need to determine the size of the closure outputs at compile-time. + // This is possible as long as the output size is not dependent upon a witness condition. + | Type::Function(_, _, _) + | Type::Slice(_) + | Type::MutableReference(_) + | Type::Forall(_, _) + // TODO: probably can allow code as it is all compile time + | Type::Code + | Type::TraitAsType(..) => false, + + Type::Alias(alias, generics) => { + let alias = alias.borrow(); + alias.get_type(generics).is_valid_non_inlined_function_input() + } + + Type::Array(length, element) => { + length.is_valid_non_inlined_function_input() && element.is_valid_non_inlined_function_input() + } + Type::String(length) => length.is_valid_non_inlined_function_input(), + Type::Tuple(elements) => elements.iter().all(|elem| elem.is_valid_non_inlined_function_input()), + Type::Struct(definition, generics) => definition + .borrow() + .get_fields(generics) + .into_iter() + .all(|(_, field)| field.is_valid_non_inlined_function_input()), + } + } + /// Returns the number of `Forall`-quantified type variables on this type. /// Returns 0 if this is not a Type::Forall pub fn generic_count(&self) -> usize { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index 86f26fd1c97..ec513d55299 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -412,8 +412,8 @@ impl Token { [Plus, Minus, Star, Slash, Percent, Ampersand, Caret, ShiftLeft, ShiftRight, Pipe] } - pub fn try_into_binary_op(self, span: Span) -> Option> { - use crate::BinaryOpKind::*; + pub fn try_into_binary_op(self, span: Span) -> Option> { + use crate::ast::BinaryOpKind::*; let binary_op = match self { Token::Plus => Add, Token::Ampersand => And, @@ -659,7 +659,6 @@ impl Attribute { ["contract_library_method"] => { Attribute::Secondary(SecondaryAttribute::ContractLibraryMethod) } - ["event"] => Attribute::Secondary(SecondaryAttribute::Event), ["abi", tag] => Attribute::Secondary(SecondaryAttribute::Abi(tag.to_string())), ["export"] => Attribute::Secondary(SecondaryAttribute::Export), ["deprecated", name] => { @@ -751,7 +750,6 @@ pub enum SecondaryAttribute { // is a helper method for a contract and should not be seen as // the entry point. ContractLibraryMethod, - Event, Export, Field(String), Custom(String), @@ -767,7 +765,6 @@ impl fmt::Display for SecondaryAttribute { } SecondaryAttribute::Custom(ref k) => write!(f, "#[{k}]"), SecondaryAttribute::ContractLibraryMethod => write!(f, "#[contract_library_method]"), - SecondaryAttribute::Event => write!(f, "#[event]"), SecondaryAttribute::Export => write!(f, "#[export]"), SecondaryAttribute::Field(ref k) => write!(f, "#[field({k})]"), SecondaryAttribute::Abi(ref k) => write!(f, "#[abi({k})]"), @@ -797,7 +794,7 @@ impl AsRef for SecondaryAttribute { | SecondaryAttribute::Field(string) | SecondaryAttribute::Abi(string) => string, SecondaryAttribute::ContractLibraryMethod => "", - SecondaryAttribute::Event | SecondaryAttribute::Export => "", + SecondaryAttribute::Export => "", } } } @@ -839,6 +836,7 @@ pub enum Keyword { ReturnData, String, Struct, + Super, Trait, Type, Unchecked, @@ -883,6 +881,7 @@ impl fmt::Display for Keyword { Keyword::ReturnData => write!(f, "return_data"), Keyword::String => write!(f, "str"), Keyword::Struct => write!(f, "struct"), + Keyword::Super => write!(f, "super"), Keyword::Trait => write!(f, "trait"), Keyword::Type => write!(f, "type"), Keyword::Unchecked => write!(f, "unchecked"), @@ -930,6 +929,7 @@ impl Keyword { "return_data" => Keyword::ReturnData, "str" => Keyword::String, "struct" => Keyword::Struct, + "super" => Keyword::Super, "trait" => Keyword::Trait, "type" => Keyword::Type, "unchecked" => Keyword::Unchecked, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lib.rs b/noir/noir-repo/compiler/noirc_frontend/src/lib.rs index 93d7960faf5..6c77e3d0545 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lib.rs @@ -28,9 +28,6 @@ pub use lexer::token; // Parser API pub use parser::{parse_program, ParsedModule}; -// AST API -pub use ast::*; - // Type API pub use hir_def::types::*; @@ -52,17 +49,18 @@ pub mod macros_api { pub use crate::parser::{parse_program, SortedModule}; pub use crate::token::SecondaryAttribute; - pub use crate::hir::def_map::ModuleDefId; - pub use crate::{ - hir::Context as HirContext, BlockExpression, CallExpression, CastExpression, Distinctness, - Expression, ExpressionKind, FunctionReturnType, Ident, IndexExpression, ItemVisibility, - LetStatement, Literal, MemberAccessExpression, MethodCallExpression, NoirFunction, Path, - PathKind, Pattern, Statement, UnresolvedType, UnresolvedTypeData, Visibility, + pub use crate::ast::{ + BlockExpression, CallExpression, CastExpression, Distinctness, Expression, ExpressionKind, + FunctionReturnType, Ident, IndexExpression, ItemVisibility, LetStatement, Literal, + MemberAccessExpression, MethodCallExpression, NoirFunction, Path, PathKind, Pattern, + Statement, UnresolvedType, UnresolvedTypeData, Visibility, }; - pub use crate::{ + pub use crate::ast::{ ForLoopStatement, ForRange, FunctionDefinition, ImportStatement, NoirStruct, Param, - PrefixExpression, Signedness, StatementKind, StructType, Type, TypeImpl, UnaryOp, + PrefixExpression, Signedness, StatementKind, TypeImpl, UnaryOp, }; + pub use crate::hir::{def_map::ModuleDefId, Context as HirContext}; + pub use crate::{StructType, Type}; /// Methods to process the AST before and after type checking pub trait MacroProcessor { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs index d9c33d8604e..434cc922a05 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -5,10 +5,8 @@ use noirc_errors::{ Location, }; -use crate::{ - hir_def::function::FunctionSignature, BinaryOpKind, Distinctness, IntegerBitSize, Signedness, - Visibility, -}; +use crate::ast::{BinaryOpKind, Distinctness, IntegerBitSize, Signedness, Visibility}; +use crate::hir_def::function::FunctionSignature; /// The monomorphized AST is expression-based, all statements are also /// folded into this expression enum. Compared to the HIR, the monomorphized @@ -99,7 +97,7 @@ pub enum Literal { #[derive(Debug, Clone, Hash)] pub struct Unary { - pub operator: crate::UnaryOp, + pub operator: crate::ast::UnaryOp, pub rhs: Box, pub result_type: Type, pub location: Location, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs index 4e779244d30..74a0dd855c0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -8,6 +8,7 @@ //! //! The entry point to this pass is the `monomorphize` function which, starting from a given //! function, will monomorphize the entire reachable program. +use crate::ast::{FunctionKind, IntegerBitSize, Signedness, UnaryOp, Visibility}; use crate::{ debug::DebugInstrumenter, hir_def::{ @@ -18,8 +19,7 @@ use crate::{ }, node_interner::{self, DefinitionKind, NodeInterner, StmtId, TraitImplKind, TraitMethodId}, token::FunctionAttribute, - FunctionKind, IntegerBitSize, Signedness, Type, TypeBinding, TypeBindings, TypeVariable, - TypeVariableKind, UnaryOp, Visibility, + Type, TypeBinding, TypeBindings, TypeVariable, TypeVariableKind, }; use acvm::FieldElement; use iter_extended::{btree_map, try_vecmap, vecmap}; @@ -283,12 +283,18 @@ impl<'interner> Monomorphizer<'interner> { } let meta = self.interner.function_meta(&f).clone(); - let func_sig = meta.function_signature(); + let mut func_sig = meta.function_signature(); + // Follow the bindings of the function signature for entry points + // which are not `main` such as foldable functions. + for param in func_sig.0.iter_mut() { + param.1 = param.1.follow_bindings(); + } + func_sig.1 = func_sig.1.map(|return_type| return_type.follow_bindings()); let modifiers = self.interner.function_modifiers(&f); let name = self.interner.function_name(&f).to_owned(); - let body_expr_id = *self.interner.function(&f).as_expr(); + let body_expr_id = self.interner.function(&f).as_expr(); let body_return_type = self.interner.id_type(body_expr_id); let return_type = match meta.return_type() { Type::TraitAsType(..) => &body_return_type, @@ -454,7 +460,7 @@ impl<'interner> Monomorphizer<'interner> { // If this is a comparison operator, the result is a boolean but // the actual method call returns an Ordering - use crate::BinaryOpKind::*; + use crate::ast::BinaryOpKind::*; let ret = if matches!(operator, Less | LessEqual | Greater | GreaterEqual) { self.interner.ordering_type() } else { @@ -618,6 +624,9 @@ impl<'interner> Monomorphizer<'interner> { HirStatement::Break => Ok(ast::Expression::Break), HirStatement::Continue => Ok(ast::Expression::Continue), HirStatement::Error => unreachable!(), + + // All `comptime` statements & expressions should be removed before runtime. + HirStatement::Comptime(_) => unreachable!("comptime statement in runtime code"), } } @@ -1259,7 +1268,7 @@ impl<'interner> Monomorphizer<'interner> { ) -> ast::Expression { use ast::*; - let int_type = Type::Integer(crate::Signedness::Unsigned, arr_elem_bits); + let int_type = Type::Integer(crate::ast::Signedness::Unsigned, arr_elem_bits); let bytes_as_expr = vecmap(bytes, |byte| { Expression::Literal(Literal::Integer((byte as u128).into(), int_type.clone(), location)) @@ -1588,7 +1597,7 @@ impl<'interner> Monomorphizer<'interner> { })) } ast::Type::MutableReference(element) => { - use crate::UnaryOp::MutableReference; + use crate::ast::UnaryOp::MutableReference; let rhs = Box::new(self.zeroed_value_of_type(element, location)); let result_type = typ.clone(); ast::Expression::Unary(ast::Unary { @@ -1673,7 +1682,7 @@ impl<'interner> Monomorphizer<'interner> { let mut result = ast::Expression::Call(ast::Call { func, arguments, return_type, location }); - use crate::BinaryOpKind::*; + use crate::ast::BinaryOpKind::*; match operator.kind { // Negate the result of the == operation NotEqual => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index 153c7e45d4a..b0e68be4868 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -16,6 +16,7 @@ use crate::hir::def_collector::dc_crate::CompilationError; use crate::hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait, UnresolvedTypeAlias}; use crate::hir::def_map::{LocalModuleId, ModuleId}; +use crate::ast::{BinaryOpKind, FunctionDefinition, ItemVisibility}; use crate::hir::resolution::errors::ResolverError; use crate::hir_def::stmt::HirLetStatement; use crate::hir_def::traits::TraitImpl; @@ -28,8 +29,7 @@ use crate::hir_def::{ }; use crate::token::{Attributes, SecondaryAttribute}; use crate::{ - BinaryOpKind, FunctionDefinition, Generics, ItemVisibility, Shared, TypeAlias, TypeBindings, - TypeVariable, TypeVariableId, TypeVariableKind, + Generics, Shared, TypeAlias, TypeBindings, TypeVariable, TypeVariableId, TypeVariableKind, }; /// An arbitrary number to limit the recursion depth when searching for trait impls. @@ -242,6 +242,8 @@ pub struct FunctionModifiers { pub attributes: Attributes, pub is_unconstrained: bool, + + pub is_comptime: bool, } impl FunctionModifiers { @@ -254,6 +256,7 @@ impl FunctionModifiers { visibility: ItemVisibility::Public, attributes: Attributes::empty(), is_unconstrained: false, + is_comptime: false, } } } @@ -759,6 +762,7 @@ impl NodeInterner { visibility: function.visibility, attributes: function.attributes.clone(), is_unconstrained: function.is_unconstrained, + is_comptime: function.is_comptime, }; self.push_function_definition(id, modifiers, module, location) } @@ -818,10 +822,10 @@ impl NodeInterner { self.func_meta.get(func_id) } - pub fn function_ident(&self, func_id: &FuncId) -> crate::Ident { + pub fn function_ident(&self, func_id: &FuncId) -> crate::ast::Ident { let name = self.function_name(func_id).to_owned(); let span = self.function_meta(func_id).name.location.span; - crate::Ident(Spanned::from(span, name)) + crate::ast::Ident(Spanned::from(span, name)) } pub fn function_name(&self, func_id: &FuncId) -> &str { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop b/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop index c8d293fb72f..ec2b4c8ab46 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop +++ b/noir/noir-repo/compiler/noirc_frontend/src/noir_parser.lalrpop @@ -4,7 +4,7 @@ use crate::lexer::token::BorrowedToken; use crate::lexer::token as noir_token; use crate::lexer::errors::LexerErrorKind; use crate::parser::TopLevelStatement; -use crate::{Ident, Path, PathKind, UseTree, UseTreeKind}; +use crate::ast::{Ident, Path, PathKind, UseTree, UseTreeKind}; use lalrpop_util::ErrorRecovery; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs index 895d4e07bbd..407b69c818b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -1,7 +1,6 @@ +use crate::ast::{Expression, IntegerBitSize}; use crate::lexer::errors::LexerErrorKind; use crate::lexer::token::Token; -use crate::Expression; -use crate::IntegerBitSize; use small_ord_set::SmallOrdSet; use thiserror::Error; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index 80c5f47f07b..9e60383afee 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -11,12 +11,11 @@ mod labels; #[allow(clippy::module_inception)] mod parser; -use crate::token::{Keyword, Token}; -use crate::{ast::ImportStatement, Expression, NoirStruct}; -use crate::{ - Ident, LetStatement, ModuleDeclaration, NoirFunction, NoirTrait, NoirTraitImpl, NoirTypeAlias, - Recoverable, StatementKind, TypeImpl, UseTree, +use crate::ast::{ + Expression, Ident, ImportStatement, LetStatement, ModuleDeclaration, NoirFunction, NoirStruct, + NoirTrait, NoirTraitImpl, NoirTypeAlias, Recoverable, StatementKind, TypeImpl, UseTree, }; +use crate::token::{Keyword, Token}; use chumsky::prelude::*; use chumsky::primitive::Container; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index 5706c3ef12f..603193d1593 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -33,17 +33,17 @@ use super::{ }; use super::{spanned, Item, ItemKind}; use crate::ast::{ - Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, -}; -use crate::lexer::{lexer::from_spanned_token_result, Lexer}; -use crate::parser::{force, ignore_then_commit, statement_recovery}; -use crate::token::{Keyword, Token, TokenKind}; -use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; +use crate::ast::{ + Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, +}; +use crate::lexer::{lexer::from_spanned_token_result, Lexer}; +use crate::parser::{force, ignore_then_commit, statement_recovery}; +use crate::token::{Keyword, Token, TokenKind}; use chumsky::prelude::*; use iter_extended::vecmap; @@ -234,14 +234,16 @@ fn implementation() -> impl NoirParser { /// global_declaration: 'global' ident global_type_annotation '=' literal fn global_declaration() -> impl NoirParser { let p = attributes::attributes() + .then(maybe_comp_time()) .then_ignore(keyword(Keyword::Global).labelled(ParsingRuleLabel::Global)) .then(ident().map(Pattern::Identifier)); + let p = then_commit(p, optional_type_annotation()); let p = then_commit_ignore(p, just(Token::Assign)); let p = then_commit(p, expression()); - p.validate(|(((attributes, pattern), r#type), expression), span, emit| { + p.validate(|((((attributes, comptime), pattern), r#type), expression), span, emit| { let global_attributes = attributes::validate_secondary_attributes(attributes, span, emit); - LetStatement { pattern, r#type, expression, attributes: global_attributes } + LetStatement { pattern, r#type, comptime, expression, attributes: global_attributes } }) .map(TopLevelStatement::Global) } @@ -498,10 +500,11 @@ where assertion::assertion_eq(expr_parser.clone()), declaration(expr_parser.clone()), assignment(expr_parser.clone()), - for_loop(expr_no_constructors, statement), + for_loop(expr_no_constructors.clone(), statement.clone()), break_statement(), continue_statement(), return_statement(expr_parser.clone()), + comptime_statement(expr_parser.clone(), expr_no_constructors, statement), expr_parser.map(StatementKind::Expression), )) }) @@ -519,6 +522,35 @@ fn continue_statement() -> impl NoirParser { keyword(Keyword::Continue).to(StatementKind::Continue) } +fn comptime_statement<'a, P1, P2, S>( + expr: P1, + expr_no_constructors: P2, + statement: S, +) -> impl NoirParser + 'a +where + P1: ExprParser + 'a, + P2: ExprParser + 'a, + S: NoirParser + 'a, +{ + keyword(Keyword::CompTime) + .ignore_then(choice(( + declaration(expr), + for_loop(expr_no_constructors, statement.clone()), + block(statement).map_with_span(|block, span| { + StatementKind::Expression(Expression::new(ExpressionKind::Block(block), span)) + }), + ))) + .map(|statement| StatementKind::Comptime(Box::new(statement))) +} + +/// Comptime in an expression position only accepts entire blocks +fn comptime_expr<'a, S>(statement: S) -> impl NoirParser + 'a +where + S: NoirParser + 'a, +{ + keyword(Keyword::CompTime).ignore_then(block(statement)).map(ExpressionKind::Block) +} + fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a where P: ExprParser + 'a, @@ -700,24 +732,25 @@ fn optional_distinctness() -> impl NoirParser { }) } -fn maybe_comp_time() -> impl NoirParser<()> { +fn maybe_comp_time() -> impl NoirParser { keyword(Keyword::CompTime).or_not().validate(|opt, span, emit| { if opt.is_some() { - emit(ParserError::with_reason(ParserErrorReason::ComptimeDeprecated, span)); + emit(ParserError::with_reason( + ParserErrorReason::ExperimentalFeature("comptime"), + span, + )); } + opt.is_some() }) } fn field_type() -> impl NoirParser { - maybe_comp_time() - .then_ignore(keyword(Keyword::Field)) + keyword(Keyword::Field) .map_with_span(|_, span| UnresolvedTypeData::FieldElement.with_span(span)) } fn bool_type() -> impl NoirParser { - maybe_comp_time() - .then_ignore(keyword(Keyword::Bool)) - .map_with_span(|_, span| UnresolvedTypeData::Bool.with_span(span)) + keyword(Keyword::Bool).map_with_span(|_, span| UnresolvedTypeData::Bool.with_span(span)) } fn string_type() -> impl NoirParser { @@ -744,21 +777,20 @@ fn format_string_type( } fn int_type() -> impl NoirParser { - maybe_comp_time() - .then(filter_map(|span, token: Token| match token { - Token::IntType(int_type) => Ok(int_type), - unexpected => { - Err(ParserError::expected_label(ParsingRuleLabel::IntegerType, unexpected, span)) - } - })) - .validate(|(_, token), span, emit| { - UnresolvedTypeData::from_int_token(token) - .map(|data| data.with_span(span)) - .unwrap_or_else(|err| { - emit(ParserError::with_reason(ParserErrorReason::InvalidBitSize(err.0), span)); - UnresolvedType::error(span) - }) - }) + filter_map(|span, token: Token| match token { + Token::IntType(int_type) => Ok(int_type), + unexpected => { + Err(ParserError::expected_label(ParsingRuleLabel::IntegerType, unexpected, span)) + } + }) + .validate(|token, span, emit| { + UnresolvedTypeData::from_int_token(token).map(|data| data.with_span(span)).unwrap_or_else( + |err| { + emit(ParserError::with_reason(ParserErrorReason::InvalidBitSize(err.0), span)); + UnresolvedType::error(span) + }, + ) + }) } fn named_type(type_parser: impl NoirParser) -> impl NoirParser { @@ -1236,6 +1268,7 @@ where }, lambdas::lambda(expr_parser.clone()), block(statement.clone()).map(ExpressionKind::Block), + comptime_expr(statement.clone()), quote(statement), variable(), literal(), @@ -1320,7 +1353,7 @@ where mod test { use super::test_helpers::*; use super::*; - use crate::ArrayLiteral; + use crate::ast::ArrayLiteral; #[test] fn parse_infix() { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs index f9c8d7aa46b..ed08a4c9922 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs @@ -4,8 +4,8 @@ use crate::parser::{ ParserError, ParserErrorReason, }; +use crate::ast::{BinaryOpKind, ConstrainKind, ConstrainStatement, InfixExpression, Recoverable}; use crate::token::{Keyword, Token}; -use crate::{BinaryOpKind, ConstrainKind, ConstrainStatement, InfixExpression, Recoverable}; use chumsky::prelude::*; use noirc_errors::Spanned; @@ -74,11 +74,11 @@ where mod test { use super::*; use crate::{ + ast::Literal, parser::parser::{ expression, test_helpers::{parse_all, parse_all_failing, parse_with}, }, - Literal, }; /// Deprecated constrain usage test diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs index 06e1a958eb1..f39b2ad6292 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs @@ -1,16 +1,16 @@ use super::{ attributes::{attributes, validate_attributes}, - block, fresh_statement, ident, keyword, nothing, optional_distinctness, optional_visibility, - parameter_name_recovery, parameter_recovery, parenthesized, parse_type, pattern, - self_parameter, where_clause, NoirParser, + block, fresh_statement, ident, keyword, maybe_comp_time, nothing, optional_distinctness, + optional_visibility, parameter_name_recovery, parameter_recovery, parenthesized, parse_type, + pattern, self_parameter, where_clause, NoirParser, }; -use crate::parser::labels::ParsingRuleLabel; -use crate::parser::spanned; -use crate::token::{Keyword, Token}; -use crate::{ +use crate::ast::{ Distinctness, FunctionDefinition, FunctionReturnType, Ident, ItemVisibility, NoirFunction, Param, Visibility, }; +use crate::parser::labels::ParsingRuleLabel; +use crate::parser::spanned; +use crate::token::{Keyword, Token}; use chumsky::prelude::*; @@ -37,6 +37,7 @@ pub(super) fn function_definition(allow_self: bool) -> impl NoirParser impl NoirParser { /// function_modifiers: 'unconstrained'? (visibility)? /// /// returns (is_unconstrained, visibility) for whether each keyword was present -fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility)> { +fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility, bool)> { keyword(Keyword::Unconstrained) .or_not() .then(visibility_modifier()) - .map(|(unconstrained, visibility)| (unconstrained.is_some(), visibility)) + .then(maybe_comp_time()) + .map(|((unconstrained, visibility), comptime)| { + (unconstrained.is_some(), visibility, comptime) + }) } /// non_empty_ident_list: ident ',' non_empty_ident_list @@ -171,8 +175,8 @@ mod test { "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", "fn func_name(x: [Field], y : [Field;2],y : pub [Field;2], z : pub [u8;5]) {}", "fn main(x: pub u8, y: pub u8) -> distinct pub [u8; 2] { [x, y] }", - "fn f(f: pub Field, y : Field, z : comptime Field) -> u8 { x + a }", - "fn f(f: pub Field, y : T, z : comptime Field) -> u8 { x + a }", + "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", + "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", "fn func_name(f: Field, y : T) where T: SomeTrait {}", "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", "fn func_name(f: Field, y : T) where T: SomeTrait, T: SomeTrait2 {}", diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs index 48ddd41ab44..2b4a1d547c0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs @@ -1,13 +1,12 @@ use chumsky::{primitive::just, Parser}; +use super::{parse_type, pattern}; +use crate::ast::{Expression, ExpressionKind, Lambda, Pattern, UnresolvedType}; use crate::{ parser::{labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, NoirParser}, token::Token, - Expression, ExpressionKind, Lambda, Pattern, UnresolvedType, }; -use super::{parse_type, pattern}; - pub(super) fn lambda<'a>( expr_parser: impl NoirParser + 'a, ) -> impl NoirParser + 'a { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs index 83d7b832d27..584224fda46 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs @@ -1,9 +1,9 @@ use chumsky::Parser; use crate::{ + ast::ExpressionKind, parser::NoirParser, token::{Token, TokenKind}, - ExpressionKind, }; use super::primitives::token_kind; @@ -22,10 +22,10 @@ pub(super) fn literal() -> impl NoirParser { #[cfg(test)] mod test { use super::*; + use crate::ast::Literal; use crate::parser::parser::{ expression, expression_no_constructors, fresh_statement, term, test_helpers::*, }; - use crate::Literal; fn expr_to_lit(expr: ExpressionKind) -> Literal { match expr { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs index ab812c07dce..47bb11991fa 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs @@ -1,5 +1,5 @@ +use crate::ast::{Path, PathKind}; use crate::parser::NoirParser; -use crate::{Path, PathKind}; use crate::token::{Keyword, Token}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs index 34927278038..8413f14ae4d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs @@ -1,9 +1,9 @@ use chumsky::prelude::*; +use crate::ast::{ExpressionKind, Ident, UnaryOp}; use crate::{ parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, token::{Keyword, Token, TokenKind}, - ExpressionKind, Ident, UnaryOp, }; use super::path; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs index 87e58f69efb..7da956bdfea 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -1,5 +1,6 @@ use chumsky::prelude::*; +use crate::ast::{Ident, NoirStruct, UnresolvedType}; use crate::{ parser::{ parser::{ @@ -10,7 +11,6 @@ use crate::{ NoirParser, TopLevelStatement, }, token::{Keyword, Token}, - Ident, NoirStruct, UnresolvedType, }; pub(super) fn struct_definition() -> impl NoirParser { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs index 1e2a6b4d65d..0507dbdbc71 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -5,14 +5,16 @@ use super::{ function_return_type, }; +use crate::ast::{ + Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, TraitItem, + UnresolvedTraitConstraint, UnresolvedType, +}; use crate::{ parser::{ ignore_then_commit, parenthesized, parser::primitives::keyword, NoirParser, ParserError, ParserErrorReason, TopLevelStatement, }, token::{Keyword, Token}, - Expression, ItemVisibility, NoirTrait, NoirTraitImpl, TraitBound, TraitImplItem, TraitItem, - UnresolvedTraitConstraint, UnresolvedType, }; use super::{generic_type_args, parse_type, path, primitives::ident}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index e4d308fbb6b..31bf2245b1f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -66,7 +66,7 @@ mod test { // Allocate a default Module for the root, giving it a ModuleId let mut modules: Arena = Arena::default(); let location = Location::new(Default::default(), root_file_id); - let root = modules.insert(ModuleData::new(None, None, location, false)); + let root = modules.insert(ModuleData::new(None, location, false)); let def_map = CrateDefMap { root: LocalModuleId(root), @@ -780,6 +780,7 @@ mod test { HirStatement::Error => panic!("Invalid HirStatement!"), HirStatement::Break => panic!("Unexpected break"), HirStatement::Continue => panic!("Unexpected continue"), + HirStatement::Comptime(_) => panic!("Unexpected comptime"), }; let expr = interner.expression(&expr_id); diff --git a/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml b/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml index fbbe778e561..5140f5a5a8c 100644 --- a/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_printable_type/Cargo.toml @@ -3,6 +3,7 @@ name = "noirc_printable_type" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/compiler/utils/arena/Cargo.toml b/noir/noir-repo/compiler/utils/arena/Cargo.toml index 41c6ebc9a8b..f6bd764ee62 100644 --- a/noir/noir-repo/compiler/utils/arena/Cargo.toml +++ b/noir/noir-repo/compiler/utils/arena/Cargo.toml @@ -3,4 +3,5 @@ name = "arena" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true diff --git a/noir/noir-repo/compiler/utils/iter-extended/Cargo.toml b/noir/noir-repo/compiler/utils/iter-extended/Cargo.toml index c91e5ea6d77..4343311506e 100644 --- a/noir/noir-repo/compiler/utils/iter-extended/Cargo.toml +++ b/noir/noir-repo/compiler/utils/iter-extended/Cargo.toml @@ -3,6 +3,7 @@ name = "iter-extended" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/compiler/wasm/Cargo.toml b/noir/noir-repo/compiler/wasm/Cargo.toml index a20efeeed8a..31ef5161014 100644 --- a/noir/noir-repo/compiler/wasm/Cargo.toml +++ b/noir/noir-repo/compiler/wasm/Cargo.toml @@ -3,6 +3,7 @@ name = "noir_wasm" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx b/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx index 828faf4a8f8..4eccc677b80 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/slices.mdx @@ -168,3 +168,28 @@ fn main() { assert(slice.len() == 2); } ``` + +### as_array + +Converts this slice into an array. + +Make sure to specify the size of the resulting array. +Panics if the resulting array length is different than the slice's length. + +```rust +fn as_array(self) -> [T; N] +``` + +Example: + +```rust +fn main() { + let slice = &[5, 6]; + + // Always specify the length of the resulting array! + let array: [Field; 2] = slice.as_array(); + + assert(array[0] == slice[0]); + assert(array[1] == slice[1]); +} +``` diff --git a/noir/noir-repo/docs/docs/noir/standard_library/containers/hashmap.md b/noir/noir-repo/docs/docs/noir/standard_library/containers/hashmap.md index 093b6d38d11..2b9f4895722 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/containers/hashmap.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/containers/hashmap.md @@ -20,8 +20,9 @@ Example: ```rust // Create a mapping from Fields to u32s with a maximum length of 12 -// using a pedersen hash -let mut map: HashMap> = HashMap::default(); +// using a poseidon2 hasher +use dep::std::hash::poseidon2::Poseidon2Hasher; +let mut map: HashMap> = HashMap::default(); map.insert(1, 2); map.insert(3, 4); diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx index f98c90a97c8..7329880c7a7 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/hashes.mdx @@ -13,7 +13,6 @@ import BlackBoxInfo from '@site/src/components/Notes/_blackbox.mdx'; ## sha256 Given an array of bytes, returns the resulting sha256 hash. -See sha256_slice for a version that works directly on slices. #include_code sha256 noir_stdlib/src/hash.nr rust @@ -28,18 +27,9 @@ fn main() { -## sha256_slice - -A version of sha256 specialized to slices: - -#include_code sha256_slice noir_stdlib/src/hash.nr rust - - - ## blake2s Given an array of bytes, returns an array with the Blake2 hash -See blake2s_slice for a version that works directly on slices. #include_code blake2s noir_stdlib/src/hash.nr rust @@ -54,18 +44,9 @@ fn main() { -## blake2s_slice - -A version of blake2s specialized to slices: - -#include_code blake2s_slice noir_stdlib/src/hash.nr rust - - - ## blake3 Given an array of bytes, returns an array with the Blake3 hash -See blake3_slice for a version that works directly on slices. #include_code blake3 noir_stdlib/src/hash.nr rust @@ -80,18 +61,9 @@ fn main() { -## blake3_slice - -A version of blake3 specialized to slices: - -#include_code blake3_slice noir_stdlib/src/hash.nr rust - - - ## pedersen_hash Given an array of Fields, returns the Pedersen hash. -See pedersen_hash_slice for a version that works directly on slices. #include_code pedersen_hash noir_stdlib/src/hash.nr rust @@ -101,18 +73,9 @@ example: -## pedersen_hash_slice - -Given a slice of Fields, returns the Pedersen hash. - -#include_code pedersen_hash_slice noir_stdlib/src/hash.nr rust - - - ## pedersen_commitment Given an array of Fields, returns the Pedersen commitment. -See pedersen_commitment_slice for a version that works directly on slices. #include_code pedersen_commitment noir_stdlib/src/hash.nr rust @@ -122,20 +85,11 @@ example: -## pedersen_commitment_slice - -Given a slice of Fields, returns the Pedersen commitment. - -#include_code pedersen_commitment_slice noir_stdlib/src/hash.nr rust - - - ## keccak256 Given an array of bytes (`u8`), returns the resulting keccak hash as an array of 32 bytes (`[u8; 32]`). Specify a message_size to hash only the first -`message_size` bytes of the input. See keccak256_slice for a version that works -directly on slices. +`message_size` bytes of the input. #include_code keccak256 noir_stdlib/src/hash.nr rust @@ -145,15 +99,6 @@ example: -## keccak256_slice - -Given a slice of bytes (`u8`), returns the resulting keccak hash as an array of -32 bytes (`[u8; 32]`). - -#include_code keccak256_slice noir_stdlib/src/hash.nr rust - - - ## poseidon Given an array of Fields, returns a new Field with the Poseidon Hash. Mind that you need to specify diff --git a/noir/noir-repo/docs/docs/noir/standard_library/traits.md b/noir/noir-repo/docs/docs/noir/standard_library/traits.md index e6e7e6d40cb..2536d9a943f 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/traits.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/traits.md @@ -140,6 +140,8 @@ impl Eq for (A, B, C, D, E) Implementing this trait on a type allows `<`, `<=`, `>`, and `>=` to be used on values of the type. +`std::cmp` also provides `max` and `min` functions for any type which implements the `Ord` trait. + Implementations: ```rust diff --git a/noir/noir-repo/docs/docs/tutorials/noirjs_app.md b/noir/noir-repo/docs/docs/tutorials/noirjs_app.md index 12beb476994..6446e0b2a76 100644 --- a/noir/noir-repo/docs/docs/tutorials/noirjs_app.md +++ b/noir/noir-repo/docs/docs/tutorials/noirjs_app.md @@ -14,9 +14,9 @@ You can find the complete app code for this guide [here](https://github.com/noir :::note -Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.19.x matches `noir_js@0.19.x`, etc. +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.27.x matches `noir_js@0.27.x`, etc. -In this guide, we will be pinned to 0.19.4. +In this guide, we will be pinned to 0.27.0. ::: @@ -34,7 +34,7 @@ Easy enough. Onwards! ## Our project -ZK is a powerful technology. An app that doesn't reveal one of the inputs to *anyone* is almost unbelievable, yet Noir makes it as easy as a single line of code. +ZK is a powerful technology. An app that doesn't reveal one of the inputs to _anyone_ is almost unbelievable, yet Noir makes it as easy as a single line of code. In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! @@ -42,13 +42,13 @@ In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! Run: -```nargo new circuit``` +`nargo new circuit` And... That's about it. Your program is ready to be compiled and run. To compile, let's `cd` into the `circuit` folder to enter our project, and call: -```nargo compile``` +`nargo compile` This compiles our circuit into `json` format and add it to a new `target` folder. @@ -77,10 +77,55 @@ Vite is a powerful tool to generate static websites. While it provides all kinds To do this this, go back to the previous folder (`cd ..`) and create a new vite project by running `npm create vite` and choosing "Vanilla" and "Javascript". -You should see `vite-project` appear in your root folder. This seems like a good time to `cd` into it and install our NoirJS packages: +A wild `vite-project` directory should now appear in your root folder! Let's not waste any time and dive right in: ```bash -npm i @noir-lang/backend_barretenberg@0.19.4 @noir-lang/noir_js@0.19.4 +cd vite-project +``` + +### Setting Up Vite and Configuring the Project + +Before we proceed with any coding, let's get our environment tailored for Noir. We'll start by laying down the foundations with a `vite.config.js` file. This little piece of configuration is our secret sauce for making sure everything meshes well with the NoirJS libraries and other special setups we might need, like handling WebAssembly modules. Here’s how you get that going: + +#### Creating the vite.config.js + +In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: + +```javascript +import { defineConfig } from "vite"; +import copy from "rollup-plugin-copy"; + +export default defineConfig({ + esbuild: { + target: "esnext", + }, + optimizeDeps: { + esbuildOptions: { + target: "esnext", + }, + }, + plugins: [ + copy({ + targets: [ + { src: "node_modules/**/*.wasm", dest: "node_modules/.vite/dist" }, + ], + copySync: true, + hook: "buildStart", + }), + ], + server: { + port: 3000, + }, +}); +``` + +#### Install Dependencies + +Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: + +```bash +npm install && npm install @noir-lang/backend_barretenberg@0.27.0 @noir-lang/noir_js@0.27.0 +npm install rollup-plugin-copy --save-dev ``` :::info @@ -99,7 +144,7 @@ At this point in the tutorial, your folder structure should look like this: #### Some cleanup -`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `index.html`, `main.js` and `package.json`. I feel lighter already. +`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `vite.config.js`, `index.html`, `main.js` and `package.json`. I feel lighter already. ![my heart is ready for you, noir.js](@site/static/img/memes/titanic.jpeg) @@ -139,7 +184,7 @@ Our app won't run like this, of course. We need some working HTML, at least. Let ``` -It *could* be a beautiful UI... Depending on which universe you live in. +It _could_ be a beautiful UI... Depending on which universe you live in. ## Some good old vanilla Javascript @@ -150,14 +195,14 @@ Start by pasting in this boilerplate code: ```js const setup = async () => { await Promise.all([ - import("@noir-lang/noirc_abi").then(module => - module.default(new URL("@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm", import.meta.url).toString()) + import('@noir-lang/noirc_abi').then((module) => + module.default(new URL('@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm', import.meta.url).toString()), + ), + import('@noir-lang/acvm_js').then((module) => + module.default(new URL('@noir-lang/acvm_js/web/acvm_js_bg.wasm', import.meta.url).toString()), ), - import("@noir-lang/acvm_js").then(module => - module.default(new URL("@noir-lang/acvm_js/web/acvm_js_bg.wasm", import.meta.url).toString()) - ) ]); -} +}; function display(container, msg) { const c = document.getElementById(container); @@ -169,11 +214,10 @@ function display(container, msg) { document.getElementById('submitGuess').addEventListener('click', async () => { try { // here's where love happens - } catch(err) { - display("logs", "Oh 💔 Wrong guess") + } catch (err) { + display('logs', 'Oh 💔 Wrong guess'); } }); - ``` The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 @@ -189,6 +233,7 @@ At this point in the tutorial, your folder structure should look like this: └── circuit └── ...same as above └── vite-project + ├── vite.config.js ├── main.js ├── package.json └── index.html @@ -209,7 +254,7 @@ import circuit from '../circuit/target/circuit.json'; [Noir is backend-agnostic](../index.mdx#whats-new-about-noir). We write Noir, but we also need a proving backend. That's why we need to import and instantiate the two dependencies we installed above: `BarretenbergBackend` and `Noir`. Let's import them right below: ```js -import { BarretenbergBackend } from '@noir-lang/backend_barretenberg'; +import { BarretenbergBackend, BarretenbergVerifier as Verifier } from '@noir-lang/backend_barretenberg'; import { Noir } from '@noir-lang/noir_js'; ``` @@ -264,8 +309,10 @@ Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add th ```js display('logs', 'Verifying proof... ⌛'); -const verification = await noir.verifyProof(proof); -if (verification) display('logs', 'Verifying proof... ✅'); +const verificationKey = await backend.getVerificationKey(); +const verifier = new Verifier(); +const isValid = await verifier.verifyProof(proof, verificationKey); +if (isValid) display('logs', 'Verifying proof... ✅'); ``` You have successfully generated a client-side Noir web app! diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.22.0/getting_started/tooling/index.md b/noir/noir-repo/docs/versioned_docs/version-v0.22.0/getting_started/tooling/index.md index 55df833005a..16f5f3ca45e 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.22.0/getting_started/tooling/index.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.22.0/getting_started/tooling/index.md @@ -1,15 +1,11 @@ --- title: Tooling -Description: This section provides information about the various tools and utilities available for Noir development. It covers the Noir playground, IDE tools, Codespaces, and community projects. +Description: This section provides information about the various tools and utilities available for Noir development. It covers IDE tools, Codespaces, and community projects. Keywords: [Noir, Development, Playground, IDE Tools, Language Service Provider, VS Code Extension, Codespaces, noir-starter, Community Projects, Awesome Noir Repository, Developer Tooling] --- Noir is meant to be easy to develop with. For that reason, a number of utilities have been put together to ease the development process as much as feasible in the zero-knowledge world. -## Playground - -The Noir playground is an easy way to test small ideas, share snippets, and integrate in other websites. You can access it at [play.noir-lang.org](https://play.noir-lang.org). - ## IDE tools When you install Nargo, you're also installing a Language Service Provider (LSP), which can be used by IDEs to provide syntax highlighting, codelens, warnings, and more. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.23.0/getting_started/tooling/index.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.23.0/getting_started/tooling/index.mdx index ac480f3c9f5..ec9ccea4115 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.23.0/getting_started/tooling/index.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.23.0/getting_started/tooling/index.mdx @@ -1,15 +1,11 @@ --- title: Tooling -Description: This section provides information about the various tools and utilities available for Noir development. It covers the Noir playground, IDE tools, Codespaces, and community projects. +Description: This section provides information about the various tools and utilities available for Noir development. It covers IDE tools, Codespaces, and community projects. Keywords: [Noir, Development, Playground, IDE Tools, Language Service Provider, VS Code Extension, Codespaces, noir-starter, Community Projects, Awesome Noir Repository, Developer Tooling] --- Noir is meant to be easy to develop with. For that reason, a number of utilities have been put together to ease the development process as much as feasible in the zero-knowledge world. -## Playground - -The Noir playground is an easy way to test small ideas, share snippets, and integrate in other websites. You can access it at [play.noir-lang.org](https://play.noir-lang.org). - ## IDE tools When you install Nargo, you're also installing a Language Service Provider (LSP), which can be used by IDEs to provide syntax highlighting, codelens, warnings, and more. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.24.0/getting_started/tooling/index.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.24.0/getting_started/tooling/index.mdx index ac480f3c9f5..ec9ccea4115 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.24.0/getting_started/tooling/index.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.24.0/getting_started/tooling/index.mdx @@ -1,15 +1,11 @@ --- title: Tooling -Description: This section provides information about the various tools and utilities available for Noir development. It covers the Noir playground, IDE tools, Codespaces, and community projects. +Description: This section provides information about the various tools and utilities available for Noir development. It covers IDE tools, Codespaces, and community projects. Keywords: [Noir, Development, Playground, IDE Tools, Language Service Provider, VS Code Extension, Codespaces, noir-starter, Community Projects, Awesome Noir Repository, Developer Tooling] --- Noir is meant to be easy to develop with. For that reason, a number of utilities have been put together to ease the development process as much as feasible in the zero-knowledge world. -## Playground - -The Noir playground is an easy way to test small ideas, share snippets, and integrate in other websites. You can access it at [play.noir-lang.org](https://play.noir-lang.org). - ## IDE tools When you install Nargo, you're also installing a Language Service Provider (LSP), which can be used by IDEs to provide syntax highlighting, codelens, warnings, and more. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.25.0/getting_started/tooling/index.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.25.0/getting_started/tooling/index.mdx index ac480f3c9f5..ec9ccea4115 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.25.0/getting_started/tooling/index.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.25.0/getting_started/tooling/index.mdx @@ -1,15 +1,11 @@ --- title: Tooling -Description: This section provides information about the various tools and utilities available for Noir development. It covers the Noir playground, IDE tools, Codespaces, and community projects. +Description: This section provides information about the various tools and utilities available for Noir development. It covers IDE tools, Codespaces, and community projects. Keywords: [Noir, Development, Playground, IDE Tools, Language Service Provider, VS Code Extension, Codespaces, noir-starter, Community Projects, Awesome Noir Repository, Developer Tooling] --- Noir is meant to be easy to develop with. For that reason, a number of utilities have been put together to ease the development process as much as feasible in the zero-knowledge world. -## Playground - -The Noir playground is an easy way to test small ideas, share snippets, and integrate in other websites. You can access it at [play.noir-lang.org](https://play.noir-lang.org). - ## IDE tools When you install Nargo, you're also installing a Language Service Provider (LSP), which can be used by IDEs to provide syntax highlighting, codelens, warnings, and more. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md b/noir/noir-repo/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md index 12beb476994..02044cd2ff6 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.25.0/tutorials/noirjs_app.md @@ -14,9 +14,9 @@ You can find the complete app code for this guide [here](https://github.com/noir :::note -Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.19.x matches `noir_js@0.19.x`, etc. +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.25.x matches `noir_js@0.25.x`, etc. -In this guide, we will be pinned to 0.19.4. +In this guide, we will be pinned to 0.25.0. ::: @@ -34,7 +34,7 @@ Easy enough. Onwards! ## Our project -ZK is a powerful technology. An app that doesn't reveal one of the inputs to *anyone* is almost unbelievable, yet Noir makes it as easy as a single line of code. +ZK is a powerful technology. An app that doesn't reveal one of the inputs to _anyone_ is almost unbelievable, yet Noir makes it as easy as a single line of code. In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! @@ -42,13 +42,13 @@ In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! Run: -```nargo new circuit``` +`nargo new circuit` And... That's about it. Your program is ready to be compiled and run. To compile, let's `cd` into the `circuit` folder to enter our project, and call: -```nargo compile``` +`nargo compile` This compiles our circuit into `json` format and add it to a new `target` folder. @@ -77,10 +77,55 @@ Vite is a powerful tool to generate static websites. While it provides all kinds To do this this, go back to the previous folder (`cd ..`) and create a new vite project by running `npm create vite` and choosing "Vanilla" and "Javascript". -You should see `vite-project` appear in your root folder. This seems like a good time to `cd` into it and install our NoirJS packages: +A wild `vite-project` directory should now appear in your root folder! Let's not waste any time and dive right in: ```bash -npm i @noir-lang/backend_barretenberg@0.19.4 @noir-lang/noir_js@0.19.4 +cd vite-project +``` + +### Setting Up Vite and Configuring the Project + +Before we proceed with any coding, let's get our environment tailored for Noir. We'll start by laying down the foundations with a `vite.config.js` file. This little piece of configuration is our secret sauce for making sure everything meshes well with the NoirJS libraries and other special setups we might need, like handling WebAssembly modules. Here’s how you get that going: + +#### Creating the vite.config.js + +In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: + +```javascript +import { defineConfig } from "vite"; +import copy from "rollup-plugin-copy"; + +export default defineConfig({ + esbuild: { + target: "esnext", + }, + optimizeDeps: { + esbuildOptions: { + target: "esnext", + }, + }, + plugins: [ + copy({ + targets: [ + { src: "node_modules/**/*.wasm", dest: "node_modules/.vite/dist" }, + ], + copySync: true, + hook: "buildStart", + }), + ], + server: { + port: 3000, + }, +}); +``` + +#### Install Dependencies + +Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: + +```bash +npm install && npm install @noir-lang/backend_barretenberg@0.25.0 @noir-lang/noir_js@0.25.0 +npm install rollup-plugin-copy --save-dev ``` :::info @@ -99,7 +144,7 @@ At this point in the tutorial, your folder structure should look like this: #### Some cleanup -`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `index.html`, `main.js` and `package.json`. I feel lighter already. +`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `vite.config.js`, `index.html`, `main.js` and `package.json`. I feel lighter already. ![my heart is ready for you, noir.js](@site/static/img/memes/titanic.jpeg) @@ -139,7 +184,7 @@ Our app won't run like this, of course. We need some working HTML, at least. Let ``` -It *could* be a beautiful UI... Depending on which universe you live in. +It _could_ be a beautiful UI... Depending on which universe you live in. ## Some good old vanilla Javascript @@ -150,14 +195,14 @@ Start by pasting in this boilerplate code: ```js const setup = async () => { await Promise.all([ - import("@noir-lang/noirc_abi").then(module => - module.default(new URL("@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm", import.meta.url).toString()) + import('@noir-lang/noirc_abi').then((module) => + module.default(new URL('@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm', import.meta.url).toString()), + ), + import('@noir-lang/acvm_js').then((module) => + module.default(new URL('@noir-lang/acvm_js/web/acvm_js_bg.wasm', import.meta.url).toString()), ), - import("@noir-lang/acvm_js").then(module => - module.default(new URL("@noir-lang/acvm_js/web/acvm_js_bg.wasm", import.meta.url).toString()) - ) ]); -} +}; function display(container, msg) { const c = document.getElementById(container); @@ -169,11 +214,10 @@ function display(container, msg) { document.getElementById('submitGuess').addEventListener('click', async () => { try { // here's where love happens - } catch(err) { - display("logs", "Oh 💔 Wrong guess") + } catch (err) { + display('logs', 'Oh 💔 Wrong guess'); } }); - ``` The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 @@ -189,6 +233,7 @@ At this point in the tutorial, your folder structure should look like this: └── circuit └── ...same as above └── vite-project + └── vite.config.js ├── main.js ├── package.json └── index.html diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.26.0/getting_started/tooling/index.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.26.0/getting_started/tooling/index.mdx index ac480f3c9f5..ec9ccea4115 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.26.0/getting_started/tooling/index.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.26.0/getting_started/tooling/index.mdx @@ -1,15 +1,11 @@ --- title: Tooling -Description: This section provides information about the various tools and utilities available for Noir development. It covers the Noir playground, IDE tools, Codespaces, and community projects. +Description: This section provides information about the various tools and utilities available for Noir development. It covers IDE tools, Codespaces, and community projects. Keywords: [Noir, Development, Playground, IDE Tools, Language Service Provider, VS Code Extension, Codespaces, noir-starter, Community Projects, Awesome Noir Repository, Developer Tooling] --- Noir is meant to be easy to develop with. For that reason, a number of utilities have been put together to ease the development process as much as feasible in the zero-knowledge world. -## Playground - -The Noir playground is an easy way to test small ideas, share snippets, and integrate in other websites. You can access it at [play.noir-lang.org](https://play.noir-lang.org). - ## IDE tools When you install Nargo, you're also installing a Language Service Provider (LSP), which can be used by IDEs to provide syntax highlighting, codelens, warnings, and more. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md b/noir/noir-repo/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md index 12beb476994..d098827e6d9 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md +++ b/noir/noir-repo/docs/versioned_docs/version-v0.26.0/tutorials/noirjs_app.md @@ -14,9 +14,9 @@ You can find the complete app code for this guide [here](https://github.com/noir :::note -Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.19.x matches `noir_js@0.19.x`, etc. +Feel free to use whatever versions, just keep in mind that Nargo and the NoirJS packages are meant to be in sync. For example, Nargo 0.26.x matches `noir_js@0.26.x`, etc. -In this guide, we will be pinned to 0.19.4. +In this guide, we will be pinned to 0.26.0. ::: @@ -34,7 +34,7 @@ Easy enough. Onwards! ## Our project -ZK is a powerful technology. An app that doesn't reveal one of the inputs to *anyone* is almost unbelievable, yet Noir makes it as easy as a single line of code. +ZK is a powerful technology. An app that doesn't reveal one of the inputs to _anyone_ is almost unbelievable, yet Noir makes it as easy as a single line of code. In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! @@ -42,13 +42,13 @@ In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! Run: -```nargo new circuit``` +`nargo new circuit` And... That's about it. Your program is ready to be compiled and run. To compile, let's `cd` into the `circuit` folder to enter our project, and call: -```nargo compile``` +`nargo compile` This compiles our circuit into `json` format and add it to a new `target` folder. @@ -77,10 +77,55 @@ Vite is a powerful tool to generate static websites. While it provides all kinds To do this this, go back to the previous folder (`cd ..`) and create a new vite project by running `npm create vite` and choosing "Vanilla" and "Javascript". -You should see `vite-project` appear in your root folder. This seems like a good time to `cd` into it and install our NoirJS packages: +A wild `vite-project` directory should now appear in your root folder! Let's not waste any time and dive right in: ```bash -npm i @noir-lang/backend_barretenberg@0.19.4 @noir-lang/noir_js@0.19.4 +cd vite-project +``` + +### Setting Up Vite and Configuring the Project + +Before we proceed with any coding, let's get our environment tailored for Noir. We'll start by laying down the foundations with a `vite.config.js` file. This little piece of configuration is our secret sauce for making sure everything meshes well with the NoirJS libraries and other special setups we might need, like handling WebAssembly modules. Here’s how you get that going: + +#### Creating the vite.config.js + +In your freshly minted `vite-project` folder, create a new file named `vite.config.js` and open it in your code editor. Paste the following to set the stage: + +```javascript +import { defineConfig } from "vite"; +import copy from "rollup-plugin-copy"; + +export default defineConfig({ + esbuild: { + target: "esnext", + }, + optimizeDeps: { + esbuildOptions: { + target: "esnext", + }, + }, + plugins: [ + copy({ + targets: [ + { src: "node_modules/**/*.wasm", dest: "node_modules/.vite/dist" }, + ], + copySync: true, + hook: "buildStart", + }), + ], + server: { + port: 3000, + }, +}); +``` + +#### Install Dependencies + +Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: + +```bash +npm install && npm install @noir-lang/backend_barretenberg@0.26.0 @noir-lang/noir_js@0.26.0 +npm install rollup-plugin-copy --save-dev ``` :::info @@ -99,7 +144,7 @@ At this point in the tutorial, your folder structure should look like this: #### Some cleanup -`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `index.html`, `main.js` and `package.json`. I feel lighter already. +`npx create vite` is amazing but it creates a bunch of files we don't really need for our simple example. Actually, let's just delete everything except for `vite.config.js`, `index.html`, `main.js` and `package.json`. I feel lighter already. ![my heart is ready for you, noir.js](@site/static/img/memes/titanic.jpeg) @@ -139,7 +184,7 @@ Our app won't run like this, of course. We need some working HTML, at least. Let ``` -It *could* be a beautiful UI... Depending on which universe you live in. +It _could_ be a beautiful UI... Depending on which universe you live in. ## Some good old vanilla Javascript @@ -150,14 +195,14 @@ Start by pasting in this boilerplate code: ```js const setup = async () => { await Promise.all([ - import("@noir-lang/noirc_abi").then(module => - module.default(new URL("@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm", import.meta.url).toString()) + import('@noir-lang/noirc_abi').then((module) => + module.default(new URL('@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm', import.meta.url).toString()), + ), + import('@noir-lang/acvm_js').then((module) => + module.default(new URL('@noir-lang/acvm_js/web/acvm_js_bg.wasm', import.meta.url).toString()), ), - import("@noir-lang/acvm_js").then(module => - module.default(new URL("@noir-lang/acvm_js/web/acvm_js_bg.wasm", import.meta.url).toString()) - ) ]); -} +}; function display(container, msg) { const c = document.getElementById(container); @@ -169,11 +214,10 @@ function display(container, msg) { document.getElementById('submitGuess').addEventListener('click', async () => { try { // here's where love happens - } catch(err) { - display("logs", "Oh 💔 Wrong guess") + } catch (err) { + display('logs', 'Oh 💔 Wrong guess'); } }); - ``` The display function doesn't do much. We're simply manipulating our website to see stuff happening. For example, if the proof fails, it will simply log a broken heart 😢 @@ -189,6 +233,7 @@ At this point in the tutorial, your folder structure should look like this: └── circuit └── ...same as above └── vite-project + └── vite.config.js ├── main.js ├── package.json └── index.html diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.27.0/getting_started/tooling/index.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.27.0/getting_started/tooling/index.mdx index ac480f3c9f5..ec9ccea4115 100644 --- a/noir/noir-repo/docs/versioned_docs/version-v0.27.0/getting_started/tooling/index.mdx +++ b/noir/noir-repo/docs/versioned_docs/version-v0.27.0/getting_started/tooling/index.mdx @@ -1,15 +1,11 @@ --- title: Tooling -Description: This section provides information about the various tools and utilities available for Noir development. It covers the Noir playground, IDE tools, Codespaces, and community projects. +Description: This section provides information about the various tools and utilities available for Noir development. It covers IDE tools, Codespaces, and community projects. Keywords: [Noir, Development, Playground, IDE Tools, Language Service Provider, VS Code Extension, Codespaces, noir-starter, Community Projects, Awesome Noir Repository, Developer Tooling] --- Noir is meant to be easy to develop with. For that reason, a number of utilities have been put together to ease the development process as much as feasible in the zero-knowledge world. -## Playground - -The Noir playground is an easy way to test small ideas, share snippets, and integrate in other websites. You can access it at [play.noir-lang.org](https://play.noir-lang.org). - ## IDE tools When you install Nargo, you're also installing a Language Service Provider (LSP), which can be used by IDEs to provide syntax highlighting, codelens, warnings, and more. diff --git a/noir/noir-repo/noir-logo.png b/noir/noir-repo/noir-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..eabb163ad73f2728f914a1bcc06c23b8832c2f36 GIT binary patch literal 224467 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�>#h1$GTE(q!!JqP$Jg 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 literal 0 HcmV?d00001 diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index dde29d7ee87..457b2cfa167 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -314,3 +314,55 @@ impl Ord for (A, B, C, D, E) where A: Ord, B: Ord, C: Ord, D: Ord result } } + +// Compares and returns the maximum of two values. +// +// Returns the second argument if the comparison determines them to be equal. +// +// # Examples +// +// ``` +// use std::cmp; +// +// assert_eq(cmp::max(1, 2), 2); +// assert_eq(cmp::max(2, 2), 2); +// ``` +pub fn max(v1: T, v2: T) -> T where T: Ord { + if v1 > v2 { v1 } else { v2 } +} + +// Compares and returns the minimum of two values. +// +// Returns the first argument if the comparison determines them to be equal. +// +// # Examples +// +// ``` +// use std::cmp; +// +// assert_eq(cmp::min(1, 2), 1); +// assert_eq(cmp::min(2, 2), 2); +// ``` +pub fn min(v1: T, v2: T) -> T where T: Ord { + if v1 > v2 { v2 } else { v1 } +} + +mod cmp_tests { + use crate::cmp::{min, max}; + + #[test] + fn sanity_check_min() { + assert_eq(min(0 as u64, 1 as u64), 0); + assert_eq(min(0 as u64, 0 as u64), 0); + assert_eq(min(1 as u64, 1 as u64), 1); + assert_eq(min(255 as u8, 0 as u8), 0); + } + + #[test] + fn sanity_check_max() { + assert_eq(max(0 as u64, 1 as u64), 1); + assert_eq(max(0 as u64, 0 as u64), 0); + assert_eq(max(1 as u64, 1 as u64), 1); + assert_eq(max(255 as u8, 0 as u8), 255); + } +} diff --git a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr index c789bc386ef..c6a3365a979 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/bounded_vec.nr @@ -1,3 +1,5 @@ +use crate::cmp::Eq; + struct BoundedVec { storage: [T; MaxLen], len: u64, @@ -93,3 +95,37 @@ impl BoundedVec { ret } } + +impl Eq for BoundedVec where T: Eq { + fn eq(self, other: BoundedVec) -> bool { + // TODO: https://github.com/noir-lang/noir/issues/4837 + // + // We make the assumption that the user has used the proper interface for working with `BoundedVec`s + // rather than directly manipulating the internal fields as this can result in an inconsistent internal state. + + (self.len == other.len) & (self.storage == other.storage) + } +} + +mod bounded_vec_tests { + // TODO: Allow imports from "super" + use crate::collections::bounded_vec::BoundedVec; + + #[test] + fn empty_equality() { + let mut bounded_vec1: BoundedVec = BoundedVec::new(); + let mut bounded_vec2: BoundedVec = BoundedVec::new(); + + assert_eq(bounded_vec1, bounded_vec2); + } + + #[test] + fn inequality() { + let mut bounded_vec1: BoundedVec = BoundedVec::new(); + let mut bounded_vec2: BoundedVec = BoundedVec::new(); + bounded_vec1.push(1); + bounded_vec2.push(2); + + assert(bounded_vec1 != bounded_vec2); + } +} diff --git a/noir/noir-repo/noir_stdlib/src/convert.nr b/noir/noir-repo/noir_stdlib/src/convert.nr index 00ac0a0fd8c..d3537df3c5e 100644 --- a/noir/noir-repo/noir_stdlib/src/convert.nr +++ b/noir/noir-repo/noir_stdlib/src/convert.nr @@ -12,12 +12,12 @@ impl From for T { // docs:start:into-trait trait Into { - fn into(input: Self) -> T; + fn into(self) -> T; } impl Into for U where T: From { - fn into(input: U) -> T { - T::from(input) + fn into(self) -> T { + T::from(self) } } // docs:end:into-trait diff --git a/noir/noir-repo/noir_stdlib/src/hash.nr b/noir/noir-repo/noir_stdlib/src/hash.nr index 1a61b5e084e..26a9fa6c2c0 100644 --- a/noir/noir-repo/noir_stdlib/src/hash.nr +++ b/noir/noir-repo/noir_stdlib/src/hash.nr @@ -1,7 +1,6 @@ mod poseidon; mod mimc; mod poseidon2; -mod pedersen; use crate::default::Default; use crate::uint128::U128; @@ -12,36 +11,18 @@ pub fn sha256(input: [u8; N]) -> [u8; 32] // docs:end:sha256 {} -#[foreign(sha256)] -// docs:start:sha256_slice -pub fn sha256_slice(input: [u8]) -> [u8; 32] -// docs:end:sha256_slice -{} - #[foreign(blake2s)] // docs:start:blake2s pub fn blake2s(input: [u8; N]) -> [u8; 32] // docs:end:blake2s {} -#[foreign(blake2s)] -// docs:start:blake2s_slice -pub fn blake2s_slice(input: [u8]) -> [u8; 32] -// docs:end:blake2s_slice -{} - #[foreign(blake3)] // docs:start:blake3 pub fn blake3(input: [u8; N]) -> [u8; 32] // docs:end:blake3 {} -#[foreign(blake3)] -// docs:start:blake3_slice -pub fn blake3_slice(input: [u8]) -> [u8; 32] -// docs:end:blake3_slice -{} - // docs:start:pedersen_commitment struct PedersenPoint { x : Field, @@ -53,28 +34,14 @@ pub fn pedersen_commitment(input: [Field; N]) -> PedersenPoint { pedersen_commitment_with_separator(input, 0) } -// docs:start:pedersen_commitment_slice -pub fn pedersen_commitment_slice(input: [Field]) -> PedersenPoint { - pedersen_commitment_with_separator_slice(input, 0) -} -// docs:end:pedersen_commitment_slice - #[foreign(pedersen_commitment)] pub fn __pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> [Field; 2] {} -#[foreign(pedersen_commitment)] -pub fn __pedersen_commitment_with_separator_slice(input: [Field], separator: u32) -> [Field; 2] {} - pub fn pedersen_commitment_with_separator(input: [Field; N], separator: u32) -> PedersenPoint { let values = __pedersen_commitment_with_separator(input, separator); PedersenPoint { x: values[0], y: values[1] } } -pub fn pedersen_commitment_with_separator_slice(input: [Field], separator: u32) -> PedersenPoint { - let values = __pedersen_commitment_with_separator_slice(input, separator); - PedersenPoint { x: values[0], y: values[1] } -} - // docs:start:pedersen_hash pub fn pedersen_hash(input: [Field; N]) -> Field // docs:end:pedersen_hash @@ -82,31 +49,18 @@ pub fn pedersen_hash(input: [Field; N]) -> Field pedersen_hash_with_separator(input, 0) } -// docs:start:pedersen_hash_slice -pub fn pedersen_hash_slice(input: [Field]) -> Field -// docs:end:pedersen_hash_slice -{ - pedersen_hash_with_separator_slice(input, 0) -} - #[foreign(pedersen_hash)] pub fn pedersen_hash_with_separator(input: [Field; N], separator: u32) -> Field {} -#[foreign(pedersen_hash)] -pub fn pedersen_hash_with_separator_slice(input: [Field], separator: u32) -> Field {} - pub fn hash_to_field(inputs: [Field]) -> Field { - let mut inputs_as_bytes = &[]; + let mut sum = 0; for input in inputs { - let input_bytes = input.to_le_bytes(32); - for i in 0..32 { - inputs_as_bytes = inputs_as_bytes.push_back(input_bytes[i]); - } + let input_bytes: [u8; 32] = input.to_le_bytes(32).as_array(); + sum += crate::field::bytes32_to_field(blake2s(input_bytes)); } - let hashed_input = blake2s_slice(inputs_as_bytes); - crate::field::bytes32_to_field(hashed_input) + sum } #[foreign(keccak256)] @@ -115,12 +69,6 @@ pub fn keccak256(input: [u8; N], message_size: u32) -> [u8; 32] // docs:end:keccak256 {} -#[foreign(keccak256)] -// docs:start:keccak256_slice -pub fn keccak256_slice(input: [u8], message_size: u32) -> [u8; 32] -// docs:end:keccak256_slice -{} - #[foreign(poseidon2_permutation)] pub fn poseidon2_permutation(_input: [Field; N], _state_length: u32) -> [Field; N] {} @@ -140,7 +88,7 @@ trait Hash{ trait Hasher{ fn finish(self) -> Field; - fn write(&mut self, input: [Field]); + fn write(&mut self, input: Field); } // BuildHasher is a factory trait, responsible for production of specific Hasher. @@ -170,49 +118,49 @@ where impl Hash for Field { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self]); + H::write(state, self); } } impl Hash for u8 { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self as Field]); + H::write(state, self as Field); } } impl Hash for u32 { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self as Field]); + H::write(state, self as Field); } } impl Hash for u64 { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self as Field]); + H::write(state, self as Field); } } impl Hash for i8 { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self as Field]); + H::write(state, self as Field); } } impl Hash for i32 { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self as Field]); + H::write(state, self as Field); } } impl Hash for i64 { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self as Field]); + H::write(state, self as Field); } } impl Hash for bool { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self as Field]); + H::write(state, self as Field); } } @@ -222,7 +170,8 @@ impl Hash for () { impl Hash for U128 { fn hash(self, state: &mut H) where H: Hasher{ - H::write(state, &[self.lo as Field, self.hi as Field]); + H::write(state, self.lo as Field); + H::write(state, self.hi as Field); } } diff --git a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr index 1fb53701013..6c5502c2fbf 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr @@ -126,9 +126,8 @@ pub fn mimc_bn254(array: [Field; N]) -> Field { r } -struct MimcHasher{ +struct MimcHasher { _state: [Field], - _len: u64, } impl Hasher for MimcHasher { @@ -136,24 +135,22 @@ impl Hasher for MimcHasher { fn finish(self) -> Field { let exponent = 7; let mut r = 0; - for i in 0..self._len { + for i in 0..self._state.len() { let h = mimc(self._state[i], r, MIMC_BN254_CONSTANTS, exponent); r = r + self._state[i] + h; } r } - fn write(&mut self, input: [Field]){ - self._state = self._state.append(input); - self._len += input.len(); + fn write(&mut self, input: Field){ + self._state = self._state.push_back(input); } } impl Default for MimcHasher{ fn default() -> Self{ - MimcHasher{ + MimcHasher { _state: &[], - _len: 0, } } } diff --git a/noir/noir-repo/noir_stdlib/src/hash/pedersen.nr b/noir/noir-repo/noir_stdlib/src/hash/pedersen.nr deleted file mode 100644 index ad21e728945..00000000000 --- a/noir/noir-repo/noir_stdlib/src/hash/pedersen.nr +++ /dev/null @@ -1,24 +0,0 @@ -use crate::hash::{Hasher, pedersen_hash_slice}; -use crate::default::Default; - -struct PedersenHasher{ - _state: [Field] -} - -impl Hasher for PedersenHasher { - fn finish(self) -> Field { - pedersen_hash_slice(self._state) - } - - fn write(&mut self, input: [Field]){ - self._state = self._state.append(input); - } -} - -impl Default for PedersenHasher{ - fn default() -> Self{ - PedersenHasher{ - _state: &[] - } - } -} diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr index 85a0802f630..742bfcaf804 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr @@ -105,66 +105,65 @@ fn apply_matrix(a: [Field; M], x: [Field; N]) -> [Field; N] { struct PoseidonHasher{ _state: [Field], - _len: u64, } impl Hasher for PoseidonHasher { #[field(bn254)] fn finish(self) -> Field { let mut result = 0; - assert(self._len < 16); - if self._len == 1 { + let len = self._state.len(); + assert(len < 16); + if len == 1 { result = bn254::hash_1([self._state[0]]); } - if self._len == 2 { + if len == 2 { result = bn254::hash_2([self._state[0],self._state[1]]); } - if self._len == 3 { + if len == 3 { result = bn254::hash_3([self._state[0],self._state[1],self._state[2]]); } - if self._len == 4 { + if len == 4 { result = bn254::hash_4([self._state[0],self._state[1],self._state[2],self._state[3]]); } - if self._len == 5 { + if len == 5 { result = bn254::hash_5([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4]]); } - if self._len == 6 { + if len == 6 { result = bn254::hash_6([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5]]); } - if self._len == 7 { + if len == 7 { result = bn254::hash_7([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6]]); } - if self._len == 8 { + if len == 8 { result = bn254::hash_8([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7]]); } - if self._len == 9 { + if len == 9 { result = bn254::hash_9([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8]]); } - if self._len == 10 { + if len == 10 { result = bn254::hash_10([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9]]); } - if self._len == 11 { + if len == 11 { result = bn254::hash_11([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10]]); } - if self._len == 12 { + if len == 12 { result = bn254::hash_12([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11]]); } - if self._len == 13 { + if len == 13 { result = bn254::hash_13([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12]]); } - if self._len == 14 { + if len == 14 { result = bn254::hash_14([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12], self._state[13]]); } - if self._len == 15 { + if len == 15 { result = bn254::hash_15([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12], self._state[13], self._state[14]]); } result } - fn write(&mut self, input: [Field]){ - self._state = self._state.append(input); - self._len += input.len(); + fn write(&mut self, input: Field){ + self._state = self._state.push_back(input); } } @@ -172,7 +171,6 @@ impl Default for PoseidonHasher{ fn default() -> Self{ PoseidonHasher{ _state: &[], - _len: 0, } } } diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 12bf373e671..e5a82a596c6 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -117,30 +117,27 @@ impl Poseidon2 { struct Poseidon2Hasher{ _state: [Field], - _len: u64, } impl Hasher for Poseidon2Hasher { fn finish(self) -> Field { let iv : Field = (self._state.len() as Field)*18446744073709551616; // iv = (self._state.len() << 64) let mut sponge = Poseidon2::new(iv); - for i in 0..self._len { + for i in 0..self._state.len() { sponge.absorb(self._state[i]); } sponge.squeeze() } - fn write(&mut self, input: [Field]){ - self._state = self._state.append(input); - self._len += input.len(); + fn write(&mut self, input: Field){ + self._state = self._state.push_back(input); } } -impl Default for Poseidon2Hasher{ - fn default() -> Self{ - Poseidon2Hasher{ +impl Default for Poseidon2Hasher { + fn default() -> Self { + Poseidon2Hasher { _state: &[], - _len: 0, } } } diff --git a/noir/noir-repo/noir_stdlib/src/slice.nr b/noir/noir-repo/noir_stdlib/src/slice.nr index 164b4f96cf6..ac542a960ed 100644 --- a/noir/noir-repo/noir_stdlib/src/slice.nr +++ b/noir/noir-repo/noir_stdlib/src/slice.nr @@ -43,4 +43,14 @@ impl [T] { } self } + + pub fn as_array(self) -> [T; N] { + assert(self.len() == N); + + let mut array = [crate::unsafe::zeroed(); N]; + for i in 0..N { + array[i] = self[i]; + } + array + } } diff --git a/noir/noir-repo/rust-toolchain.toml b/noir/noir-repo/rust-toolchain.toml index 0e5ac891ce9..fe2949c8458 100644 --- a/noir/noir-repo/rust-toolchain.toml +++ b/noir/noir-repo/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.73.0" +channel = "1.74.1" components = [ "rust-src" ] targets = [ "wasm32-unknown-unknown", "wasm32-wasi", "aarch64-apple-darwin" ] profile = "default" diff --git a/noir/noir-repo/scripts/benchmark_start.sh b/noir/noir-repo/scripts/benchmark_start.sh new file mode 100755 index 00000000000..3e69b3d2c65 --- /dev/null +++ b/noir/noir-repo/scripts/benchmark_start.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo -1 | sudo tee /proc/sys/kernel/perf_event_paranoid diff --git a/noir/noir-repo/scripts/benchmark_stop.sh b/noir/noir-repo/scripts/benchmark_stop.sh new file mode 100755 index 00000000000..964e5291817 --- /dev/null +++ b/noir/noir-repo/scripts/benchmark_stop.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo 4 | sudo tee /proc/sys/kernel/perf_event_paranoid diff --git a/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/src/main.nr b/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/src/main.nr index ade43f898e1..907c3628142 100644 --- a/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/src/main.nr @@ -1,6 +1,6 @@ use dep::std::collections::map::HashMap; use dep::std::hash::BuildHasherDefault; -use dep::std::hash::pedersen::PedersenHasher; +use dep::std::hash::poseidon2::Poseidon2Hasher; struct Entry{ key: Field, @@ -10,7 +10,7 @@ struct Entry{ global HASHMAP_CAP = 8; global HASHMAP_LEN = 6; -fn allocate_hashmap() -> HashMap> { +fn allocate_hashmap() -> HashMap> { HashMap::default() } diff --git a/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr b/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr index fd1a95ee5fb..012c8466f2f 100644 --- a/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr @@ -4,7 +4,6 @@ use dep::std::ec::tecurve::affine::Point as TEPoint; use dep::std::hash; use dep::std::eddsa::{eddsa_to_pub, eddsa_poseidon_verify, eddsa_verify_with_hasher}; use dep::std::hash::poseidon2::Poseidon2Hasher; -use dep::std::hash::pedersen::PedersenHasher; fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) { // Skip this test for non-bn254 backends @@ -53,8 +52,5 @@ fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) { // Using a different hash should fail let mut hasher = Poseidon2Hasher::default(); assert(!eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher)); - // Using a different hash should fail - let mut hasher = PedersenHasher::default(); - assert(!eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher)); } } diff --git a/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Nargo.toml b/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Nargo.toml new file mode 100644 index 00000000000..8c2bc79ea8d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fold_numeric_generic_poseidon" +type = "bin" +authors = [""] +compiler_version = ">=0.27.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Prover.toml b/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Prover.toml new file mode 100644 index 00000000000..00e821cf89d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/Prover.toml @@ -0,0 +1,2 @@ +enable = [true, false] +to_hash = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] diff --git a/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr b/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr new file mode 100644 index 00000000000..f9f3e75789b --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/fold_numeric_generic_poseidon/src/main.nr @@ -0,0 +1,33 @@ +use dep::std::hash::{pedersen_hash_with_separator, poseidon2::Poseidon2}; + +global NUM_HASHES = 2; +global HASH_LENGTH = 10; + +#[fold] +pub fn poseidon_hash(inputs: [Field; N]) -> Field { + Poseidon2::hash(inputs, inputs.len()) +} + +fn main( + to_hash: [[Field; HASH_LENGTH]; NUM_HASHES], + enable: [bool; NUM_HASHES] +) -> pub [Field; NUM_HASHES + 1] { + let mut result = [0; NUM_HASHES + 1]; + for i in 0..NUM_HASHES { + let enable = enable[i]; + let to_hash = to_hash[i]; + if enable { + result[i] = poseidon_hash(to_hash); + } + } + + // We want to make sure that the foldable function with a numeric generic + // is monomorphized correctly. + let mut double_preimage = [0; 20]; + for i in 0..HASH_LENGTH * 2 { + double_preimage[i] = to_hash[0][i % HASH_LENGTH]; + } + result[NUM_HASHES] = poseidon_hash(double_preimage); + + result +} diff --git a/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr b/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr index 4d2cbd45993..76daa594a89 100644 --- a/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr @@ -2,7 +2,7 @@ mod utils; use dep::std::collections::map::HashMap; use dep::std::hash::BuildHasherDefault; -use dep::std::hash::pedersen::PedersenHasher; +use dep::std::hash::poseidon2::Poseidon2Hasher; use dep::std::cmp::Eq; use utils::cut; @@ -25,7 +25,7 @@ global K_CMP = FIELD_CMP; global V_CMP = FIELD_CMP; global KV_CMP = |a: (K, V), b: (K, V)| a.0.lt(b.0); -global ALLOCATE_HASHMAP = || -> HashMap> +global ALLOCATE_HASHMAP = || -> HashMap> HashMap::default(); fn main(input: [Entry; HASHMAP_LEN]) { @@ -194,24 +194,24 @@ fn test_mut_iterators() { } // docs:start:type_alias -type MyMap = HashMap>; +type MyMap = HashMap>; // docs:end:type_alias /// Tests examples from the stdlib hashmap documentation fn doc_tests() { // docs:start:default_example - let hashmap: HashMap> = HashMap::default(); + let hashmap: HashMap> = HashMap::default(); assert(hashmap.is_empty()); // docs:end:default_example // docs:start:with_hasher_example - let my_hasher: BuildHasherDefault = Default::default(); - let hashmap: HashMap> = HashMap::with_hasher(my_hasher); + let my_hasher: BuildHasherDefault = Default::default(); + let hashmap: HashMap> = HashMap::with_hasher(my_hasher); assert(hashmap.is_empty()); // docs:end:with_hasher_example // docs:start:insert_example - let mut map: HashMap> = HashMap::default(); + let mut map: HashMap> = HashMap::default(); map.insert(12, 42); assert(map.len() == 1); // docs:end:insert_example @@ -255,7 +255,7 @@ fn doc_tests() { // docs:end:len_example // docs:start:capacity_example - let empty_map: HashMap> = HashMap::default(); + let empty_map: HashMap> = HashMap::default(); assert(empty_map.len() == 0); assert(empty_map.capacity() == 42); // docs:end:capacity_example @@ -283,8 +283,8 @@ fn doc_tests() { // docs:end:retain_example // docs:start:eq_example - let mut map1: HashMap> = HashMap::default(); - let mut map2: HashMap> = HashMap::default(); + let mut map1: HashMap> = HashMap::default(); + let mut map2: HashMap> = HashMap::default(); map1.insert(1, 2); map1.insert(3, 4); @@ -297,7 +297,7 @@ fn doc_tests() { } // docs:start:get_example -fn get_example(map: HashMap>) { +fn get_example(map: HashMap>) { let x = map.get(12); if x.is_some() { @@ -306,7 +306,7 @@ fn get_example(map: HashMap> } // docs:end:get_example -fn entries_examples(map: HashMap>) { +fn entries_examples(map: HashMap>) { // docs:start:entries_example let entries = map.entries(); @@ -344,7 +344,7 @@ fn entries_examples(map: HashMap>) { +fn iter_examples(mut map: HashMap>) { // docs:start:iter_mut_example // Add 1 to each key in the map, and double the value associated with that key. map.iter_mut(|k, v| (k + 1, v * 2)); diff --git a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Nargo.toml b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Nargo.toml deleted file mode 100644 index 759c3b20ba8..00000000000 --- a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "regression_sha256_slice" -type = "bin" -authors = [""] -compiler_version = ">=0.26.0" - -[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Prover.toml b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Prover.toml deleted file mode 100644 index 8a027e9eca9..00000000000 --- a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Prover.toml +++ /dev/null @@ -1 +0,0 @@ -x = ["5", "10"] diff --git a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/src/main.nr deleted file mode 100644 index 60b0911cf09..00000000000 --- a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/src/main.nr +++ /dev/null @@ -1,12 +0,0 @@ -use dep::std; - -fn main(x: [u8; 2]) { - let mut y = x.as_slice(); - let digest1 = std::hash::sha256_slice(y); - let mut v = y; - if x[0] != 0 { - v = y.push_back(x[0]); - } - let digest2 = std::hash::sha256_slice(v); - assert(digest1 != digest2); -} diff --git a/noir/noir-repo/tooling/backend_interface/Cargo.toml b/noir/noir-repo/tooling/backend_interface/Cargo.toml index 2d991f9ae6c..b731c138c7d 100644 --- a/noir/noir-repo/tooling/backend_interface/Cargo.toml +++ b/noir/noir-repo/tooling/backend_interface/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "backend-interface" description = "The definition of the backend CLI interface which Nargo uses for proving/verifying ACIR circuits." -version = "0.11.0" +version.workspace = true authors.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/noir/noir-repo/tooling/backend_interface/src/cli/info.rs b/noir/noir-repo/tooling/backend_interface/src/cli/info.rs index 8ca3d4dd0a3..6e6603ce53e 100644 --- a/noir/noir-repo/tooling/backend_interface/src/cli/info.rs +++ b/noir/noir-repo/tooling/backend_interface/src/cli/info.rs @@ -56,7 +56,7 @@ fn info_command() -> Result<(), BackendError> { let expression_width = InfoCommand { crs_path }.run(backend.binary_path())?; - assert!(matches!(expression_width, ExpressionWidth::Bounded { width: 3 })); + assert!(matches!(expression_width, ExpressionWidth::Bounded { width: 4 })); Ok(()) } diff --git a/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs b/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs index 0f9770c805d..b3dfff9e94c 100644 --- a/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs +++ b/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs @@ -10,7 +10,7 @@ use const_format::formatcp; const USERNAME: &str = "AztecProtocol"; const REPO: &str = "aztec-packages"; -const VERSION: &str = "0.34.0"; +const VERSION: &str = "0.35.1"; const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = diff --git a/noir/noir-repo/tooling/debugger/Cargo.toml b/noir/noir-repo/tooling/debugger/Cargo.toml index 30d11db8cf3..a3bf12f5368 100644 --- a/noir/noir-repo/tooling/debugger/Cargo.toml +++ b/noir/noir-repo/tooling/debugger/Cargo.toml @@ -4,6 +4,7 @@ description = "Debugger for Noir" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true [build-dependencies] diff --git a/noir/noir-repo/tooling/debugger/ignored-tests.txt b/noir/noir-repo/tooling/debugger/ignored-tests.txt index 4507aeb8545..3b63f8d5542 100644 --- a/noir/noir-repo/tooling/debugger/ignored-tests.txt +++ b/noir/noir-repo/tooling/debugger/ignored-tests.txt @@ -15,3 +15,6 @@ to_bytes_integration fold_basic fold_basic_nested_call fold_call_witness_condition +fold_after_inlined_calls +fold_numeric_generic_poseidon + diff --git a/noir/noir-repo/tooling/lsp/Cargo.toml b/noir/noir-repo/tooling/lsp/Cargo.toml index 750e85694e2..a599b096e52 100644 --- a/noir/noir-repo/tooling/lsp/Cargo.toml +++ b/noir/noir-repo/tooling/lsp/Cargo.toml @@ -3,7 +3,8 @@ name = "noir_lsp" description = "Language server for Noir" version.workspace = true authors.workspace = true -edition.workspace = true +edition.workspace = true# +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/tooling/nargo/Cargo.toml b/noir/noir-repo/tooling/nargo/Cargo.toml index efd38a182e0..72b412ac5b9 100644 --- a/noir/noir-repo/tooling/nargo/Cargo.toml +++ b/noir/noir-repo/tooling/nargo/Cargo.toml @@ -4,13 +4,11 @@ description = "Noir's package manager" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[build-dependencies] -rustc_version = "0.4.0" - [dependencies] acvm.workspace = true fm.workspace = true diff --git a/noir/noir-repo/tooling/nargo/build.rs b/noir/noir-repo/tooling/nargo/build.rs deleted file mode 100644 index ab2b7579132..00000000000 --- a/noir/noir-repo/tooling/nargo/build.rs +++ /dev/null @@ -1,12 +0,0 @@ -use rustc_version::{version, Version}; - -fn check_rustc_version() { - assert!( - version().unwrap() >= Version::parse("1.73.0").unwrap(), - "The minimal supported rustc version is 1.73.0." - ); -} - -fn main() { - check_rustc_version(); -} diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index 1629ae86edb..c20be037e62 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -5,6 +5,7 @@ default-run = "nargo" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,7 +16,6 @@ name = "nargo" path = "src/main.rs" [build-dependencies] -rustc_version = "0.4.0" build-data.workspace = true toml.workspace = true @@ -72,13 +72,9 @@ assert_cmd = "2.0.8" assert_fs = "1.0.10" predicates = "2.1.5" fm.workspace = true -criterion = "0.5.0" +criterion.workspace = true +pprof.workspace = true paste = "1.0.14" -pprof = { version = "0.12", features = [ - "flamegraph", - "frame-pointer", - "criterion", -] } iai = "0.1.1" test-binary = "3.0.2" diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index bf97dfb3e96..0ed2d4c07f7 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -1,21 +1,11 @@ -use rustc_version::{version, Version}; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::{env, fs}; -fn check_rustc_version() { - assert!( - version().unwrap() >= Version::parse("1.73.0").unwrap(), - "The minimal supported rustc version is 1.73.0." - ); -} - const GIT_COMMIT: &&str = &"GIT_COMMIT"; fn main() { - check_rustc_version(); - // Only use build_data if the environment variable isn't set. if std::env::var(GIT_COMMIT).is_err() { build_data::set_GIT_COMMIT(); diff --git a/noir/noir-repo/tooling/nargo_fmt/Cargo.toml b/noir/noir-repo/tooling/nargo_fmt/Cargo.toml index 374413ac9f2..05b2fdb7d52 100644 --- a/noir/noir-repo/tooling/nargo_fmt/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_fmt/Cargo.toml @@ -3,6 +3,7 @@ name = "nargo_fmt" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true [dependencies] diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/array.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/array.rs index db7dc4701b7..011e775a018 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/array.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/array.rs @@ -1,4 +1,4 @@ -use noirc_frontend::{hir::resolution::errors::Span, token::Token, Expression}; +use noirc_frontend::{ast::Expression, hir::resolution::errors::Span, token::Token}; use crate::{ items::Item, diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs index 6cf69a2309d..e4616c99aaa 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/expr.rs @@ -1,7 +1,7 @@ -use noirc_frontend::{ - macros_api::Span, token::Token, ArrayLiteral, BlockExpression, Expression, ExpressionKind, - Literal, UnaryOp, +use noirc_frontend::ast::{ + ArrayLiteral, BlockExpression, Expression, ExpressionKind, Literal, UnaryOp, }; +use noirc_frontend::{macros_api::Span, token::Token}; use crate::visitor::{ expr::{format_brackets, format_parens, NewlineMode}, diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs index 55eb259bcdd..564ef3fa370 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/imports.rs @@ -1,4 +1,4 @@ -use noirc_frontend::{PathKind, UseTreeKind}; +use noirc_frontend::ast; use crate::{ items::Item, @@ -60,13 +60,13 @@ pub(crate) struct UseTree { } impl UseTree { - pub(crate) fn from_ast(use_tree: noirc_frontend::UseTree) -> Self { + pub(crate) fn from_ast(use_tree: ast::UseTree) -> Self { let mut result = UseTree { path: vec![] }; match use_tree.prefix.kind { - PathKind::Crate => result.path.push(UseSegment::Crate), - PathKind::Dep => result.path.push(UseSegment::Dep), - PathKind::Plain => {} + ast::PathKind::Crate => result.path.push(UseSegment::Crate), + ast::PathKind::Dep => result.path.push(UseSegment::Dep), + ast::PathKind::Plain => {} }; result.path.extend( @@ -78,13 +78,13 @@ impl UseTree { ); match use_tree.kind { - UseTreeKind::Path(name, alias) => { + ast::UseTreeKind::Path(name, alias) => { result.path.push(UseSegment::Ident( name.to_string(), alias.map(|rename| rename.to_string()), )); } - UseTreeKind::List(list) => { + ast::UseTreeKind::List(list) => { let segment = UseSegment::List(list.into_iter().map(UseTree::from_ast).collect()); result.path.push(segment); } diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/infix.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/infix.rs index 5d2b387496a..e2555f21187 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/infix.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/infix.rs @@ -1,6 +1,6 @@ use std::iter::zip; -use noirc_frontend::{Expression, ExpressionKind}; +use noirc_frontend::ast::{Expression, ExpressionKind}; use crate::{ rewrite, diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/parenthesized.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/parenthesized.rs index 3926b52cb73..93e1538b042 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/parenthesized.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/parenthesized.rs @@ -1,4 +1,5 @@ -use noirc_frontend::{hir::resolution::errors::Span, Expression, ExpressionKind}; +use noirc_frontend::ast::{Expression, ExpressionKind}; +use noirc_frontend::hir::resolution::errors::Span; use crate::visitor::{FmtVisitor, Shape}; diff --git a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs index 980d02ee5dc..278457f82d1 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/rewrite/typ.rs @@ -1,4 +1,4 @@ -use noirc_frontend::{UnresolvedType, UnresolvedTypeData}; +use noirc_frontend::ast::{UnresolvedType, UnresolvedTypeData}; use crate::{ utils::span_is_empty, diff --git a/noir/noir-repo/tooling/nargo_fmt/src/utils.rs b/noir/noir-repo/tooling/nargo_fmt/src/utils.rs index 94969d45e81..2c5c3085e66 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/utils.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/utils.rs @@ -3,10 +3,10 @@ use std::borrow::Cow; use crate::items::HasItem; use crate::rewrite; use crate::visitor::{FmtVisitor, Shape}; +use noirc_frontend::ast::{Expression, Ident, Param, Visibility}; use noirc_frontend::hir::resolution::errors::Span; use noirc_frontend::lexer::Lexer; use noirc_frontend::token::Token; -use noirc_frontend::{Expression, Ident, Param, Visibility}; pub(crate) fn changed_comment_content(original: &str, new: &str) -> bool { comments(original).ne(comments(new)) diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/expr.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/expr.rs index f9836adda18..18b962a7f85 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/expr.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/expr.rs @@ -1,7 +1,8 @@ -use noirc_frontend::{ - hir::resolution::errors::Span, lexer::Lexer, token::Token, BlockExpression, - ConstructorExpression, Expression, ExpressionKind, IfExpression, Statement, StatementKind, +use noirc_frontend::ast::Expression; +use noirc_frontend::ast::{ + BlockExpression, ConstructorExpression, ExpressionKind, IfExpression, Statement, StatementKind, }; +use noirc_frontend::{hir::resolution::errors::Span, lexer::Lexer, token::Token}; use super::{ExpressionType, FmtVisitor, Shape}; use crate::{ diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs index 28aad3c551f..82cfefba632 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/item.rs @@ -1,10 +1,3 @@ -use noirc_frontend::{ - hir::resolution::errors::Span, - parser::{Item, ItemKind}, - token::{Keyword, Token}, - Distinctness, NoirFunction, ParsedModule, Visibility, -}; - use crate::{ rewrite::{self, UseTree}, utils::{ @@ -13,6 +6,13 @@ use crate::{ }, visitor::expr::{format_seq, NewlineMode}, }; +use noirc_frontend::ast::{Distinctness, NoirFunction, Visibility}; +use noirc_frontend::{ + hir::resolution::errors::Span, + parser::{Item, ItemKind}, + token::{Keyword, Token}, + ParsedModule, +}; use super::{ expr::Tactic::{HorizontalVertical, LimitedHorizontalVertical}, diff --git a/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs b/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs index ee8cc990e0e..e41827c94a1 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/visitor/stmt.rs @@ -1,6 +1,8 @@ use std::iter::zip; -use noirc_frontend::{ +use noirc_frontend::macros_api::Span; + +use noirc_frontend::ast::{ ConstrainKind, ConstrainStatement, ExpressionKind, ForRange, Statement, StatementKind, }; @@ -14,92 +16,94 @@ impl super::FmtVisitor<'_> { for (Statement { kind, span }, index) in zip(stmts, 1..) { let is_last = index == len; + self.visit_stmt(kind, span, is_last); + self.last_position = span.end(); + } + } - match kind { - StatementKind::Expression(expr) => self.visit_expr( - expr, - if is_last { ExpressionType::SubExpression } else { ExpressionType::Statement }, - ), - StatementKind::Semi(expr) => { - self.visit_expr(expr, ExpressionType::Statement); - self.push_str(";"); - } - StatementKind::Let(let_stmt) => { - let let_str = - self.slice(span.start()..let_stmt.expression.span.start()).trim_end(); - - let expr_str = rewrite::sub_expr(self, self.shape(), let_stmt.expression); - - self.push_rewrite(format!("{let_str} {expr_str};"), span); - } - StatementKind::Constrain(ConstrainStatement(expr, message, kind)) => { - let mut nested_shape = self.shape(); - let shape = nested_shape; - - nested_shape.indent.block_indent(self.config); - - let message = message.map_or(String::new(), |message| { - let message = rewrite::sub_expr(self, nested_shape, message); - format!(", {message}") - }); - - let (callee, args) = match kind { - ConstrainKind::Assert | ConstrainKind::Constrain => { - let assertion = rewrite::sub_expr(self, nested_shape, expr); - let args = format!("{assertion}{message}"); - - ("assert", args) - } - ConstrainKind::AssertEq => { - if let ExpressionKind::Infix(infix) = expr.kind { - let lhs = rewrite::sub_expr(self, nested_shape, infix.lhs); - let rhs = rewrite::sub_expr(self, nested_shape, infix.rhs); + fn visit_stmt(&mut self, kind: StatementKind, span: Span, is_last: bool) { + match kind { + StatementKind::Expression(expr) => self.visit_expr( + expr, + if is_last { ExpressionType::SubExpression } else { ExpressionType::Statement }, + ), + StatementKind::Semi(expr) => { + self.visit_expr(expr, ExpressionType::Statement); + self.push_str(";"); + } + StatementKind::Let(let_stmt) => { + let let_str = self.slice(span.start()..let_stmt.expression.span.start()).trim_end(); - let args = format!("{lhs}, {rhs}{message}"); + let expr_str = rewrite::sub_expr(self, self.shape(), let_stmt.expression); - ("assert_eq", args) - } else { - unreachable!() - } + self.push_rewrite(format!("{let_str} {expr_str};"), span); + } + StatementKind::Constrain(ConstrainStatement(expr, message, kind)) => { + let mut nested_shape = self.shape(); + let shape = nested_shape; + + nested_shape.indent.block_indent(self.config); + + let message = message.map_or(String::new(), |message| { + let message = rewrite::sub_expr(self, nested_shape, message); + format!(", {message}") + }); + + let (callee, args) = match kind { + ConstrainKind::Assert | ConstrainKind::Constrain => { + let assertion = rewrite::sub_expr(self, nested_shape, expr); + let args = format!("{assertion}{message}"); + + ("assert", args) + } + ConstrainKind::AssertEq => { + if let ExpressionKind::Infix(infix) = expr.kind { + let lhs = rewrite::sub_expr(self, nested_shape, infix.lhs); + let rhs = rewrite::sub_expr(self, nested_shape, infix.rhs); + + let args = format!("{lhs}, {rhs}{message}"); + + ("assert_eq", args) + } else { + unreachable!() } - }; - - let args = wrap_exprs( - "(", - ")", - args, - nested_shape, - shape, - NewlineMode::IfContainsNewLineAndWidth, - ); - let constrain = format!("{callee}{args};"); - - self.push_rewrite(constrain, span); - } - StatementKind::For(for_stmt) => { - let identifier = self.slice(for_stmt.identifier.span()); - let range = match for_stmt.range { - ForRange::Range(start, end) => format!( - "{}..{}", - rewrite::sub_expr(self, self.shape(), start), - rewrite::sub_expr(self, self.shape(), end) - ), - ForRange::Array(array) => rewrite::sub_expr(self, self.shape(), array), - }; - let block = rewrite::sub_expr(self, self.shape(), for_stmt.block); - - let result = format!("for {identifier} in {range} {block}"); - self.push_rewrite(result, span); - } - StatementKind::Assign(_) => { - self.push_rewrite(self.slice(span).to_string(), span); - } - StatementKind::Error => unreachable!(), - StatementKind::Break => self.push_rewrite("break;".into(), span), - StatementKind::Continue => self.push_rewrite("continue;".into(), span), + } + }; + + let args = wrap_exprs( + "(", + ")", + args, + nested_shape, + shape, + NewlineMode::IfContainsNewLineAndWidth, + ); + let constrain = format!("{callee}{args};"); + + self.push_rewrite(constrain, span); } - - self.last_position = span.end(); + StatementKind::For(for_stmt) => { + let identifier = self.slice(for_stmt.identifier.span()); + let range = match for_stmt.range { + ForRange::Range(start, end) => format!( + "{}..{}", + rewrite::sub_expr(self, self.shape(), start), + rewrite::sub_expr(self, self.shape(), end) + ), + ForRange::Array(array) => rewrite::sub_expr(self, self.shape(), array), + }; + let block = rewrite::sub_expr(self, self.shape(), for_stmt.block); + + let result = format!("for {identifier} in {range} {block}"); + self.push_rewrite(result, span); + } + StatementKind::Assign(_) => { + self.push_rewrite(self.slice(span).to_string(), span); + } + StatementKind::Error => unreachable!(), + StatementKind::Break => self.push_rewrite("break;".into(), span), + StatementKind::Continue => self.push_rewrite("continue;".into(), span), + StatementKind::Comptime(statement) => self.visit_stmt(*statement, span, is_last), } } } diff --git a/noir/noir-repo/tooling/nargo_toml/Cargo.toml b/noir/noir-repo/tooling/nargo_toml/Cargo.toml index c835ddd936c..574972d99e7 100644 --- a/noir/noir-repo/tooling/nargo_toml/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_toml/Cargo.toml @@ -4,6 +4,7 @@ description = "Utilities for working with Nargo.toml files" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index 438e91ff302..af9e47a8e63 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "portal:../../../../barretenberg/ts", + "@aztec/bb.js": "0.35.1", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/tooling/noirc_abi/Cargo.toml b/noir/noir-repo/tooling/noirc_abi/Cargo.toml index b7fe1ef8084..3258ea04c40 100644 --- a/noir/noir-repo/tooling/noirc_abi/Cargo.toml +++ b/noir/noir-repo/tooling/noirc_abi/Cargo.toml @@ -3,6 +3,7 @@ name = "noirc_abi" version.workspace = true authors.workspace = true edition.workspace = true +rust-version.workspace = true license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index 6ad13500bdd..8ff0154d32c 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -10,7 +10,8 @@ use acvm::{ use errors::AbiError; use input_parser::InputValue; use iter_extended::{try_btree_map, try_vecmap, vecmap}; -use noirc_frontend::{hir::Context, Signedness, Type, TypeBinding, TypeVariableKind, Visibility}; +use noirc_frontend::ast::{Signedness, Visibility}; +use noirc_frontend::{hir::Context, Type, TypeBinding, TypeVariableKind}; use serde::{Deserialize, Serialize}; use std::ops::Range; use std::{collections::BTreeMap, str}; diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index b45678f5d8b..e9915882fac 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,18 +221,19 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": - version: 0.0.0-use.local - resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" +"@aztec/bb.js@npm:0.35.1": + version: 0.35.1 + resolution: "@aztec/bb.js@npm:0.35.1" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: ./dest/node/main.js + bb.js: dest/node/main.js + checksum: 8e3551f059523d9494af4721a9219e2c6e63c8ed1df447a2d0daa9f8526a794758ae708bd1d9c9b1fbfb89c56dc867d9f0b87250dbabfcde23ec02dabbb5a32a languageName: node - linkType: soft + linkType: hard "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4395,7 +4396,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": "portal:../../../../barretenberg/ts" + "@aztec/bb.js": 0.35.1 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3