From 84a400535eccefb532f45532a390b1be21a6b538 Mon Sep 17 00:00:00 2001 From: Aztec Bot <49558828+AztecBot@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:48:25 -0500 Subject: [PATCH] feat: Sync from noir (#10788) Automated pull of development from the [noir](https://github.com/noir-lang/noir) programming language, a dependency of Aztec. BEGIN_COMMIT_OVERRIDE chore: remove malformed functions from brillig reports (https://github.com/noir-lang/noir/pull/6898) chore: clean up gates reports script (https://github.com/noir-lang/noir/pull/6896) chore: move empty programs to `compile_success_empty` (https://github.com/noir-lang/noir/pull/6891) feat: add a warning when using unsafe blocks without safety comments (https://github.com/noir-lang/noir/pull/6860) chore: quick docs fix for #6839 (https://github.com/noir-lang/noir/pull/6840) chore: Avoid duplicate Not instructions during flattening (https://github.com/noir-lang/noir/pull/6886) chore: Use smallvec for instruction results (https://github.com/noir-lang/noir/pull/6877) chore(ci): Display times in compilation and execution reports only with seconds (https://github.com/noir-lang/noir/pull/6880) feat: flatten nested if-else statements with equivalent conditions (https://github.com/noir-lang/noir/pull/6875) chore(ci): Take averages for compilation and execution report of small programs (https://github.com/noir-lang/noir/pull/6874) fix: don't deduplicate binary math of unsigned types (https://github.com/noir-lang/noir/pull/6848) feat: warn on unnecessary unsafe blocks (https://github.com/noir-lang/noir/pull/6867) chore: remove the `as_field` and `from_field` built-ins (https://github.com/noir-lang/noir/pull/6845) chore: fix warnings (https://github.com/noir-lang/noir/pull/6863) fix: detect cycles in globals (https://github.com/noir-lang/noir/pull/6859) chore(ci): Execution time report (https://github.com/noir-lang/noir/pull/6827) chore(ci): Add non determinism check and fixes (https://github.com/noir-lang/noir/pull/6847) chore(docs): updating the solidity contract how-to guide (https://github.com/noir-lang/noir/pull/6804) fix: double alias in path (https://github.com/noir-lang/noir/pull/6855) feat: configurable external check failures (https://github.com/noir-lang/noir/pull/6810) chore: move constant creation out of loop (https://github.com/noir-lang/noir/pull/6836) fix: implement `as_field` and `from_field` in the interpreter (https://github.com/noir-lang/noir/pull/6829) chore: Use Vec for callstacks (https://github.com/noir-lang/noir/pull/6821) feat: replace `eval_global_as_array_length` with type/interpreter evaluation (https://github.com/noir-lang/noir/pull/6469) chore: refactor `DataFlowGraph.insert_instruction_and_results` (https://github.com/noir-lang/noir/pull/6823) chore(docs): updating noirjs tutorial for 1.0.0 (https://github.com/noir-lang/noir/pull/6792) feat: Sync from aztec-packages (https://github.com/noir-lang/noir/pull/6824) chore: Have rust-analyzer use the stable toolchain (https://github.com/noir-lang/noir/pull/6825) END_COMMIT_OVERRIDE --------- Co-authored-by: ludamad Co-authored-by: Tom French Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- .noir-sync-commit | 2 +- .../noir-projects/aztec-nr.failures.jsonl} | 0 .../noir-contracts.failures.jsonl} | 0 .../crates/blob.failures.jsonl | 0 .../crates/parity-lib.failures.jsonl | 0 .../crates/private-kernel-lib.failures.jsonl | 0 .../crates/reset-kernel-lib.failures.jsonl | 0 .../crates/rollup-lib.failures.jsonl | 0 .../crates/types.failures.jsonl | 0 .../critical_libraries_status/README.md | 20 + .../noir-lang/ec/.actual.jsonl | 4 + .../noir-lang/ec/.actual.jsonl.jq | 1 + .../noir-lang/ec/.failures.jsonl | 0 .../noir-lang/ec/.failures.jsonl.jq | 0 .../noir-lang/eddsa/.failures.jsonl | 0 .../noir-lang/mimc/.failures.jsonl | 0 .../noir-lang/noir-bignum/.failures.jsonl | 0 .../noir-lang/noir-edwards/.failures.jsonl | 0 .../noir-lang/noir_base64/.failures.jsonl | 0 .../noir-lang/noir_bigcurve/.failures.jsonl | 0 .../noir_json_parser/.failures.jsonl | 0 .../noir-lang/noir_rsa/.failures.jsonl | 0 .../noir-lang/noir_sort/.failures.jsonl | 0 .../noir_string_search/.failures.jsonl | 0 .../noir-lang/schnorr/.failures.jsonl | 0 .../noir-lang/sparse_array/.failures.jsonl | 0 .../.github/scripts/check_test_results.sh | 38 ++ noir/noir-repo/.github/workflows/reports.yml | 185 ++++++--- .../.github/workflows/test-js-packages.yml | 17 +- noir/noir-repo/.gitignore | 1 + noir/noir-repo/.vscode/settings.json | 3 +- noir/noir-repo/Cargo.lock | 4 + .../acir/src/circuit/black_box_functions.rs | 4 +- .../acvm-repo/acir/src/circuit/mod.rs | 2 +- .../acvm-repo/acir/src/circuit/opcodes.rs | 1 + .../acir/src/native_types/expression/mod.rs | 9 +- .../compiler/optimizers/merge_expressions.rs | 2 +- .../acvm-repo/acvm/src/pwg/blackbox/bigint.rs | 2 +- noir/noir-repo/acvm-repo/acvm/tests/solver.rs | 2 +- .../acvm-repo/blackbox_solver/src/bigint.rs | 2 +- .../compiler/noirc_driver/src/lib.rs | 4 + .../compiler/noirc_errors/src/reporter.rs | 2 +- .../compiler/noirc_evaluator/Cargo.toml | 1 + .../noirc_evaluator/src/acir/acir_variable.rs | 1 + .../src/acir/generated_acir.rs | 4 +- .../compiler/noirc_evaluator/src/acir/mod.rs | 40 +- .../src/brillig/brillig_gen/brillig_block.rs | 4 +- .../src/brillig/brillig_ir/artifact.rs | 4 +- .../compiler/noirc_evaluator/src/errors.rs | 4 +- .../check_for_underconstrained_values.rs | 9 +- .../src/ssa/function_builder/data_bus.rs | 2 +- .../noirc_evaluator/src/ssa/ir/call_stack.rs | 6 +- .../noirc_evaluator/src/ssa/ir/dfg.rs | 89 ++-- .../noirc_evaluator/src/ssa/ir/instruction.rs | 87 +++- .../src/ssa/ir/instruction/call.rs | 25 -- .../noirc_evaluator/src/ssa/ir/list.rs | 187 --------- .../noirc_evaluator/src/ssa/ir/mod.rs | 1 - .../src/ssa/opt/flatten_cfg.rs | 161 ++++++-- .../ssa/opt/flatten_cfg/branch_analysis.rs | 12 +- .../src/ssa/opt/loop_invariant.rs | 92 ++--- .../noirc_evaluator/src/ssa/opt/mem2reg.rs | 8 +- .../src/ssa/opt/remove_enable_side_effects.rs | 2 - .../src/ssa/opt/remove_if_else.rs | 2 - .../src/ssa/opt/resolve_is_unconstrained.rs | 5 +- .../src/ssa/opt/runtime_separation.rs | 2 +- .../noirc_evaluator/src/ssa/opt/unrolling.rs | 6 +- .../noirc_frontend/src/ast/statement.rs | 15 +- .../compiler/noirc_frontend/src/debug/mod.rs | 21 +- .../src/elaborator/expressions.rs | 35 +- .../noirc_frontend/src/elaborator/mod.rs | 43 +- .../src/elaborator/path_resolution.rs | 31 +- .../noirc_frontend/src/elaborator/patterns.rs | 25 +- .../src/elaborator/statements.rs | 5 +- .../noirc_frontend/src/elaborator/types.rs | 169 +++----- .../noirc_frontend/src/hir/comptime/errors.rs | 11 +- .../src/hir/comptime/interpreter.rs | 72 ++-- .../src/hir/comptime/interpreter/builtin.rs | 23 ++ .../noirc_frontend/src/hir/comptime/value.rs | 39 +- .../src/hir/resolution/errors.rs | 43 +- .../noirc_frontend/src/hir/scope/mod.rs | 8 +- .../src/hir/type_check/errors.rs | 30 +- .../noirc_frontend/src/hir_def/stmt.rs | 16 + .../noirc_frontend/src/hir_def/types.rs | 10 +- .../noirc_frontend/src/lexer/lexer.rs | 23 +- .../noirc_frontend/src/lexer/token.rs | 3 + .../src/monomorphization/mod.rs | 7 +- .../noirc_frontend/src/node_interner.rs | 19 +- .../noirc_frontend/src/parser/errors.rs | 13 +- .../noirc_frontend/src/parser/parser.rs | 5 +- .../src/parser/parser/expression.rs | 31 +- .../src/parser/parser/global.rs | 5 +- .../src/parser/parser/pattern.rs | 4 +- .../src/parser/parser/statement.rs | 13 +- .../src/parser/parser/type_expression.rs | 4 +- .../src/parser/parser/where_clause.rs | 4 +- .../compiler/noirc_frontend/src/tests.rs | 156 ++++--- .../noirc_frontend/src/tests/aliases.rs | 28 ++ .../src/tests/arithmetic_generics.rs | 46 +++ .../noirc_frontend/src/tests/references.rs | 1 + noir/noir-repo/cspell.json | 3 + ...rifier.md => how-to-solidity-verifier.mdx} | 141 ++++--- .../docs/noir/concepts/data_types/fields.md | 2 +- .../docs/docs/noir/concepts/unconstrained.md | 2 + .../docs/docs/tutorials/noirjs_app.md | 380 ++++++++---------- .../img/how-tos/solidity_verifier_6.png | Bin 0 -> 40585 bytes .../img/how-tos/solidity_verifier_7.png | Bin 0 -> 73765 bytes .../img/how-tos/solidity_verifier_8.png | Bin 0 -> 41640 bytes .../img/how-tos/solidity_verifier_9.png | Bin 0 -> 58960 bytes .../img/tutorials/noirjs_webapp/webapp1.png | Bin 0 -> 15009 bytes .../noir_stdlib/src/array/check_shuffle.nr | 3 + noir/noir-repo/noir_stdlib/src/array/mod.nr | 7 +- .../noir_stdlib/src/collections/map.nr | 2 +- .../noir_stdlib/src/collections/umap.nr | 11 +- noir/noir-repo/noir_stdlib/src/convert.nr | 48 +++ noir/noir-repo/noir_stdlib/src/field/bn254.nr | 15 +- noir/noir-repo/noir_stdlib/src/field/mod.nr | 1 + noir/noir-repo/noir_stdlib/src/hash/mod.nr | 5 +- noir/noir-repo/noir_stdlib/src/hash/sha256.nr | 32 +- noir/noir-repo/noir_stdlib/src/lib.nr | 39 +- noir/noir-repo/noir_stdlib/src/meta/expr.nr | 7 +- noir/noir-repo/noir_stdlib/src/meta/mod.nr | 2 +- noir/noir-repo/noir_stdlib/src/uint128.nr | 36 +- .../test_programs/compilation_report.sh | 26 +- .../brillig_mut_ref_from_acir/src/main.nr | 1 + .../regression_5008/src/main.nr | 1 + .../unconstrained_ref/src/main.nr | 1 + .../acir_inside_brillig_recursion/Nargo.toml | 0 .../acir_inside_brillig_recursion/src/main.nr | 1 + .../brillig_cast/src/main.nr | 1 + .../src/main.nr | 1 + .../src/main.nr | 1 + .../brillig_modulo/src/main.nr | 1 + .../brillig_slice_input/src/main.nr | 2 + .../cast_and_shift_global/Nargo.toml | 0 .../cast_and_shift_global/src/main.nr | 0 .../check_large_field_bits/Nargo.toml | 0 .../check_large_field_bits/src/main.nr | 0 .../comptime_as_field/Nargo.toml | 6 + .../comptime_as_field/src/main.nr | 6 + .../comptime_from_field/Nargo.toml | 6 + .../comptime_from_field/src/main.nr | 6 + .../comptime_slice_equality/Nargo.toml | 0 .../comptime_slice_equality/src/main.nr | 0 .../double_generic_alias_in_path/Nargo.toml | 7 + .../double_generic_alias_in_path/src/main.nr | 14 + .../empty/Nargo.toml | 0 .../empty/src/main.nr | 0 .../is_unconstrained/Nargo.toml | 0 .../is_unconstrained/src/main.nr | 1 + .../macros_in_comptime/src/main.nr | 5 +- .../regression_5462/Nargo.toml | 0 .../regression_5462/src/main.nr | 0 .../trait_inheritance/Nargo.toml | 0 .../trait_inheritance/src/main.nr | 0 .../unit_value/Nargo.toml | 0 .../unit_value/src/main.nr | 0 .../check_uncostrained_regression/src/main.nr | 1 + .../src/main.nr | 1 + .../brillig_assert_fail/src/main.nr | 1 + .../brillig_assert_msg_runtime/src/main.nr | 1 + .../src/main.nr | 1 + .../regression_5202/src/main.nr | 2 + .../test_programs/execution_report.sh | 76 ++++ .../acir_inside_brillig_recursion/Prover.toml | 1 - .../aes128_encrypt/src/main.nr | 10 +- .../src/main.nr | 1 + .../execution_success/bigint/src/main.nr | 1 + .../brillig_acir_as_brillig/src/main.nr | 1 + .../brillig_arrays/src/main.nr | 1 + .../brillig_blake2s/src/main.nr | 1 + .../brillig_calls/src/main.nr | 1 + .../brillig_calls_array/src/main.nr | 1 + .../brillig_calls_conditionals/src/main.nr | 1 + .../brillig_conditional/src/main.nr | 1 + .../brillig_fns_as_values/src/main.nr | 1 + .../brillig_identity_function/src/main.nr | 1 + .../brillig_nested_arrays/src/main.nr | 1 + .../execution_success/brillig_not/src/main.nr | 1 + .../brillig_oracle/src/main.nr | 1 + .../brillig_recursion/src/main.nr | 1 + .../brillig_uninitialized_arrays/src/main.nr | 1 + .../global_consts/src/main.nr | 5 +- .../hint_black_box/src/main.nr | 5 +- .../is_unconstrained/Prover.toml | 1 - .../nested_arrays_from_brillig/src/main.nr | 5 +- .../reference_only_used_as_alias/src/main.nr | 1 + .../regression_5435/src/main.nr | 5 +- .../regression_6451/src/main.nr | 5 +- .../regression_6674_3/src/main.nr | 5 +- .../regression_6834/Nargo.toml | 7 + .../regression_6834/Prover.toml | 2 + .../regression_6834/src/main.nr | 9 + .../src/main.nr | 5 +- .../execution_success/u16_support/src/main.nr | 1 + .../execution_success/uhashmap/src/main.nr | 11 +- .../test_programs/gates_report_brillig.sh | 2 +- .../gates_report_brillig_execution.sh | 4 +- .../comptime_expr/src/main.nr | 11 +- .../noir_test_success/mock_oracle/src/main.nr | 9 + .../out_of_bounds_alignment/src/main.nr | 1 + .../noir-repo/tooling/acvm_cli/src/cli/mod.rs | 8 - noir/noir-repo/tooling/debugger/build.rs | 7 +- .../noir-repo/tooling/debugger/src/context.rs | 10 +- noir/noir-repo/tooling/lsp/src/modules.rs | 4 +- .../tooling/lsp/src/requests/hover.rs | 32 +- .../tooling/lsp/src/use_segment_positions.rs | 11 +- noir/noir-repo/tooling/nargo_cli/build.rs | 9 +- .../tooling/nargo_cli/src/cli/compile_cmd.rs | 14 + .../tooling/nargo_cli/src/cli/execute_cmd.rs | 2 +- .../nargo_cli/src/cli/test_cmd/formatters.rs | 1 + .../tooling/nargo_cli/tests/stdlib-props.rs | 8 +- noir/noir-repo/tooling/nargo_fmt/build.rs | 5 +- .../tooling/nargo_fmt/src/formatter.rs | 12 +- .../src/formatter/comments_and_whitespace.rs | 8 +- .../nargo_fmt/src/formatter/expression.rs | 14 +- .../nargo_fmt/src/formatter/statement.rs | 5 +- .../nargo_fmt/src/formatter/use_tree_merge.rs | 8 +- .../nargo_fmt/tests/expected/unsafe.nr | 4 +- .../tooling/nargo_fmt/tests/input/unsafe.nr | 5 +- noir/noir-repo/tooling/nargo_toml/src/lib.rs | 2 +- 220 files changed, 1982 insertions(+), 1231 deletions(-) rename noir/noir-repo/{test_programs/execution_success/cast_and_shift_global/Prover.toml => .github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/aztec-nr.failures.jsonl} (100%) rename noir/noir-repo/{test_programs/execution_success/trait_inheritance/Prover.toml => .github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-contracts.failures.jsonl} (100%) create mode 100644 noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/blob.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/parity-lib.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/private-kernel-lib.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/rollup-lib.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/types.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/README.md create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl.jq create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.failures.jsonl.jq create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/eddsa/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/mimc/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir-bignum/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir-edwards/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_base64/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_bigcurve/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_json_parser/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_rsa/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_sort/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_string_search/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/schnorr/.failures.jsonl create mode 100644 noir/noir-repo/.github/critical_libraries_status/noir-lang/sparse_array/.failures.jsonl create mode 100755 noir/noir-repo/.github/scripts/check_test_results.sh delete mode 100644 noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/list.rs rename noir/noir-repo/docs/docs/how_to/{how-to-solidity-verifier.md => how-to-solidity-verifier.mdx} (64%) create mode 100644 noir/noir-repo/docs/static/img/how-tos/solidity_verifier_6.png create mode 100644 noir/noir-repo/docs/static/img/how-tos/solidity_verifier_7.png create mode 100644 noir/noir-repo/docs/static/img/how-tos/solidity_verifier_8.png create mode 100644 noir/noir-repo/docs/static/img/how-tos/solidity_verifier_9.png create mode 100644 noir/noir-repo/docs/static/img/tutorials/noirjs_webapp/webapp1.png rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/acir_inside_brillig_recursion/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/acir_inside_brillig_recursion/src/main.nr (88%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/cast_and_shift_global/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/cast_and_shift_global/src/main.nr (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/check_large_field_bits/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/check_large_field_bits/src/main.nr (100%) create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/src/main.nr create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/src/main.nr rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/comptime_slice_equality/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/comptime_slice_equality/src/main.nr (100%) create mode 100644 noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/Nargo.toml create mode 100644 noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/src/main.nr rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/empty/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/empty/src/main.nr (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/is_unconstrained/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/is_unconstrained/src/main.nr (89%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/regression_5462/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/regression_5462/src/main.nr (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/trait_inheritance/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/trait_inheritance/src/main.nr (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/unit_value/Nargo.toml (100%) rename noir/noir-repo/test_programs/{execution_success => compile_success_empty}/unit_value/src/main.nr (100%) create mode 100755 noir/noir-repo/test_programs/execution_report.sh delete mode 100644 noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/Prover.toml delete mode 100644 noir/noir-repo/test_programs/execution_success/is_unconstrained/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_success/regression_6834/Nargo.toml create mode 100644 noir/noir-repo/test_programs/execution_success/regression_6834/Prover.toml create mode 100644 noir/noir-repo/test_programs/execution_success/regression_6834/src/main.nr diff --git a/.noir-sync-commit b/.noir-sync-commit index a6780a1e9ef..ab7c61a3bd7 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -f337992de96ef656681ebfc96a30c2c9c9b82a70 \ No newline at end of file +913be5b013323449963d31ab00f521f572cfcd67 diff --git a/noir/noir-repo/test_programs/execution_success/cast_and_shift_global/Prover.toml b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/aztec-nr.failures.jsonl similarity index 100% rename from noir/noir-repo/test_programs/execution_success/cast_and_shift_global/Prover.toml rename to noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/aztec-nr.failures.jsonl diff --git a/noir/noir-repo/test_programs/execution_success/trait_inheritance/Prover.toml b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-contracts.failures.jsonl similarity index 100% rename from noir/noir-repo/test_programs/execution_success/trait_inheritance/Prover.toml rename to noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-contracts.failures.jsonl diff --git a/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/blob.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/blob.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/parity-lib.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/parity-lib.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/private-kernel-lib.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/private-kernel-lib.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/reset-kernel-lib.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/rollup-lib.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/rollup-lib.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/types.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/AztecProtocol/aztec-packages/noir-projects/noir-protocol-circuits/crates/types.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/README.md b/noir/noir-repo/.github/critical_libraries_status/README.md new file mode 100644 index 00000000000..47e9d7ad6ed --- /dev/null +++ b/noir/noir-repo/.github/critical_libraries_status/README.md @@ -0,0 +1,20 @@ +# Critical Libraries Status + +This directory contains one `.failures.jsonl` file per external directory that is checked by CI. +CI will run the external repository tests and compare the test failures against those recorded +in these files. If there's a difference, CI will fail. + +This allows us to mark some tests as expected to fail if we introduce breaking changes. +When tests are fixed on the external repository, CI will let us know that we need to remove +the `.failures.jsonl` failures on our side. + +The format of the `.failures.jsonl` files is one JSON per line with a failure: + +```json +{"suite":"one","name":"foo"} +``` + +If it's expected that an external repository doesn't compile (because a PR introduces breaking changes +to, say, the type system) you can remove the `.failures.jsonl` file for that repository and CI +will pass again. Once the repository compiles again, CI will let us know and require us to put +back the `.failures.jsonl` file. \ No newline at end of file diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl new file mode 100644 index 00000000000..cb56f792778 --- /dev/null +++ b/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl @@ -0,0 +1,4 @@ +{"event":"started","name":"one","test_count":1,"type":"suite"} +{"event":"started","name":"foo","suite":"one","type":"test"} +{"event":"failed","exec_time":0.05356625,"name":"foo","suite":"one","type":"test"} +{"event":"ok","failed":0,"ignored":0,"passed":1,"type":"suite"} \ No newline at end of file diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl.jq b/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl.jq new file mode 100644 index 00000000000..1123e7e68e4 --- /dev/null +++ b/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.actual.jsonl.jq @@ -0,0 +1 @@ +{"suite":"one","name":"foo"} diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.failures.jsonl.jq b/noir/noir-repo/.github/critical_libraries_status/noir-lang/ec/.failures.jsonl.jq new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/eddsa/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/eddsa/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/mimc/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/mimc/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir-bignum/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir-bignum/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir-edwards/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir-edwards/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_base64/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_base64/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_bigcurve/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_bigcurve/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_json_parser/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_json_parser/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_rsa/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_rsa/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_sort/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_sort/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_string_search/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/noir_string_search/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/schnorr/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/schnorr/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/critical_libraries_status/noir-lang/sparse_array/.failures.jsonl b/noir/noir-repo/.github/critical_libraries_status/noir-lang/sparse_array/.failures.jsonl new file mode 100644 index 00000000000..e69de29bb2d diff --git a/noir/noir-repo/.github/scripts/check_test_results.sh b/noir/noir-repo/.github/scripts/check_test_results.sh new file mode 100755 index 00000000000..c844c025876 --- /dev/null +++ b/noir/noir-repo/.github/scripts/check_test_results.sh @@ -0,0 +1,38 @@ +#!/bin/bash +set -eu + +# Usage: ./check_test_results.sh +# Compares the output of two test results of the same repository. +# If any of the files doesn't exist or is empty, the script will consider that the test suite +# couldn't be compiled. + +function process_json_lines() { + cat $1 | jq -c 'select(.type == "test" and .event == "failed") | {suite: .suite, name: .name}' | jq -s -c 'sort_by(.suite, .name) | .[]' > $1.jq +} + +if [ -f $1 ] && [ -f $2 ]; then + # Both files exist, let's compare them + $(process_json_lines $2) + if ! diff $1 $2.jq; then + echo "Error: test failures don't match expected failures" + echo "Lines prefixed with '>' are new test failures (you could add them to '$1')" + echo "Lines prefixed with '<' are tests that were expected to fail but passed (you could remove them from '$1')" + fi +elif [ -f $1 ]; then + # Only the expected file exists, which means the actual test couldn't be compiled. + echo "Error: external library tests couldn't be compiled." + echo "You could rename '$1' to '$1.does_not_compile' if it's expected that the external library can't be compiled." + exit -1 +elif [ -f $2 ]; then + # Only the actual file exists, which means we are expecting the external library + # not to compile but it did. + echo "Error: expected external library not to compile, but it did." + echo "You could create '$1' with these contents:" + $(process_json_lines $2) + cat $2.jq + exit -1 +else + # Both files don't exists, which means we are expecting the external library not + # to compile, and it didn't, so all is good. + exit 0 +fi \ No newline at end of file diff --git a/noir/noir-repo/.github/workflows/reports.yml b/noir/noir-repo/.github/workflows/reports.yml index 1e355dc9e6b..e8a22984318 100644 --- a/noir/noir-repo/.github/workflows/reports.yml +++ b/noir/noir-repo/.github/workflows/reports.yml @@ -76,7 +76,7 @@ jobs: - name: Compare gates reports id: gates_diff - uses: noir-lang/noir-gates-diff@1931aaaa848a1a009363d6115293f7b7fc72bb87 + uses: noir-lang/noir-gates-diff@84ada11295b9a1e1da7325af4e45e2db9f775175 with: report: gates_report.json summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%) @@ -121,7 +121,7 @@ jobs: - name: Compare Brillig bytecode size reports id: brillig_bytecode_diff - uses: noir-lang/noir-gates-diff@d88f7523b013b9edd3f31c5cfddaef87a3fe1b48 + uses: noir-lang/noir-gates-diff@84ada11295b9a1e1da7325af4e45e2db9f775175 with: report: gates_report_brillig.json header: | @@ -170,7 +170,7 @@ jobs: - name: Compare Brillig execution reports id: brillig_execution_diff - uses: noir-lang/noir-gates-diff@d88f7523b013b9edd3f31c5cfddaef87a3fe1b48 + uses: noir-lang/noir-gates-diff@84ada11295b9a1e1da7325af4e45e2db9f775175 with: report: gates_report_brillig_execution.json header: | @@ -225,8 +225,8 @@ jobs: retention-days: 3 overwrite: true - generate_compilation_report: - name: Compilation time + generate_compilation_and_execution_report: + name: Compilation and execution time needs: [build-nargo] runs-on: ubuntu-22.04 permissions: @@ -252,10 +252,15 @@ jobs: - name: Generate Compilation report working-directory: ./test_programs run: | - ./compilation_report.sh - cat compilation_report.json + ./compilation_report.sh 0 1 mv compilation_report.json ../compilation_report.json + - name: Generate Execution report + working-directory: ./test_programs + run: | + ./execution_report.sh 0 1 + mv execution_report.json ../execution_report.json + - name: Upload compilation report uses: actions/upload-artifact@v4 with: @@ -263,8 +268,16 @@ jobs: path: compilation_report.json retention-days: 3 overwrite: true + + - name: Upload execution report + uses: actions/upload-artifact@v4 + with: + name: in_progress_execution_report + path: execution_report.json + retention-days: 3 + overwrite: true - external_repo_compilation_report: + external_repo_compilation_and_execution_report: needs: [build-nargo] runs-on: ubuntu-latest timeout-minutes: 15 @@ -272,15 +285,15 @@ jobs: fail-fast: false matrix: include: - - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-contracts } - - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/parity-root } - - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/private-kernel-inner } - - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/private-kernel-tail } - - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/private-kernel-reset } + - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-contracts, is_library: true } + - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/parity-root, take_average: true } + - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/private-kernel-inner, take_average: true } + - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/private-kernel-tail, take_average: true } + - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/private-kernel-reset, take_average: true } - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/rollup-base-private } - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/rollup-base-public } - name: External repo compilation report - ${{ matrix.project.repo }}/${{ matrix.project.path }} + name: External repo compilation and execution reports - ${{ matrix.project.repo }}/${{ matrix.project.path }} steps: - name: Download nargo binary uses: actions/download-artifact@v4 @@ -302,6 +315,13 @@ jobs: sparse-checkout: | test_programs/compilation_report.sh sparse-checkout-cone-mode: false + + - uses: actions/checkout@v4 + with: + path: scripts + sparse-checkout: | + test_programs/execution_report.sh + sparse-checkout-cone-mode: false - name: Checkout uses: actions/checkout@v4 @@ -310,15 +330,67 @@ jobs: path: test-repo ref: ${{ matrix.project.ref }} - - name: Generate compilation report + - name: Generate compilation report without averages working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ !matrix.project.take_average }} run: | mv /home/runner/work/noir/noir/scripts/test_programs/compilation_report.sh ./compilation_report.sh chmod +x ./compilation_report.sh ./compilation_report.sh 1 + + - name: Generate execution report + working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ !matrix.project.is_library }} + run: | + mv /home/runner/work/noir/noir/scripts/test_programs/execution_report.sh ./execution_report.sh + ./execution_report.sh 1 + + - name: Generate compilation report with averages + working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ matrix.project.take_average }} + run: | + mv /home/runner/work/noir/noir/scripts/test_programs/compilation_report.sh ./compilation_report.sh + chmod +x ./compilation_report.sh + ./compilation_report.sh 1 1 + + - name: Generate execution report without averages + working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ !matrix.project.is_library && !matrix.project.take_average }} + run: | + mv /home/runner/work/noir/noir/scripts/test_programs/execution_report.sh ./execution_report.sh + ./execution_report.sh 1 + + - name: Generate execution report with averages + working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ !matrix.project.is_library && matrix.project.take_average }} + run: | + mv /home/runner/work/noir/noir/scripts/test_programs/execution_report.sh ./execution_report.sh + ./execution_report.sh 1 1 + + - name: Generate compilation report with averages + working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ matrix.project.take_average }} + run: | + mv /home/runner/work/noir/noir/scripts/test_programs/compilation_report.sh ./compilation_report.sh + chmod +x ./compilation_report.sh + ./compilation_report.sh 1 1 + + - name: Generate execution report without averages + working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ !matrix.project.is_library && !matrix.project.take_average }} + run: | + mv /home/runner/work/noir/noir/scripts/test_programs/execution_report.sh ./execution_report.sh + ./execution_report.sh 1 + + - name: Generate execution report with averages + working-directory: ./test-repo/${{ matrix.project.path }} + if: ${{ !matrix.project.is_library && matrix.project.take_average }} + run: | + mv /home/runner/work/noir/noir/scripts/test_programs/execution_report.sh ./execution_report.sh + ./execution_report.sh 1 1 - name: Move compilation report - id: report + id: compilation_report shell: bash run: | PACKAGE_NAME=${{ matrix.project.path }} @@ -326,18 +398,36 @@ jobs: mv ./test-repo/${{ matrix.project.path }}/compilation_report.json ./compilation_report_$PACKAGE_NAME.json echo "compilation_report_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT + - name: Move execution report + id: execution_report + shell: bash + if: ${{ !matrix.project.is_library }} + run: | + PACKAGE_NAME=${{ matrix.project.path }} + PACKAGE_NAME=$(basename $PACKAGE_NAME) + mv ./test-repo/${{ matrix.project.path }}/execution_report.json ./execution_report_$PACKAGE_NAME.json + echo "execution_report_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT + - name: Upload compilation report uses: actions/upload-artifact@v4 with: - name: compilation_report_${{ steps.report.outputs.compilation_report_name }} - path: compilation_report_${{ steps.report.outputs.compilation_report_name }}.json + name: compilation_report_${{ steps.compilation_report.outputs.compilation_report_name }} + path: compilation_report_${{ steps.compilation_report.outputs.compilation_report_name }}.json + retention-days: 3 + overwrite: true + + - name: Upload execution report + uses: actions/upload-artifact@v4 + with: + name: execution_report_${{ steps.execution_report.outputs.execution_report_name }} + path: execution_report_${{ steps.execution_report.outputs.execution_report_name }}.json retention-days: 3 overwrite: true upload_compilation_report: name: Upload compilation report - needs: [generate_compilation_report, external_repo_compilation_report] - # We want this job to run even if one variation of the matrix in `external_repo_compilation_report` fails + needs: [generate_compilation_and_execution_report, external_repo_compilation_and_execution_report] + # We want this job to run even if one variation of the matrix in `external_repo_compilation_and_execution_report` fails if: always() runs-on: ubuntu-latest permissions: @@ -364,7 +454,7 @@ jobs: - name: Parse compilation report id: compilation_report - uses: noir-lang/noir-bench-report@0d7464a8c39170523932d7846b6e6b458a294aea + uses: noir-lang/noir-bench-report@e408e131e96c3615b4f820d7d642360fb4d6e2f4 with: report: compilation_report.json header: | @@ -477,7 +567,7 @@ jobs: - name: Parse memory report id: memory_report - uses: noir-lang/noir-bench-report@0d7464a8c39170523932d7846b6e6b458a294aea + uses: noir-lang/noir-bench-report@e408e131e96c3615b4f820d7d642360fb4d6e2f4 with: report: memory_report.json header: | @@ -491,48 +581,47 @@ jobs: header: memory message: ${{ steps.memory_report.outputs.markdown }} - generate_compilation_report: - name: Compilation time - needs: [build-nargo] - runs-on: ubuntu-22.04 + upload_execution_report: + name: Upload execution report + needs: [generate_compilation_and_execution_report, external_repo_compilation_and_execution_report] + # We want this job to run even if one variation of the matrix in `external_repo_compilation_and_execution_report` fails + if: always() + runs-on: ubuntu-latest permissions: pull-requests: write steps: - uses: actions/checkout@v4 - - name: Download nargo binary + - name: Download initial execution report uses: actions/download-artifact@v4 with: - name: nargo - path: ./nargo + name: in_progress_execution_report - - name: Set nargo on PATH - run: | - nargo_binary="${{ github.workspace }}/nargo/nargo" - chmod +x $nargo_binary - echo "$(dirname $nargo_binary)" >> $GITHUB_PATH - export PATH="$PATH:$(dirname $nargo_binary)" - nargo -V + - name: Download matrix execution reports + uses: actions/download-artifact@v4 + with: + pattern: execution_report_* + path: ./reports - - name: Generate Compilation report - working-directory: ./test_programs + - name: Merge execution reports using jq run: | - ./compilation_report.sh - mv compilation_report.json ../compilation_report.json + mv ./.github/scripts/merge-bench-reports.sh merge-bench-reports.sh + ./merge-bench-reports.sh execution_report - - name: Parse compilation report - id: compilation_report - uses: noir-lang/noir-bench-report@0d7464a8c39170523932d7846b6e6b458a294aea + - name: Parse execution report + id: execution_report + uses: noir-lang/noir-bench-report@e408e131e96c3615b4f820d7d642360fb4d6e2f4 with: - report: compilation_report.json + report: execution_report.json header: | - # Compilation Report - memory_report: false + # Execution Report + execution_report: true - name: Add memory report to sticky comment if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' uses: marocchino/sticky-pull-request-comment@v2 with: - header: compilation - message: ${{ steps.compilation_report.outputs.markdown }} + header: execution_time + message: ${{ steps.execution_report.outputs.markdown }} + diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index d227b4eb00b..e593389a971 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -543,8 +543,6 @@ jobs: external-repo-checks: needs: [build-nargo, critical-library-list] runs-on: ubuntu-22.04 - # Only run when 'run-external-checks' label is present - if: contains(github.event.pull_request.labels.*.name, 'run-external-checks') timeout-minutes: 30 strategy: fail-fast: false @@ -557,11 +555,17 @@ jobs: - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/parity-lib } - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/private-kernel-lib } - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/reset-kernel-lib } - - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/rollup-lib } - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/types } + # Use 1 test threads for rollup-lib because each test requires a lot of memory, and multiple ones in parallel exceed the maximum memory limit. + - project: { repo: AztecProtocol/aztec-packages, path: noir-projects/noir-protocol-circuits/crates/rollup-lib, nargo_args: "--test-threads 1" } name: Check external repo - ${{ matrix.project.repo }}/${{ matrix.project.path }} steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: noir-repo + - name: Checkout uses: actions/checkout@v4 with: @@ -592,9 +596,14 @@ jobs: - name: Run nargo test working-directory: ./test-repo/${{ matrix.project.path }} - run: nargo test -q --silence-warnings + run: | + out=$(nargo test --silence-warnings --skip-brillig-constraints-check --format json ${{ matrix.project.nargo_args }}) && echo "$out" > ${{ github.workspace }}/noir-repo/.github/critical_libraries_status/${{ matrix.project.repo }}/${{ matrix.project.path }}.actual.jsonl env: NARGO_IGNORE_TEST_FAILURES_FROM_FOREIGN_CALLS: true + + - name: Compare test results + working-directory: ./noir-repo + run: .github/scripts/check_test_results.sh .github/critical_libraries_status/${{ matrix.project.repo }}/${{ matrix.project.path }}.failures.jsonl .github/critical_libraries_status/${{ matrix.project.repo }}/${{ matrix.project.path }}.actual.jsonl # This is a job which depends on all test jobs and reports the overall status. # This allows us to add/remove test jobs without having to update the required workflows. diff --git a/noir/noir-repo/.gitignore b/noir/noir-repo/.gitignore index 8442d688fbf..9bfb8a89331 100644 --- a/noir/noir-repo/.gitignore +++ b/noir/noir-repo/.gitignore @@ -36,6 +36,7 @@ gates_report.json gates_report_brillig.json gates_report_brillig_execution.json compilation_report.json +execution_report.json # Github Actions scratch space # This gives a location to download artifacts into the repository in CI without making git dirty. diff --git a/noir/noir-repo/.vscode/settings.json b/noir/noir-repo/.vscode/settings.json index fb8ea527881..cd1c5f886df 100644 --- a/noir/noir-repo/.vscode/settings.json +++ b/noir/noir-repo/.vscode/settings.json @@ -7,5 +7,6 @@ }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" } } diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index f14fb841372..bb45ea80517 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -3184,6 +3184,7 @@ dependencies = [ "serde_json", "serde_with", "similar-asserts", + "smallvec", "test-case", "thiserror", "tracing", @@ -4487,6 +4488,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "smawk" diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs index 700589d2040..d0ec7d02201 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/black_box_functions.rs @@ -42,13 +42,13 @@ pub enum BlackBoxFunc { /// https://tools.ietf.org/html/rfc7693 /// - inputs are a byte array, i.e a vector of (witness, 8) /// - output is a byte array of length 32, i.e. an array of 32 - /// (witness, 8), constrained to be the blake2s of the inputs. + /// (witness, 8), constrained to be the blake2s of the inputs. Blake2s, /// Computes the Blake3 hash of the inputs /// - inputs are a byte array, i.e a vector of (witness, 8) /// - output is a byte array of length 32, i.e an array of 32 - /// (witness, 8), constrained to be the blake3 of the inputs. + /// (witness, 8), constrained to be the blake3 of the inputs. Blake3, /// Verifies a ECDSA signature over the secp256k1 curve. diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs index 4ff581bf17a..e651e6998a4 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -281,7 +281,7 @@ impl Deserialize<'a>> Program { where D: Deserializer<'de>, { - let bytecode_b64: String = serde::Deserialize::deserialize(deserializer)?; + let bytecode_b64: String = Deserialize::deserialize(deserializer)?; let program_bytes = base64::engine::general_purpose::STANDARD .decode(bytecode_b64) .map_err(D::Error::custom)?; diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs index f47c40b0dd7..dec58b5f90b 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes.rs @@ -47,6 +47,7 @@ pub enum Opcode { /// - **express a constraint** on witnesses; for instance to express that a /// witness `w` is a boolean, you can add the opcode: `w*w-w=0` /// - or, to **compute the value** of an arithmetic operation of some inputs. + /// /// For instance, to multiply two witnesses `x` and `y`, you would use the /// opcode `z-x*y=0`, which would constrain `z` to be `x*y`. /// diff --git a/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs b/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs index 2bbbc39d0ca..cdb8974526f 100644 --- a/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/native_types/expression/mod.rs @@ -85,6 +85,7 @@ impl Expression { /// /// - `mul_term` in an expression contains degree-2 terms /// - `linear_combinations` contains degree-1 terms + /// /// Hence, it is sufficient to check that there are no `mul_terms` /// /// Examples: @@ -98,11 +99,11 @@ impl Expression { /// Returns `true` if the expression can be seen as a degree-1 univariate polynomial /// /// - `mul_terms` in an expression can be univariate, however unless the coefficient - /// is zero, it is always degree-2. + /// is zero, it is always degree-2. /// - `linear_combinations` contains the sum of degree-1 terms, these terms do not - /// need to contain the same variable and so it can be multivariate. However, we - /// have thus far only checked if `linear_combinations` contains one term, so this - /// method will return false, if the `Expression` has not been simplified. + /// need to contain the same variable and so it can be multivariate. However, we + /// have thus far only checked if `linear_combinations` contains one term, so this + /// method will return false, if the `Expression` has not been simplified. /// /// Hence, we check in the simplest case if an expression is a degree-1 univariate, /// by checking if it contains no `mul_terms` and it contains one `linear_combination` term. diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs index 43e32101cc5..e95c6207c3c 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/optimizers/merge_expressions.rs @@ -404,7 +404,7 @@ mod tests { ], q_c: FieldElement::zero(), }), - Opcode::BlackBoxFuncCall(acir::circuit::opcodes::BlackBoxFuncCall::RANGE { + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::RANGE { input: FunctionInput::witness(Witness(3), 32), }), ]; diff --git a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/bigint.rs b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/bigint.rs index ccad3510682..7ce34dbc6fe 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/bigint.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/pwg/blackbox/bigint.rs @@ -10,7 +10,7 @@ use crate::pwg::OpcodeResolutionError; /// Resolve BigInt opcodes by storing BigInt values (and their moduli) by their ID in the BigIntSolver /// - When it encounters a bigint operation opcode, it performs the operation on the stored values -/// and store the result using the provided ID. +/// and store the result using the provided ID. /// - When it gets a to_bytes opcode, it simply looks up the value and resolves the output witness accordingly. #[derive(Default)] pub(crate) struct AcvmBigIntSolver { diff --git a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs index 8b164b7c0f2..b43f7512b6e 100644 --- a/noir/noir-repo/acvm-repo/acvm/tests/solver.rs +++ b/noir/noir-repo/acvm-repo/acvm/tests/solver.rs @@ -1457,7 +1457,7 @@ fn poseidon2_permutation_zeroes() { #[test] fn sha256_compression_zeros() { let results = solve_array_input_blackbox_call( - [(FieldElement::zero(), false); 24].try_into().unwrap(), + [(FieldElement::zero(), false); 24].into(), 8, None, sha256_compression_op, diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs b/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs index 540862843ab..dab611a81e8 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs +++ b/noir/noir-repo/acvm-repo/blackbox_solver/src/bigint.rs @@ -8,7 +8,7 @@ use crate::BlackBoxResolutionError; /// Resolve BigInt opcodes by storing BigInt values (and their moduli) by their ID in a HashMap: /// - When it encounters a bigint operation opcode, it performs the operation on the stored values -/// and store the result using the provided ID. +/// and store the result using the provided ID. /// - When it gets a to_bytes opcode, it simply looks up the value and resolves the output witness accordingly. #[derive(Default, Debug, Clone, PartialEq, Eq)] diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 9318e4d2b5c..1b311504b5c 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -150,6 +150,10 @@ pub struct CompileOptions { /// A lower value keeps the original program if it was smaller, even if it has more jumps. #[arg(long, hide = true, allow_hyphen_values = true)] pub max_bytecode_increase_percent: Option, + + /// Used internally to test for non-determinism in the compiler. + #[clap(long, hide = true)] + pub check_non_determinism: bool, } pub fn parse_expression_width(input: &str) -> Result { diff --git a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs index e57775d9a7f..20a765fdaa5 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/reporter.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/reporter.rs @@ -230,7 +230,7 @@ pub fn report<'files>( let color_choice = if std::io::stderr().is_terminal() { ColorChoice::Auto } else { ColorChoice::Never }; let writer = StandardStream::stderr(color_choice); - let config = codespan_reporting::term::Config::default(); + let config = term::Config::default(); let stack_trace = stack_trace(files, &custom_diagnostic.call_stack); let diagnostic = convert_diagnostic(custom_diagnostic, file, stack_trace, deny_warnings); diff --git a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml index 72fba8aadc2..15531fafff7 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml @@ -28,6 +28,7 @@ tracing.workspace = true chrono = "0.4.37" rayon.workspace = true cfg-if.workspace = true +smallvec = { version = "1.13.2", features = ["serde"] } [dev-dependencies] proptest.workspace = true diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/acir/acir_variable.rs b/noir/noir-repo/compiler/noirc_evaluator/src/acir/acir_variable.rs index 78188ea2b65..bb277751b9e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/acir/acir_variable.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/acir/acir_variable.rs @@ -954,6 +954,7 @@ impl> AcirContext { offset: AcirVar, bits: u32, ) -> Result<(), RuntimeError> { + #[allow(unused_qualifications)] const fn num_bits() -> usize { std::mem::size_of::() * 8 } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/acir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/acir/generated_acir.rs index b6a5a817ea7..a2b161688c0 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/acir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/acir/generated_acir.rs @@ -497,7 +497,7 @@ impl GeneratedAcir { /// This implies that either `y` or `t` or both is `0`. /// - If `t == 0`, then by definition `t == 0`. /// - If `y == 0`, this does not mean anything at this point in time, due to it having no - /// constraints. + /// constraints. /// /// Naively, we could apply the following constraint: `y == 1 - t`. /// This along with the previous `y * t == 0` constraint means that @@ -604,7 +604,7 @@ impl GeneratedAcir { ) { // Check whether we have a call to this Brillig function already exists. // This helps us optimize the Brillig metadata to only be stored once per Brillig entry point. - let inserted_func_before = self.brillig_locations.get(&brillig_function_index).is_some(); + let inserted_func_before = self.brillig_locations.contains_key(&brillig_function_index); let opcode = AcirOpcode::BrilligCall { id: brillig_function_index, inputs, outputs, predicate }; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs index e7b011b6d7b..95e0dd12132 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/acir/mod.rs @@ -1,7 +1,6 @@ //! This file holds the pass to convert from Noir's SSA IR to ACIR. use fxhash::FxHashMap as HashMap; -use im::Vector; use std::collections::{BTreeMap, HashSet}; use std::fmt::Debug; @@ -248,7 +247,7 @@ impl Debug for AcirDynamicArray { #[derive(Debug, Clone)] pub(crate) enum AcirValue { Var(AcirVar, AcirType), - Array(Vector), + Array(im::Vector), DynamicArray(AcirDynamicArray), } @@ -1118,7 +1117,7 @@ impl<'a> Context<'a> { &mut self, instruction: InstructionId, dfg: &DataFlowGraph, - array: Vector, + array: im::Vector, index: FieldElement, store_value: Option, ) -> Result { @@ -1303,7 +1302,7 @@ impl<'a> Context<'a> { match typ { Type::Numeric(_) => self.array_get_value(&Type::field(), call_data_block, offset), Type::Array(arc, len) => { - let mut result = Vector::new(); + let mut result = im::Vector::new(); for _i in 0..*len { for sub_type in arc.iter() { let element = self.get_from_call_data(offset, call_data_block, sub_type)?; @@ -1394,7 +1393,7 @@ impl<'a> Context<'a> { Ok(AcirValue::Var(read, typ)) } Type::Array(element_types, len) => { - let mut values = Vector::new(); + let mut values = im::Vector::new(); for _ in 0..len { for typ in element_types.as_ref() { values.push_back(self.array_get_value(typ, block_id, var_index)?); @@ -1682,7 +1681,7 @@ impl<'a> Context<'a> { let read = self.acir_context.read_from_memory(source, &index_var)?; Ok::(AcirValue::Var(read, AcirType::field())) })?; - let array: Vector = init_values.into(); + let array: im::Vector = init_values.into(); self.initialize_array(destination, array_len, Some(AcirValue::Array(array)))?; Ok(()) } @@ -2053,8 +2052,9 @@ impl<'a> Context<'a> { /// /// There are some edge cases to consider: /// - Constants are not explicitly type casted, so we need to check for this and - /// return the type of the other operand, if we have a constant. + /// return the type of the other operand, if we have a constant. /// - 0 is not seen as `Field 0` but instead as `Unit 0` + /// /// TODO: The latter seems like a bug, if we cannot differentiate between a function returning /// TODO nothing and a 0. /// @@ -2273,7 +2273,7 @@ impl<'a> Context<'a> { let slice = self.convert_value(slice_contents, dfg); let mut new_elem_size = Self::flattened_value_size(&slice); - let mut new_slice = Vector::new(); + let mut new_slice = im::Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; let elements_to_push = &arguments[2..]; @@ -2344,7 +2344,7 @@ impl<'a> Context<'a> { let one = self.acir_context.add_constant(FieldElement::one()); let new_slice_length = self.acir_context.add_var(slice_length, one)?; - let mut new_slice = Vector::new(); + let mut new_slice = im::Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; let elements_to_push = &arguments[2..]; @@ -2418,7 +2418,7 @@ impl<'a> Context<'a> { } let slice = self.convert_value(slice_contents, dfg); - let mut new_slice = Vector::new(); + let mut new_slice = im::Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; let mut results = vec![ @@ -2444,7 +2444,7 @@ impl<'a> Context<'a> { let slice = self.convert_value(slice_contents, dfg); - let mut new_slice = Vector::new(); + let mut new_slice = im::Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; let element_size = slice_typ.element_size(); @@ -2631,7 +2631,7 @@ impl<'a> Context<'a> { let slice_size = Self::flattened_value_size(&slice); - let mut new_slice = Vector::new(); + let mut new_slice = im::Vector::new(); self.slice_intrinsic_input(&mut new_slice, slice)?; // Compiler sanity check @@ -2760,8 +2760,6 @@ impl<'a> Context<'a> { unreachable!("Expected static_assert to be removed by this point") } Intrinsic::StrAsBytes => unreachable!("Expected as_bytes to be removed by this point"), - Intrinsic::FromField => unreachable!("Expected from_field to be removed by this point"), - Intrinsic::AsField => unreachable!("Expected as_field to be removed by this point"), Intrinsic::IsUnconstrained => { unreachable!("Expected is_unconstrained to be removed by this point") } @@ -2783,7 +2781,7 @@ impl<'a> Context<'a> { fn slice_intrinsic_input( &mut self, - old_slice: &mut Vector, + old_slice: &mut im::Vector, input: AcirValue, ) -> Result<(), RuntimeError> { match input { @@ -2905,7 +2903,6 @@ mod test { ssa::{ function_builder::FunctionBuilder, ir::{ - call_stack::CallStack, function::FunctionId, instruction::BinaryOp, map::Id, @@ -2932,8 +2929,7 @@ mod test { builder.new_function("foo".into(), foo_id, inline_type); } // Set a call stack for testing whether `brillig_locations` in the `GeneratedAcir` was accurately set. - let mut stack = CallStack::unit(Location::dummy()); - stack.push_back(Location::dummy()); + let stack = vec![Location::dummy(), Location::dummy()]; let call_stack = builder.current_function.dfg.call_stack_data.get_or_insert_locations(stack); builder.set_call_stack(call_stack); @@ -3358,8 +3354,8 @@ mod test { // We have two normal Brillig functions that was called multiple times. // We should have a single locations map for each function's debug metadata. assert_eq!(main_acir.brillig_locations.len(), 2); - assert!(main_acir.brillig_locations.get(&BrilligFunctionId(0)).is_some()); - assert!(main_acir.brillig_locations.get(&BrilligFunctionId(1)).is_some()); + assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); + assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(1))); } // Test that given multiple primitive operations that are represented by Brillig directives (e.g. invert/quotient), @@ -3494,7 +3490,7 @@ mod test { // We have one normal Brillig functions that was called twice. // We should have a single locations map for each function's debug metadata. assert_eq!(main_acir.brillig_locations.len(), 1); - assert!(main_acir.brillig_locations.get(&BrilligFunctionId(0)).is_some()); + assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); } // Test that given both normal Brillig calls, Brillig stdlib calls, and non-inlined ACIR calls, that we accurately generate ACIR. @@ -3587,7 +3583,7 @@ mod test { ); assert_eq!(main_acir.brillig_locations.len(), 1); - assert!(main_acir.brillig_locations.get(&BrilligFunctionId(0)).is_some()); + assert!(main_acir.brillig_locations.contains_key(&BrilligFunctionId(0))); let foo_acir = &acir_functions[1]; let foo_opcodes = foo_acir.opcodes(); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 0b02cd29f0f..5bcddc21275 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -635,9 +635,7 @@ impl<'block> BrilligBlock<'block> { let array = array.extract_register(); self.brillig_context.load_instruction(destination, array); } - Intrinsic::FromField - | Intrinsic::AsField - | Intrinsic::IsUnconstrained + Intrinsic::IsUnconstrained | Intrinsic::DerivePedersenGenerators | Intrinsic::ApplyRangeConstraint | Intrinsic::StrAsBytes diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index be4a6b84bc1..3654a95a03f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -27,7 +27,7 @@ pub(crate) struct GeneratedBrillig { pub(crate) locations: BTreeMap, pub(crate) error_types: BTreeMap, pub(crate) name: String, - pub(crate) procedure_locations: HashMap, + pub(crate) procedure_locations: BTreeMap, } #[derive(Default, Debug, Clone)] @@ -61,7 +61,7 @@ pub(crate) struct BrilligArtifact { /// This is created as artifacts are linked together and allows us to determine /// which opcodes originate from reusable procedures.s /// The range is inclusive for both start and end opcode locations. - pub(crate) procedure_locations: HashMap, + pub(crate) procedure_locations: BTreeMap, } /// A pointer to a location in the opcode. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs index ee662c50b75..1e484f8af59 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/errors.rs @@ -201,7 +201,7 @@ impl RuntimeError { RuntimeError::UnknownLoopBound { .. } => { let primary_message = self.to_string(); let location = - self.call_stack().back().expect("Expected RuntimeError to have a location"); + self.call_stack().last().expect("Expected RuntimeError to have a location"); Diagnostic::simple_error( primary_message, @@ -212,7 +212,7 @@ impl RuntimeError { _ => { let message = self.to_string(); let location = - self.call_stack().back().unwrap_or_else(|| panic!("Expected RuntimeError to have a location. Error message: {message}")); + self.call_stack().last().unwrap_or_else(|| panic!("Expected RuntimeError to have a location. Error message: {message}")); Diagnostic::simple_error(message, String::new(), location.span) } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index 48be8eb7ad8..2f167ec8ab3 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -294,11 +294,9 @@ impl DependencyContext { Intrinsic::ArrayLen | Intrinsic::ArrayRefCount | Intrinsic::ArrayAsStrUnchecked - | Intrinsic::AsField | Intrinsic::AsSlice | Intrinsic::BlackBox(..) | Intrinsic::DerivePedersenGenerators - | Intrinsic::FromField | Intrinsic::Hint(..) | Intrinsic::SlicePushBack | Intrinsic::SlicePushFront @@ -316,10 +314,9 @@ impl DependencyContext { self.update_children(&arguments, &results); } }, - Value::Function(callee) => match all_functions[&callee].runtime() { + Value::Function(callee) => match all_functions[callee].runtime() { RuntimeType::Brillig(_) => { // Record arguments/results for each Brillig call for the check - self.tainted.insert( *instruction, BrilligTaintedIds::new(&arguments, &results), @@ -575,12 +572,10 @@ impl Context { Intrinsic::ArrayLen | Intrinsic::ArrayAsStrUnchecked | Intrinsic::ArrayRefCount - | Intrinsic::AsField | Intrinsic::AsSlice | Intrinsic::BlackBox(..) | Intrinsic::Hint(Hint::BlackBox) | Intrinsic::DerivePedersenGenerators - | Intrinsic::FromField | Intrinsic::SliceInsert | Intrinsic::SlicePushBack | Intrinsic::SlicePushFront @@ -596,7 +591,7 @@ impl Context { self.value_sets.push(instruction_arguments_and_results); } }, - Value::Function(callee) => match all_functions[&callee].runtime() { + Value::Function(callee) => match all_functions[callee].runtime() { RuntimeType::Brillig(_) => { // For calls to Brillig functions we memorize the mapping of results to argument ValueId's and InstructionId's // The latter are needed to produce the callstack later 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 48af34d466c..068fff7d284 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 @@ -252,7 +252,7 @@ impl FunctionBuilder { for size in ssa_param_sizes { let visibilities: Vec = flattened_params_databus_visibility.drain(0..size).collect(); - let visibility = visibilities.get(0).copied().unwrap_or(DatabusVisibility::None); + let visibility = visibilities.first().copied().unwrap_or(DatabusVisibility::None); assert!( visibilities.iter().all(|v| *v == visibility), "inconsistent databus visibility for ssa param" diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/call_stack.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/call_stack.rs index 113609f4a11..e1df616bc66 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/call_stack.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/call_stack.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use noirc_errors::Location; -pub(crate) type CallStack = im::Vector; +pub(crate) type CallStack = Vec; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] pub(crate) struct CallStackId(u32); @@ -57,9 +57,9 @@ impl Default for CallStackHelper { impl CallStackHelper { /// Construct a CallStack from a CallStackId pub(crate) fn get_call_stack(&self, mut call_stack: CallStackId) -> CallStack { - let mut result = im::Vector::new(); + let mut result = Vec::new(); while let Some(parent) = self.locations[call_stack.index()].parent { - result.push_back(self.locations[call_stack.index()].value); + result.push(self.locations[call_stack.index()].value); call_stack = parent; } result diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index 72ba369f98c..9ccf7f11512 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -42,7 +42,7 @@ pub(crate) struct DataFlowGraph { /// Call instructions require the func signature, but /// other instructions may need some more reading on my part #[serde_as(as = "HashMap")] - results: HashMap>, + results: HashMap>, /// Storage for all of the values defined in this /// function. @@ -165,6 +165,36 @@ impl DataFlowGraph { id } + fn insert_instruction_without_simplification( + &mut self, + instruction_data: Instruction, + block: BasicBlockId, + ctrl_typevars: Option>, + call_stack: CallStackId, + ) -> InstructionId { + let id = self.make_instruction(instruction_data, ctrl_typevars); + self.blocks[block].insert_instruction(id); + self.locations.insert(id, call_stack); + id + } + + pub(crate) fn insert_instruction_and_results_without_simplification( + &mut self, + instruction_data: Instruction, + block: BasicBlockId, + ctrl_typevars: Option>, + call_stack: CallStackId, + ) -> InsertInstructionResult { + let id = self.insert_instruction_without_simplification( + instruction_data, + block, + ctrl_typevars, + call_stack, + ); + + InsertInstructionResult::Results(id, self.instruction_results(id)) + } + /// Inserts a new instruction at the end of the given block and returns its results pub(crate) fn insert_instruction_and_results( &mut self, @@ -184,7 +214,8 @@ impl DataFlowGraph { result @ (SimplifyResult::SimplifiedToInstruction(_) | SimplifyResult::SimplifiedToInstructionMultiple(_) | SimplifyResult::None) => { - let instructions = result.instructions().unwrap_or(vec![instruction]); + let mut instructions = result.instructions().unwrap_or(vec![instruction]); + assert!(!instructions.is_empty(), "`SimplifyResult::SimplifiedToInstructionMultiple` must not return empty vector"); if instructions.len() > 1 { // There's currently no way to pass results from one instruction in `instructions` on to the next. @@ -196,17 +227,22 @@ impl DataFlowGraph { ); } - let mut last_id = None; - + // Pull off the last instruction as we want to return its results. + let last_instruction = instructions.pop().expect("`instructions` can't be empty"); for instruction in instructions { - let id = self.make_instruction(instruction, ctrl_typevars.clone()); - self.blocks[block].insert_instruction(id); - self.locations.insert(id, call_stack); - last_id = Some(id); + self.insert_instruction_without_simplification( + instruction, + block, + ctrl_typevars.clone(), + call_stack, + ); } - - let id = last_id.expect("There should be at least 1 simplified instruction"); - InsertInstructionResult::Results(id, self.instruction_results(id)) + self.insert_instruction_and_results_without_simplification( + last_instruction, + block, + ctrl_typevars, + call_stack, + ) } } } @@ -306,16 +342,18 @@ impl DataFlowGraph { /// Returns the results of the instruction pub(crate) fn make_instruction_results( &mut self, - instruction_id: InstructionId, + instruction: InstructionId, ctrl_typevars: Option>, ) { - let result_types = self.instruction_result_types(instruction_id, ctrl_typevars); - let results = vecmap(result_types.into_iter().enumerate(), |(position, typ)| { - let instruction = instruction_id; - self.values.insert(Value::Instruction { typ, position, instruction }) + let mut results = smallvec::SmallVec::new(); + let mut position = 0; + self.for_each_instruction_result_type(instruction, ctrl_typevars, |this, typ| { + let result = this.values.insert(Value::Instruction { typ, position, instruction }); + position += 1; + results.push(result); }); - self.results.insert(instruction_id, results); + self.results.insert(instruction, results); } /// Return the result types of this instruction. @@ -326,18 +364,21 @@ impl DataFlowGraph { /// the type of an instruction that does not require them. Compared to passing an empty Vec, /// Option has the benefit of panicking if it is accidentally used for a Call instruction, /// rather than silently returning the empty Vec and continuing. - fn instruction_result_types( - &self, + fn for_each_instruction_result_type( + &mut self, instruction_id: InstructionId, ctrl_typevars: Option>, - ) -> Vec { + mut f: impl FnMut(&mut Self, Type), + ) { let instruction = &self.instructions[instruction_id]; match instruction.result_type() { - InstructionResultType::Known(typ) => vec![typ], - InstructionResultType::Operand(value) => vec![self.type_of_value(value)], - InstructionResultType::None => vec![], + InstructionResultType::Known(typ) => f(self, typ), + InstructionResultType::Operand(value) => f(self, self.type_of_value(value)), + InstructionResultType::None => (), InstructionResultType::Unknown => { - ctrl_typevars.expect("Control typevars required but not given") + for typ in ctrl_typevars.expect("Control typevars required but not given") { + f(self, typ); + } } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 3072bb1d72d..5d9bfc89f61 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -43,9 +43,9 @@ pub(crate) type InstructionId = Id; /// These are similar to built-ins in other languages. /// These can be classified under two categories: /// - Opcodes which the IR knows the target machine has -/// special support for. (LowLevel) +/// special support for. (LowLevel) /// - Opcodes which have no function definition in the -/// source code and must be processed by the IR. +/// source code and must be processed by the IR. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub(crate) enum Intrinsic { ArrayLen, @@ -65,8 +65,6 @@ pub(crate) enum Intrinsic { ToRadix(Endian), BlackBox(BlackBoxFunc), Hint(Hint), - FromField, - AsField, AsWitness, IsUnconstrained, DerivePedersenGenerators, @@ -97,8 +95,6 @@ impl std::fmt::Display for Intrinsic { Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"), Intrinsic::BlackBox(function) => write!(f, "{function}"), Intrinsic::Hint(Hint::BlackBox) => write!(f, "black_box"), - Intrinsic::FromField => write!(f, "from_field"), - Intrinsic::AsField => write!(f, "as_field"), Intrinsic::AsWitness => write!(f, "as_witness"), Intrinsic::IsUnconstrained => write!(f, "is_unconstrained"), Intrinsic::DerivePedersenGenerators => write!(f, "derive_pedersen_generators"), @@ -140,8 +136,6 @@ impl Intrinsic { | Intrinsic::SlicePushFront | Intrinsic::SliceInsert | Intrinsic::StrAsBytes - | Intrinsic::FromField - | Intrinsic::AsField | Intrinsic::IsUnconstrained | Intrinsic::DerivePedersenGenerators | Intrinsic::FieldLessThan => false, @@ -213,8 +207,6 @@ impl Intrinsic { "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)), "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)), "to_be_bits" => Some(Intrinsic::ToBits(Endian::Big)), - "from_field" => Some(Intrinsic::FromField), - "as_field" => Some(Intrinsic::AsField), "as_witness" => Some(Intrinsic::AsWitness), "is_unconstrained" => Some(Intrinsic::IsUnconstrained), "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators), @@ -556,10 +548,25 @@ impl Instruction { /// If true the instruction will depend on `enable_side_effects` context during acir-gen. pub(crate) fn requires_acir_gen_predicate(&self, dfg: &DataFlowGraph) -> bool { match self { - Instruction::Binary(binary) - if matches!(binary.operator, BinaryOp::Div | BinaryOp::Mod) => - { - true + Instruction::Binary(binary) => { + match binary.operator { + BinaryOp::Add + | BinaryOp::Sub + | BinaryOp::Mul + | BinaryOp::Div + | BinaryOp::Mod => { + // Some binary math can overflow or underflow, but this is only the case + // for unsigned types (here we assume the type of binary.lhs is the same) + dfg.type_of_value(binary.rhs).is_unsigned() + } + BinaryOp::Eq + | BinaryOp::Lt + | BinaryOp::And + | BinaryOp::Or + | BinaryOp::Xor + | BinaryOp::Shl + | BinaryOp::Shr => false, + } } Instruction::ArrayGet { array, index } => { @@ -577,7 +584,6 @@ impl Instruction { _ => false, }, Instruction::Cast(_, _) - | Instruction::Binary(_) | Instruction::Not(_) | Instruction::Truncate { .. } | Instruction::Constrain(_, _, _) @@ -952,9 +958,11 @@ impl Instruction { } } Instruction::IfElse { then_condition, then_value, else_condition, else_value } => { + let then_condition = dfg.resolve(*then_condition); + let else_condition = dfg.resolve(*else_condition); let typ = dfg.type_of_value(*then_value); - if let Some(constant) = dfg.get_numeric_constant(*then_condition) { + if let Some(constant) = dfg.get_numeric_constant(then_condition) { if constant.is_one() { return SimplifiedTo(*then_value); } else if constant.is_zero() { @@ -968,10 +976,51 @@ impl Instruction { return SimplifiedTo(then_value); } - if matches!(&typ, Type::Numeric(_)) { - let then_condition = *then_condition; - let else_condition = *else_condition; + if let Value::Instruction { instruction, .. } = &dfg[then_value] { + if let Instruction::IfElse { + then_condition: inner_then_condition, + then_value: inner_then_value, + else_condition: inner_else_condition, + .. + } = dfg[*instruction] + { + if then_condition == inner_then_condition { + let instruction = Instruction::IfElse { + then_condition, + then_value: inner_then_value, + else_condition: inner_else_condition, + else_value, + }; + return SimplifiedToInstruction(instruction); + } + // TODO: We could check to see if `then_condition == inner_else_condition` + // but we run into issues with duplicate NOT instructions having distinct ValueIds. + } + }; + + if let Value::Instruction { instruction, .. } = &dfg[else_value] { + if let Instruction::IfElse { + then_condition: inner_then_condition, + else_condition: inner_else_condition, + else_value: inner_else_value, + .. + } = dfg[*instruction] + { + if then_condition == inner_then_condition { + let instruction = Instruction::IfElse { + then_condition, + then_value, + else_condition: inner_else_condition, + else_value: inner_else_value, + }; + return SimplifiedToInstruction(instruction); + } + // TODO: We could check to see if `then_condition == inner_else_condition` + // but we run into issues with duplicate NOT instructions having distinct ValueIds. + } + }; + if matches!(&typ, Type::Numeric(_)) { let result = ValueMerger::merge_numeric_values( dfg, block, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index fa7d314ff33..6da4c7702c8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -330,31 +330,6 @@ pub(super) fn simplify_call( Intrinsic::BlackBox(bb_func) => { simplify_black_box_func(bb_func, arguments, dfg, block, call_stack) } - Intrinsic::AsField => { - let instruction = Instruction::Cast(arguments[0], NumericType::NativeField); - SimplifyResult::SimplifiedToInstruction(instruction) - } - Intrinsic::FromField => { - let incoming_type = Type::field(); - let target_type = return_type.clone().unwrap(); - - let truncate = Instruction::Truncate { - value: arguments[0], - bit_size: target_type.bit_size(), - max_bit_size: incoming_type.bit_size(), - }; - let truncated_value = dfg - .insert_instruction_and_results( - truncate, - block, - Some(vec![incoming_type]), - call_stack, - ) - .first(); - - let instruction = Instruction::Cast(truncated_value, target_type.unwrap_numeric()); - SimplifyResult::SimplifiedToInstruction(instruction) - } Intrinsic::AsWitness => SimplifyResult::None, Intrinsic::IsUnconstrained => SimplifyResult::None, Intrinsic::DerivePedersenGenerators => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/list.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/list.rs deleted file mode 100644 index 9a84d304444..00000000000 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/list.rs +++ /dev/null @@ -1,187 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::sync::Arc; - -/// A shared linked list type intended to be cloned -#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct List { - head: Arc>, - len: usize, -} - -#[derive(Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -enum Node { - #[default] - Nil, - Cons(T, Arc>), -} - -impl Default for List { - fn default() -> Self { - List { head: Arc::new(Node::Nil), len: 0 } - } -} - -impl List { - pub fn new() -> Self { - Self::default() - } - - /// This is actually a push_front since we just create a new head for the - /// list. This is done so that the tail of the list can still be shared. - /// In the case of call stacks, the last node will be main, while the top - /// of the call stack will be the head of this list. - pub fn push_back(&mut self, value: T) { - self.len += 1; - self.head = Arc::new(Node::Cons(value, self.head.clone())); - } - - /// It is more efficient to iterate from the head of the list towards the tail. - /// For callstacks this means from the top of the call stack towards main. - fn iter_rev(&self) -> IterRev { - IterRev { head: &self.head, len: self.len } - } - - pub fn clear(&mut self) { - *self = Self::default(); - } - - pub fn append(&mut self, other: Self) - where - T: Copy + std::fmt::Debug, - { - let other = other.into_iter().collect::>(); - - for item in other { - self.push_back(item); - } - } - - pub fn len(&self) -> usize { - self.len - } - - pub fn is_empty(&self) -> bool { - self.len == 0 - } - - fn pop_front(&mut self) -> Option - where - T: Copy, - { - match self.head.as_ref() { - Node::Nil => None, - Node::Cons(value, rest) => { - let value = *value; - self.head = rest.clone(); - self.len -= 1; - Some(value) - } - } - } - - pub fn truncate(&mut self, len: usize) - where - T: Copy, - { - if self.len > len { - for _ in 0..self.len - len { - self.pop_front(); - } - } - } - - pub fn unit(item: T) -> Self { - let mut this = Self::default(); - this.push_back(item); - this - } - - pub fn back(&self) -> Option<&T> { - match self.head.as_ref() { - Node::Nil => None, - Node::Cons(item, _) => Some(item), - } - } -} - -pub struct IterRev<'a, T> { - head: &'a Node, - len: usize, -} - -impl IntoIterator for List -where - T: Copy + std::fmt::Debug, -{ - type Item = T; - - type IntoIter = std::iter::Rev>; - - fn into_iter(self) -> Self::IntoIter { - let items: Vec<_> = self.iter_rev().copied().collect(); - items.into_iter().rev() - } -} - -impl<'a, T> IntoIterator for &'a List { - type Item = &'a T; - - type IntoIter = std::iter::Rev< as IntoIterator>::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { - let items: Vec<_> = self.iter_rev().collect(); - items.into_iter().rev() - } -} - -impl<'a, T> Iterator for IterRev<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option { - match self.head { - Node::Nil => None, - Node::Cons(value, rest) => { - self.head = rest; - Some(value) - } - } - } - - fn size_hint(&self) -> (usize, Option) { - (0, Some(self.len)) - } -} - -impl<'a, T> ExactSizeIterator for IterRev<'a, T> {} - -impl std::fmt::Debug for List -where - T: std::fmt::Debug, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[")?; - for (i, item) in self.iter_rev().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "{item:?}")?; - } - write!(f, "]") - } -} - -impl std::fmt::Display for List -where - T: std::fmt::Display, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[")?; - for (i, item) in self.iter_rev().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "{item}")?; - } - write!(f, "]") - } -} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/mod.rs index 88e0d8900db..686606452fb 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/mod.rs @@ -6,7 +6,6 @@ pub(crate) mod dom; pub(crate) mod function; pub(crate) mod function_inserter; pub(crate) mod instruction; -pub mod list; pub(crate) mod map; pub(crate) mod post_order; pub(crate) mod printer; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 43420229257..aec172dddcd 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -34,7 +34,7 @@ //! the following transformations of certain instructions within the block are expected: //! //! 1. A constraint is multiplied by the condition and changes the constraint to -//! an equality with c: +//! an equality with c: //! //! constrain v0 //! ============ @@ -211,6 +211,13 @@ struct Context<'f> { /// /// The `ValueId` here is that which is returned by the allocate instruction. local_allocations: HashSet, + + /// A map from `cond` to `Not(cond)` + /// + /// `Not` instructions are inserted constantly by this pass and this map helps keep + /// us from unnecessarily inserting extra instructions, and keeps ids unique which + /// helps simplifications. + not_instructions: HashMap, } #[derive(Clone)] @@ -256,6 +263,7 @@ fn flatten_function_cfg(function: &mut Function, no_predicates: &HashMap Context<'f> { let condition_call_stack = self.inserter.function.dfg.get_value_call_stack_id(cond_context.condition); - let else_condition = - self.insert_instruction(Instruction::Not(cond_context.condition), condition_call_stack); + + let else_condition = self.not_instruction(cond_context.condition, condition_call_stack); let else_condition = self.link_condition(else_condition); let old_allocations = std::mem::take(&mut self.local_allocations); @@ -464,6 +472,16 @@ impl<'f> Context<'f> { vec![self.cfg.successors(*block).next().unwrap()] } + fn not_instruction(&mut self, condition: ValueId, call_stack: CallStackId) -> ValueId { + if let Some(existing) = self.not_instructions.get(&condition) { + return *existing; + } + + let not = self.insert_instruction(Instruction::Not(condition), call_stack); + self.not_instructions.insert(condition, not); + not + } + /// Process the 'exit' block of a conditional statement fn else_stop(&mut self, block: &BasicBlockId) -> Vec { let mut cond_context = self.condition_stack.pop().unwrap(); @@ -671,8 +689,7 @@ impl<'f> Context<'f> { .insert_instruction_with_typevars(load, Some(vec![typ]), call_stack) .first(); - let else_condition = - self.insert_instruction(Instruction::Not(condition), call_stack); + let else_condition = self.not_instruction(condition, call_stack); let instruction = Instruction::IfElse { then_condition: condition, @@ -706,7 +723,6 @@ impl<'f> Context<'f> { let argument_type = self.inserter.function.dfg.type_of_value(field); let cast = Instruction::Cast(condition, argument_type.unwrap_numeric()); - let casted_condition = self.insert_instruction(cast, call_stack); let field = self.insert_instruction( Instruction::binary(BinaryOp::Mul, field, casted_condition), @@ -789,7 +805,7 @@ impl<'f> Context<'f> { fn var_or_one(&mut self, var: ValueId, condition: ValueId, call_stack: CallStackId) -> ValueId { let field = self.insert_instruction(Instruction::binary(BinaryOp::Mul, var, condition), call_stack); - let not_condition = self.insert_instruction(Instruction::Not(condition), call_stack); + let not_condition = self.not_instruction(condition, call_stack); self.insert_instruction( Instruction::binary(BinaryOp::Add, field, not_condition), call_stack, @@ -910,7 +926,6 @@ mod test { v8 = mul v5, v2 v9 = add v7, v8 store v9 at v1 - v10 = not v0 enable_side_effects u1 1 return } @@ -949,15 +964,14 @@ mod test { v8 = mul v5, v2 v9 = add v7, v8 store v9 at v1 - v10 = not v0 - enable_side_effects v10 - v11 = load v1 -> Field - v12 = cast v10 as Field - v13 = cast v0 as Field - v15 = mul v12, Field 6 - v16 = mul v13, v11 - v17 = add v15, v16 - store v17 at v1 + enable_side_effects v3 + v10 = load v1 -> Field + v11 = cast v3 as Field + v12 = cast v0 as Field + v14 = mul v11, Field 6 + v15 = mul v12, v10 + v16 = add v14, v15 + store v16 at v1 enable_side_effects u1 1 return } @@ -1085,16 +1099,14 @@ mod test { v23 = mul v20, Field 6 v24 = mul v21, v16 v25 = add v23, v24 - enable_side_effects v0 - v26 = not v0 - enable_side_effects v26 - v27 = cast v26 as Field - v28 = cast v0 as Field - v30 = mul v27, Field 3 - v31 = mul v28, v25 - v32 = add v30, v31 + enable_side_effects v3 + v26 = cast v3 as Field + v27 = cast v0 as Field + v29 = mul v26, Field 3 + v30 = mul v27, v25 + v31 = add v29, v30 enable_side_effects u1 1 - return v32 + return v31 }"; let main = ssa.main(); @@ -1288,13 +1300,12 @@ mod test { v16 = mul v14, v11 v17 = add v15, v16 store v17 at v6 - v18 = not v5 - enable_side_effects v18 - v19 = load v6 -> u8 - v20 = cast v18 as u8 - v21 = cast v4 as u8 - v22 = mul v21, v19 - store v22 at v6 + enable_side_effects v12 + v18 = load v6 -> u8 + v19 = cast v12 as u8 + v20 = cast v4 as u8 + v21 = mul v20, v18 + store v21 at v6 enable_side_effects u1 1 constrain v5 == u1 1 return @@ -1460,4 +1471,88 @@ mod test { let merged_ssa = Ssa::from_str(src).unwrap(); let _ = merged_ssa.flatten_cfg(); } + + #[test] + fn eliminates_unnecessary_if_else_instructions_on_numeric_types() { + let src = " + acir(inline) fn main f0 { + b0(v0: bool): + v1 = allocate -> &mut [Field; 1] + store Field 0 at v1 + jmpif v0 then: b1, else: b2 + b1(): + store Field 1 at v1 + store Field 2 at v1 + jmp b2() + b2(): + v3 = load v1 -> Field + return v3 + }"; + + let ssa = Ssa::from_str(src).unwrap(); + + let ssa = ssa.flatten_cfg().mem2reg().fold_constants(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: u1): + v1 = allocate -> &mut [Field; 1] + enable_side_effects v0 + v2 = not v0 + v3 = cast v0 as Field + v4 = cast v2 as Field + v6 = mul v3, Field 2 + v7 = mul v4, v3 + v8 = add v6, v7 + enable_side_effects u1 1 + return v8 + } + "; + + assert_normalized_ssa_equals(ssa, expected); + } + + #[test] + fn eliminates_unnecessary_if_else_instructions_on_array_types() { + let src = " + acir(inline) fn main f0 { + b0(v0: bool, v1: bool): + v2 = make_array [Field 0] : [Field; 1] + v3 = allocate -> &mut [Field; 1] + store v2 at v3 + jmpif v0 then: b1, else: b2 + b1(): + v4 = make_array [Field 1] : [Field; 1] + store v4 at v3 + v5 = make_array [Field 2] : [Field; 1] + store v5 at v3 + jmp b2() + b2(): + v24 = load v3 -> Field + return v24 + }"; + + let ssa = Ssa::from_str(src).unwrap(); + + let ssa = ssa + .flatten_cfg() + .mem2reg() + .remove_if_else() + .fold_constants() + .dead_instruction_elimination(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u1): + enable_side_effects v0 + v2 = cast v0 as Field + v4 = mul v2, Field 2 + v5 = make_array [v4] : [Field; 1] + enable_side_effects u1 1 + return v5 + } + "; + + assert_normalized_ssa_equals(ssa, expected); + } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs index ce54bb533f7..85240106f9f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/branch_analysis.rs @@ -2,14 +2,14 @@ //! //! The algorithm is split into two parts: //! 1. The outer part: -//! A. An (unrolled) CFG can be though of as a linear sequence of blocks where some nodes split -//! off, but eventually rejoin to a new node and continue the linear sequence. -//! B. Follow this sequence in order, and whenever a split is found call -//! `find_join_point_of_branches` and then recur from the join point it returns until the -//! return instruction is found. +//! A. An (unrolled) CFG can be though of as a linear sequence of blocks where some nodes split +//! off, but eventually rejoin to a new node and continue the linear sequence. +//! B. Follow this sequence in order, and whenever a split is found call +//! `find_join_point_of_branches` and then recur from the join point it returns until the +//! return instruction is found. //! //! 2. The inner part defined by `find_join_point_of_branches`: -//! A. For each of the two branches in a jmpif block: +//! A. For each of the two branches in a jmpif block: //! - Check if either has multiple predecessors. If so, it is a join point. //! - If not, continue to search the linear sequence of successor blocks from that block. //! - If another split point is found, recur in `find_join_point_of_branches` diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs index 44c0eb380c2..c188ed1f80f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/loop_invariant.rs @@ -263,17 +263,17 @@ mod test { fn simple_loop_invariant_code_motion() { let src = " brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): - jmp b1(u32 0) - b1(v2: u32): - v5 = lt v2, u32 4 + b0(v0: i32, v1: i32): + jmp b1(i32 0) + b1(v2: i32): + v5 = lt v2, i32 4 jmpif v5 then: b3, else: b2 b2(): return b3(): v6 = mul v0, v1 - constrain v6 == u32 6 - v8 = add v2, u32 1 + constrain v6 == i32 6 + v8 = add v2, i32 1 jmp b1(v8) } "; @@ -287,17 +287,17 @@ mod test { // `v6 = mul v0, v1` in b3 should now be `v3 = mul v0, v1` in b0 let expected = " brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): + b0(v0: i32, v1: i32): v3 = mul v0, v1 - jmp b1(u32 0) - b1(v2: u32): - v6 = lt v2, u32 4 + jmp b1(i32 0) + b1(v2: i32): + v6 = lt v2, i32 4 jmpif v6 then: b3, else: b2 b2(): return b3(): - constrain v3 == u32 6 - v9 = add v2, u32 1 + constrain v3 == i32 6 + v9 = add v2, i32 1 jmp b1(v9) } "; @@ -312,25 +312,25 @@ mod test { // is hoisted to the parent loop's pre-header block. let src = " brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): - jmp b1(u32 0) - b1(v2: u32): - v6 = lt v2, u32 4 + b0(v0: i32, v1: i32): + jmp b1(i32 0) + b1(v2: i32): + v6 = lt v2, i32 4 jmpif v6 then: b3, else: b2 b2(): return b3(): - jmp b4(u32 0) - b4(v3: u32): - v7 = lt v3, u32 4 + jmp b4(i32 0) + b4(v3: i32): + v7 = lt v3, i32 4 jmpif v7 then: b6, else: b5 b5(): - v9 = add v2, u32 1 + v9 = add v2, i32 1 jmp b1(v9) b6(): v10 = mul v0, v1 - constrain v10 == u32 6 - v12 = add v3, u32 1 + constrain v10 == i32 6 + v12 = add v3, i32 1 jmp b4(v12) } "; @@ -344,25 +344,25 @@ mod test { // `v10 = mul v0, v1` in b6 should now be `v4 = mul v0, v1` in b0 let expected = " brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): + b0(v0: i32, v1: i32): v4 = mul v0, v1 - jmp b1(u32 0) - b1(v2: u32): - v7 = lt v2, u32 4 + jmp b1(i32 0) + b1(v2: i32): + v7 = lt v2, i32 4 jmpif v7 then: b3, else: b2 b2(): return b3(): - jmp b4(u32 0) - b4(v3: u32): - v8 = lt v3, u32 4 + jmp b4(i32 0) + b4(v3: i32): + v8 = lt v3, i32 4 jmpif v8 then: b6, else: b5 b5(): - v10 = add v2, u32 1 + v10 = add v2, i32 1 jmp b1(v10) b6(): - constrain v4 == u32 6 - v12 = add v3, u32 1 + constrain v4 == i32 6 + v12 = add v3, i32 1 jmp b4(v12) } "; @@ -386,19 +386,19 @@ mod test { // hoist `v7 = mul v6, v0`. let src = " brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): - jmp b1(u32 0) - b1(v2: u32): - v5 = lt v2, u32 4 + b0(v0: i32, v1: i32): + jmp b1(i32 0) + b1(v2: i32): + v5 = lt v2, i32 4 jmpif v5 then: b3, else: b2 b2(): return b3(): v6 = mul v0, v1 v7 = mul v6, v0 - v8 = eq v7, u32 12 - constrain v7 == u32 12 - v9 = add v2, u32 1 + v8 = eq v7, i32 12 + constrain v7 == i32 12 + v9 = add v2, i32 1 jmp b1(v9) } "; @@ -411,19 +411,19 @@ mod test { let expected = " brillig(inline) fn main f0 { - b0(v0: u32, v1: u32): + b0(v0: i32, v1: i32): v3 = mul v0, v1 v4 = mul v3, v0 - v6 = eq v4, u32 12 - jmp b1(u32 0) - b1(v2: u32): - v9 = lt v2, u32 4 + v6 = eq v4, i32 12 + jmp b1(i32 0) + b1(v2: i32): + v9 = lt v2, i32 4 jmpif v9 then: b3, else: b2 b2(): return b3(): - constrain v4 == u32 12 - v11 = add v2, u32 1 + constrain v4 == i32 12 + v11 = add v2, i32 1 jmp b1(v11) } "; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 1e5cd8bdfbd..4356a23335c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -32,9 +32,11 @@ //! - We also track the last instance of a load instruction to each address in a block. //! If we see that the last load instruction was from the same address as the current load instruction, //! we move to replace the result of the current load with the result of the previous load. +//! //! This removal requires a couple conditions: -//! - No store occurs to that address before the next load, -//! - The address is not used as an argument to a call +//! - No store occurs to that address before the next load, +//! - The address is not used as an argument to a call +//! //! This optimization helps us remove repeated loads for which there are not known values. //! - On `Instruction::Store { address, value }`: //! - If the address of the store is known: @@ -200,7 +202,7 @@ impl<'f> PerFunctionContext<'f> { .get(store_address) .map_or(false, |expression| matches!(expression, Expression::Dereference(_))); - if self.last_loads.get(store_address).is_none() + if !self.last_loads.contains_key(store_address) && !store_alias_used && !is_dereference { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index e85e2c4a441..e99f239e82e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -176,8 +176,6 @@ impl Context { | Intrinsic::ToRadix(_) | Intrinsic::BlackBox(_) | Intrinsic::Hint(Hint::BlackBox) - | Intrinsic::FromField - | Intrinsic::AsField | Intrinsic::AsSlice | Intrinsic::AsWitness | Intrinsic::IsUnconstrained diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 79f2354aff6..9a931e36ea2 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -235,8 +235,6 @@ fn slice_capacity_change( | Intrinsic::StrAsBytes | Intrinsic::BlackBox(_) | Intrinsic::Hint(Hint::BlackBox) - | Intrinsic::FromField - | Intrinsic::AsField | Intrinsic::AsWitness | Intrinsic::IsUnconstrained | Intrinsic::DerivePedersenGenerators diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs index 87e680932c6..eff6576b87f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/resolve_is_unconstrained.rs @@ -39,6 +39,8 @@ impl Function { } } + let is_unconstrained = matches!(self.runtime(), RuntimeType::Brillig(_)).into(); + let is_within_unconstrained = self.dfg.make_constant(is_unconstrained, NumericType::bool()); for instruction_id in is_unconstrained_calls { let call_returns = self.dfg.instruction_results(instruction_id); let original_return_id = call_returns[0]; @@ -46,9 +48,6 @@ impl Function { // We replace the result with a fresh id. This will be unused, so the DIE pass will remove the leftover intrinsic call. self.dfg.replace_result(instruction_id, original_return_id); - let is_unconstrained = matches!(self.runtime(), RuntimeType::Brillig(_)).into(); - let is_within_unconstrained = - self.dfg.make_constant(is_unconstrained, NumericType::bool()); // Replace all uses of the original return value with the constant self.dfg.set_value_from_id(original_return_id, is_within_unconstrained); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/runtime_separation.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/runtime_separation.rs index 5628e12b9ae..b671d5011a1 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/runtime_separation.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/runtime_separation.rs @@ -78,7 +78,7 @@ impl RuntimeSeparatorContext { if within_brillig { for called_func_id in called_functions.iter() { - let called_func = &ssa.functions[&called_func_id]; + let called_func = &ssa.functions[called_func_id]; if matches!(called_func.runtime(), RuntimeType::Acir(_)) { self.acir_functions_called_from_brillig.insert(*called_func_id); } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs index 2a272236195..ab4256197b9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/unrolling.rs @@ -18,6 +18,8 @@ //! //! When unrolling ACIR code, we remove reference count instructions because they are //! only used by Brillig bytecode. +use std::collections::BTreeSet; + use acvm::{acir::AcirField, FieldElement}; use im::HashSet; @@ -117,7 +119,7 @@ pub(super) struct Loop { back_edge_start: BasicBlockId, /// All the blocks contained within the loop, including `header` and `back_edge_start`. - pub(super) blocks: HashSet, + pub(super) blocks: BTreeSet, } pub(super) struct Loops { @@ -238,7 +240,7 @@ impl Loop { back_edge_start: BasicBlockId, cfg: &ControlFlowGraph, ) -> Self { - let mut blocks = HashSet::default(); + let mut blocks = BTreeSet::default(); blocks.insert(header); let mut insert = |block, stack: &mut Vec| { 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 c77fe7513a1..bd65fc33377 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -1,5 +1,4 @@ use std::fmt::Display; -use std::sync::atomic::{AtomicU32, Ordering}; use acvm::acir::AcirField; use acvm::FieldElement; @@ -154,6 +153,7 @@ impl StatementKind { r#type, expression, comptime: false, + is_global_let: false, attributes, }) } @@ -562,6 +562,7 @@ pub struct LetStatement { // True if this should only be run during compile-time pub comptime: bool, + pub is_global_let: bool, } #[derive(Debug, PartialEq, Eq, Clone)] @@ -839,10 +840,9 @@ impl ForRange { block: Expression, for_loop_span: Span, ) -> Statement { - /// Counter used to generate unique names when desugaring - /// code in the parser requires the creation of fresh variables. - /// The parser is stateless so this is a static global instead. - static UNIQUE_NAME_COUNTER: AtomicU32 = AtomicU32::new(0); + // Counter used to generate unique names when desugaring + // code in the parser requires the creation of fresh variables. + let mut unique_name_counter: u32 = 0; match self { ForRange::Range(..) => { @@ -853,7 +853,8 @@ impl ForRange { let start_range = ExpressionKind::integer(FieldElement::zero()); let start_range = Expression::new(start_range, array_span); - let next_unique_id = UNIQUE_NAME_COUNTER.fetch_add(1, Ordering::Relaxed); + let next_unique_id = unique_name_counter; + unique_name_counter += 1; let array_name = format!("$i{next_unique_id}"); let array_span = array.span; let array_ident = Ident::new(array_name, array_span); @@ -886,7 +887,7 @@ impl ForRange { })); let end_range = Expression::new(end_range, array_span); - let next_unique_id = UNIQUE_NAME_COUNTER.fetch_add(1, Ordering::Relaxed); + let next_unique_id = unique_name_counter; let index_name = format!("$i{next_unique_id}"); let fresh_identifier = Ident::new(index_name.clone(), array_span); 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 f05fc721581..c9c2cdcfea1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -523,7 +523,9 @@ pub fn build_debug_crate_file() -> String { __debug_var_assign_oracle(var_id, value); } pub fn __debug_var_assign(var_id: u32, value: T) { - unsafe {{ + unsafe { + //@safety: debug context + { __debug_var_assign_inner(var_id, value); }} } @@ -534,7 +536,9 @@ pub fn build_debug_crate_file() -> String { __debug_var_drop_oracle(var_id); } pub fn __debug_var_drop(var_id: u32) { - unsafe {{ + unsafe { + //@safety: debug context + { __debug_var_drop_inner(var_id); }} } @@ -545,7 +549,9 @@ pub fn build_debug_crate_file() -> String { __debug_fn_enter_oracle(fn_id); } pub fn __debug_fn_enter(fn_id: u32) { - unsafe {{ + unsafe { + //@safety: debug context + { __debug_fn_enter_inner(fn_id); }} } @@ -556,7 +562,9 @@ pub fn build_debug_crate_file() -> String { __debug_fn_exit_oracle(fn_id); } pub fn __debug_fn_exit(fn_id: u32) { - unsafe {{ + unsafe { + //@safety: debug context + { __debug_fn_exit_inner(fn_id); }} } @@ -567,7 +575,9 @@ pub fn build_debug_crate_file() -> String { __debug_dereference_assign_oracle(var_id, value); } pub fn __debug_dereference_assign(var_id: u32, value: T) { - unsafe {{ + unsafe { + //@safety: debug context + { __debug_dereference_assign_inner(var_id, value); }} } @@ -594,6 +604,7 @@ pub fn build_debug_crate_file() -> String { }} pub fn __debug_member_assign_{n}(var_id: u32, value: T, {var_sig}) {{ unsafe {{ + //@safety: debug context __debug_inner_member_assign_{n}(var_id, value, {vars}); }} }} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs index b4ea06f1030..c24cc1a9c86 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -32,7 +32,7 @@ use crate::{ Kind, QuotedType, Shared, StructType, Type, }; -use super::{Elaborator, LambdaContext}; +use super::{Elaborator, LambdaContext, UnsafeBlockStatus}; impl<'context> Elaborator<'context> { pub(crate) fn elaborate_expression(&mut self, expr: Expression) -> (ExprId, Type) { @@ -59,7 +59,7 @@ impl<'context> Elaborator<'context> { return self.elaborate_comptime_block(comptime, expr.span) } ExpressionKind::Unsafe(block_expression, _) => { - self.elaborate_unsafe_block(block_expression) + self.elaborate_unsafe_block(block_expression, expr.span) } ExpressionKind::Resolved(id) => return (id, self.interner.id_type(id)), ExpressionKind::Interned(id) => { @@ -140,15 +140,36 @@ impl<'context> Elaborator<'context> { (HirBlockExpression { statements }, block_type) } - fn elaborate_unsafe_block(&mut self, block: BlockExpression) -> (HirExpression, Type) { + fn elaborate_unsafe_block( + &mut self, + block: BlockExpression, + span: Span, + ) -> (HirExpression, Type) { // Before entering the block we cache the old value of `in_unsafe_block` so it can be restored. - let old_in_unsafe_block = self.in_unsafe_block; - self.in_unsafe_block = true; + let old_in_unsafe_block = self.unsafe_block_status; + let is_nested_unsafe_block = + !matches!(old_in_unsafe_block, UnsafeBlockStatus::NotInUnsafeBlock); + if is_nested_unsafe_block { + let span = Span::from(span.start()..span.start() + 6); // Only highlight the `unsafe` keyword + self.push_err(TypeCheckError::NestedUnsafeBlock { span }); + } + + self.unsafe_block_status = UnsafeBlockStatus::InUnsafeBlockWithoutUnconstrainedCalls; let (hir_block_expression, typ) = self.elaborate_block_expression(block); - // Finally, we restore the original value of `self.in_unsafe_block`. - self.in_unsafe_block = old_in_unsafe_block; + if let UnsafeBlockStatus::InUnsafeBlockWithoutUnconstrainedCalls = self.unsafe_block_status + { + let span = Span::from(span.start()..span.start() + 6); // Only highlight the `unsafe` keyword + self.push_err(TypeCheckError::UnnecessaryUnsafeBlock { span }); + } + + // Finally, we restore the original value of `self.in_unsafe_block`, + // but only if this isn't a nested unsafe block (that way if we found an unconstrained call + // for this unsafe block we'll also consider the outer one as finding one, and we don't double error) + if !is_nested_unsafe_block { + self.unsafe_block_status = old_in_unsafe_block; + } (HirExpression::Unsafe(hir_block_expression), typ) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs index fe1d1e38e1a..593ea6b20e8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/mod.rs @@ -4,8 +4,8 @@ use std::{ }; use crate::{ - ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, usage_tracker::UsageTracker, - StructField, StructType, TypeBindings, + ast::ItemVisibility, hir_def::traits::ResolvedTraitBound, node_interner::GlobalValue, + usage_tracker::UsageTracker, StructField, StructType, TypeBindings, }; use crate::{ ast::{ @@ -79,6 +79,16 @@ pub struct LambdaContext { pub scope_index: usize, } +/// Determines whether we are in an unsafe block and, if so, whether +/// any unconstrained calls were found in it (because if not we'll warn +/// that the unsafe block is not needed). +#[derive(Copy, Clone)] +enum UnsafeBlockStatus { + NotInUnsafeBlock, + InUnsafeBlockWithoutUnconstrainedCalls, + InUnsafeBlockWithConstrainedCalls, +} + pub struct Elaborator<'context> { scopes: ScopeForest, @@ -90,7 +100,7 @@ pub struct Elaborator<'context> { pub(crate) file: FileId, - in_unsafe_block: bool, + unsafe_block_status: UnsafeBlockStatus, nested_loops: usize, /// Contains a mapping of the current struct or functions's generics to @@ -202,7 +212,7 @@ impl<'context> Elaborator<'context> { def_maps, usage_tracker, file: FileId::dummy(), - in_unsafe_block: false, + unsafe_block_status: UnsafeBlockStatus::NotInUnsafeBlock, nested_loops: 0, generics: Vec::new(), lambda_stack: Vec::new(), @@ -1652,20 +1662,13 @@ impl<'context> Elaborator<'context> { self.push_err(ResolverError::MutableGlobal { span }); } - let comptime = let_stmt.comptime; - - let (let_statement, _typ) = if comptime { - self.elaborate_in_comptime_context(|this| this.elaborate_let(let_stmt, Some(global_id))) - } else { - self.elaborate_let(let_stmt, Some(global_id)) - }; + let (let_statement, _typ) = self + .elaborate_in_comptime_context(|this| this.elaborate_let(let_stmt, Some(global_id))); let statement_id = self.interner.get_global(global_id).let_statement; self.interner.replace_statement(statement_id, let_statement); - if comptime { - self.elaborate_comptime_global(global_id); - } + self.elaborate_comptime_global(global_id); if let Some(name) = name { self.interner.register_global(global_id, name, global.visibility, self.module_id()); @@ -1696,7 +1699,17 @@ impl<'context> Elaborator<'context> { self.debug_comptime(location, |interner| value.display(interner).to_string()); - self.interner.get_global_mut(global_id).value = Some(value); + self.interner.get_global_mut(global_id).value = GlobalValue::Resolved(value); + } + } + + /// If the given global is unresolved, elaborate it and return true + fn elaborate_global_if_unresolved(&mut self, global_id: &GlobalId) -> bool { + if let Some(global) = self.unresolved_globals.remove(global_id) { + self.elaborate_global(global); + true + } else { + false } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/path_resolution.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/path_resolution.rs index a68991becb7..51673342fef 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/path_resolution.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/path_resolution.rs @@ -9,7 +9,7 @@ use crate::hir::resolution::visibility::item_in_module_is_visible; use crate::locations::ReferencesTracker; use crate::node_interner::{FuncId, GlobalId, StructId, TraitId, TypeAliasId}; -use crate::Type; +use crate::{Shared, Type, TypeAlias}; use super::types::SELF_TYPE_NAME; use super::Elaborator; @@ -218,18 +218,8 @@ impl<'context> Elaborator<'context> { ), ModuleDefId::TypeAliasId(id) => { let type_alias = self.interner.get_type_alias(id); - let type_alias = type_alias.borrow(); - - let module_id = match &type_alias.typ { - Type::Struct(struct_id, _generics) => struct_id.borrow().id.module_id(), - Type::Error => { - return Err(PathResolutionError::Unresolved(last_ident.clone())); - } - _ => { - // For now we only allow type aliases that point to structs. - // The more general case is captured here: https://github.com/noir-lang/noir/issues/6398 - panic!("Type alias in path not pointing to struct not yet supported") - } + let Some(module_id) = get_type_alias_module_def_id(&type_alias) else { + return Err(PathResolutionError::Unresolved(last_ident.clone())); }; ( @@ -345,3 +335,18 @@ fn merge_intermediate_path_resolution_item_with_module_def_id( }, } } + +fn get_type_alias_module_def_id(type_alias: &Shared) -> Option { + let type_alias = type_alias.borrow(); + + match &type_alias.typ { + Type::Struct(struct_id, _generics) => Some(struct_id.borrow().id.module_id()), + Type::Alias(type_alias, _generics) => get_type_alias_module_def_id(type_alias), + Type::Error => None, + _ => { + // For now we only allow type aliases that point to structs. + // The more general case is captured here: https://github.com/noir-lang/noir/issues/6398 + panic!("Type alias in path not pointing to struct not yet supported") + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs index 3fbdadbbee8..133473219f6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -604,17 +604,11 @@ impl<'context> Elaborator<'context> { alias_generics }; - // Now instantiate the underlying struct with those generics, the struct might + // Now instantiate the underlying struct or alias with those generics, the struct might // have more generics than those in the alias, like in this example: // // type Alias = Struct; - let typ = type_alias.get_type(&generics); - let Type::Struct(_, generics) = typ else { - // See https://github.com/noir-lang/noir/issues/6398 - panic!("Expected type alias to point to struct") - }; - - generics + get_type_alias_generics(&type_alias, &generics) } PathResolutionItem::TraitFunction(trait_id, Some(generics), _func_id) => { let trait_ = self.interner.get_trait(trait_id); @@ -666,9 +660,7 @@ impl<'context> Elaborator<'context> { self.interner.add_function_reference(func_id, hir_ident.location); } DefinitionKind::Global(global_id) => { - if let Some(global) = self.unresolved_globals.remove(&global_id) { - self.elaborate_global(global); - } + self.elaborate_global_if_unresolved(&global_id); if let Some(current_item) = self.current_item { self.interner.add_global_dependency(current_item, global_id); } @@ -882,3 +874,14 @@ impl<'context> Elaborator<'context> { (id, typ) } } + +fn get_type_alias_generics(type_alias: &TypeAlias, generics: &[Type]) -> Vec { + let typ = type_alias.get_type(generics); + match typ { + Type::Struct(_, generics) => generics, + Type::Alias(type_alias, generics) => { + get_type_alias_generics(&type_alias.borrow(), &generics) + } + _ => panic!("Expected type alias to point to struct or alias"), + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs index 93009f49071..8a46d85d563 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/statements.rs @@ -76,6 +76,7 @@ impl<'context> Elaborator<'context> { ) -> (HirStatement, Type) { let expr_span = let_stmt.expression.span; let (expression, expr_type) = self.elaborate_expression(let_stmt.expression); + let type_contains_unspecified = let_stmt.r#type.contains_unspecified(); let annotated_type = self.resolve_inferred_type(let_stmt.r#type); @@ -123,7 +124,9 @@ impl<'context> Elaborator<'context> { let attributes = let_stmt.attributes; let comptime = let_stmt.comptime; - let let_ = HirLetStatement { pattern, r#type, expression, attributes, comptime }; + let is_global_let = let_stmt.is_global_let; + let let_ = + HirLetStatement::new(pattern, r#type, expression, attributes, comptime, is_global_let); (HirStatement::Let(let_), Type::Unit) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs index 2e4809f3511..550ee41fbd4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/types.rs @@ -1,6 +1,5 @@ use std::{borrow::Cow, rc::Rc}; -use acvm::acir::AcirField; use iter_extended::vecmap; use noirc_errors::{Location, Span}; use rustc_hash::FxHashMap as HashMap; @@ -12,7 +11,6 @@ use crate::{ UnresolvedTypeData, UnresolvedTypeExpression, WILDCARD_TYPE, }, hir::{ - comptime::{Interpreter, Value}, def_collector::dc_crate::CompilationError, resolution::{errors::ResolverError, import::PathResolutionError}, type_check::{ @@ -30,14 +28,14 @@ use crate::{ traits::{NamedType, ResolvedTraitBound, Trait, TraitConstraint}, }, node_interner::{ - DefinitionKind, DependencyId, ExprId, GlobalId, ImplSearchErrorKind, NodeInterner, TraitId, + DependencyId, ExprId, GlobalValue, ImplSearchErrorKind, NodeInterner, TraitId, TraitImplKind, TraitMethodId, }, token::SecondaryAttribute, Generics, Kind, ResolvedGeneric, Type, TypeBinding, TypeBindings, UnificationError, }; -use super::{lints, path_resolution::PathResolutionItem, Elaborator}; +use super::{lints, path_resolution::PathResolutionItem, Elaborator, UnsafeBlockStatus}; pub const SELF_TYPE_NAME: &str = "Self"; @@ -415,17 +413,41 @@ impl<'context> Elaborator<'context> { .map(|let_statement| Kind::numeric(let_statement.r#type)) .unwrap_or(Kind::u32()); - // TODO(https://github.com/noir-lang/noir/issues/6238): - // support non-u32 generics here - if !kind.unifies(&Kind::u32()) { - let error = TypeCheckError::EvaluatedGlobalIsntU32 { - expected_kind: Kind::u32().to_string(), - expr_kind: kind.to_string(), - expr_span: path.span(), - }; - self.push_err(error); - } - Some(Type::Constant(self.eval_global_as_array_length(id, path).into(), kind)) + let Some(stmt) = self.interner.get_global_let_statement(id) else { + if self.elaborate_global_if_unresolved(&id) { + return self.lookup_generic_or_global_type(path); + } else { + let path = path.clone(); + self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); + return None; + } + }; + + let rhs = stmt.expression; + let span = self.interner.expr_span(&rhs); + + let GlobalValue::Resolved(global_value) = &self.interner.get_global(id).value + else { + self.push_err(ResolverError::UnevaluatedGlobalType { span }); + return None; + }; + + let Some(global_value) = global_value.to_field_element() else { + let global_value = global_value.clone(); + if global_value.is_integral() { + self.push_err(ResolverError::NegativeGlobalType { span, global_value }); + } else { + self.push_err(ResolverError::NonIntegralGlobalType { span, global_value }); + } + return None; + }; + + let Ok(global_value) = kind.ensure_value_fits(global_value, span) else { + self.push_err(ResolverError::GlobalLargerThanKind { span, global_value, kind }); + return None; + }; + + Some(Type::Constant(global_value, kind)) } _ => None, } @@ -633,31 +655,6 @@ impl<'context> Elaborator<'context> { .or_else(|| self.resolve_trait_method_by_named_generic(path)) } - fn eval_global_as_array_length(&mut self, global_id: GlobalId, path: &Path) -> u32 { - let Some(stmt) = self.interner.get_global_let_statement(global_id) else { - if let Some(global) = self.unresolved_globals.remove(&global_id) { - self.elaborate_global(global); - return self.eval_global_as_array_length(global_id, path); - } else { - let path = path.clone(); - self.push_err(ResolverError::NoSuchNumericTypeVariable { path }); - return 0; - } - }; - - let length = stmt.expression; - let span = self.interner.expr_span(&length); - let result = try_eval_array_length_id(self.interner, length, span); - - match result.map(|length| length.try_into()) { - Ok(Ok(length_value)) => return length_value, - Ok(Err(_cast_err)) => self.push_err(ResolverError::IntegerTooLarge { span }), - Err(Some(error)) => self.push_err(error), - Err(None) => (), - } - 0 - } - pub(super) fn unify( &mut self, actual: &Type, @@ -1486,8 +1483,14 @@ impl<'context> Elaborator<'context> { func_type_is_unconstrained || self.is_unconstrained_call(call.func); let crossing_runtime_boundary = is_current_func_constrained && is_unconstrained_call; if crossing_runtime_boundary { - if !self.in_unsafe_block { - self.push_err(TypeCheckError::Unsafe { span }); + match self.unsafe_block_status { + UnsafeBlockStatus::NotInUnsafeBlock => { + self.push_err(TypeCheckError::Unsafe { span }); + } + UnsafeBlockStatus::InUnsafeBlockWithoutUnconstrainedCalls => { + self.unsafe_block_status = UnsafeBlockStatus::InUnsafeBlockWithConstrainedCalls; + } + UnsafeBlockStatus::InUnsafeBlockWithConstrainedCalls => (), } if let Some(called_func_id) = self.interner.lookup_function_from_expr(&call.func) { @@ -1834,88 +1837,6 @@ fn bind_generic(param: &ResolvedGeneric, arg: &Type, bindings: &mut TypeBindings } } -pub fn try_eval_array_length_id( - interner: &NodeInterner, - rhs: ExprId, - span: Span, -) -> Result> { - // Arbitrary amount of recursive calls to try before giving up - let fuel = 100; - try_eval_array_length_id_with_fuel(interner, rhs, span, fuel) -} - -fn try_eval_array_length_id_with_fuel( - interner: &NodeInterner, - rhs: ExprId, - span: Span, - fuel: u32, -) -> Result> { - if fuel == 0 { - // If we reach here, it is likely from evaluating cyclic globals. We expect an error to - // be issued for them after name resolution so issue no error now. - return Err(None); - } - - match interner.expression(&rhs) { - HirExpression::Literal(HirLiteral::Integer(int, false)) => { - int.try_into_u128().ok_or(Some(ResolverError::IntegerTooLarge { span })) - } - HirExpression::Ident(ident, _) => { - if let Some(definition) = interner.try_definition(ident.id) { - match definition.kind { - DefinitionKind::Global(global_id) => { - let let_statement = interner.get_global_let_statement(global_id); - if let Some(let_statement) = let_statement { - let expression = let_statement.expression; - try_eval_array_length_id_with_fuel(interner, expression, span, fuel - 1) - } else { - Err(Some(ResolverError::InvalidArrayLengthExpr { span })) - } - } - _ => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), - } - } else { - Err(Some(ResolverError::InvalidArrayLengthExpr { span })) - } - } - HirExpression::Infix(infix) => { - let lhs = try_eval_array_length_id_with_fuel(interner, infix.lhs, span, fuel - 1)?; - let rhs = try_eval_array_length_id_with_fuel(interner, infix.rhs, span, fuel - 1)?; - - match infix.operator.kind { - BinaryOpKind::Add => Ok(lhs + rhs), - BinaryOpKind::Subtract => Ok(lhs - rhs), - BinaryOpKind::Multiply => Ok(lhs * rhs), - BinaryOpKind::Divide => Ok(lhs / rhs), - BinaryOpKind::Equal => Ok((lhs == rhs) as u128), - BinaryOpKind::NotEqual => Ok((lhs != rhs) as u128), - BinaryOpKind::Less => Ok((lhs < rhs) as u128), - BinaryOpKind::LessEqual => Ok((lhs <= rhs) as u128), - BinaryOpKind::Greater => Ok((lhs > rhs) as u128), - BinaryOpKind::GreaterEqual => Ok((lhs >= rhs) as u128), - BinaryOpKind::And => Ok(lhs & rhs), - BinaryOpKind::Or => Ok(lhs | rhs), - BinaryOpKind::Xor => Ok(lhs ^ rhs), - BinaryOpKind::ShiftRight => Ok(lhs >> rhs), - BinaryOpKind::ShiftLeft => Ok(lhs << rhs), - BinaryOpKind::Modulo => Ok(lhs % rhs), - } - } - HirExpression::Cast(cast) => { - let lhs = try_eval_array_length_id_with_fuel(interner, cast.lhs, span, fuel - 1)?; - let lhs_value = Value::Field(lhs.into()); - let evaluated_value = - Interpreter::evaluate_cast_one_step(&cast, rhs, lhs_value, interner) - .map_err(|error| Some(ResolverError::ArrayLengthInterpreter { error }))?; - - evaluated_value - .to_u128() - .ok_or_else(|| Some(ResolverError::InvalidArrayLengthExpr { span })) - } - _other => Err(Some(ResolverError::InvalidArrayLengthExpr { span })), - } -} - /// Gives an error if a user tries to create a mutable reference /// to an immutable variable. fn verify_mutable_reference(interner: &NodeInterner, rhs: ExprId) -> Result<(), ResolverError> { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs index 3df20b39209..6ff918328a1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -243,6 +243,9 @@ pub enum InterpreterError { CannotInterpretFormatStringWithErrors { location: Location, }, + GlobalsDependencyCycle { + location: Location, + }, // These cases are not errors, they are just used to prevent us from running more code // until the loop can be resumed properly. These cases will never be displayed to users. @@ -319,7 +322,8 @@ impl InterpreterError { | InterpreterError::CannotResolveExpression { location, .. } | InterpreterError::CannotSetFunctionBody { location, .. } | InterpreterError::UnknownArrayLength { location, .. } - | InterpreterError::CannotInterpretFormatStringWithErrors { location } => *location, + | InterpreterError::CannotInterpretFormatStringWithErrors { location } + | InterpreterError::GlobalsDependencyCycle { location } => *location, InterpreterError::FailedToParseMacro { error, file, .. } => { Location::new(error.span(), *file) @@ -674,6 +678,11 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { "Some of the variables to interpolate could not be evaluated".to_string(); CustomDiagnostic::simple_error(msg, secondary, location.span) } + InterpreterError::GlobalsDependencyCycle { location } => { + let msg = "This global recursively depends on itself".to_string(); + let secondary = String::new(); + CustomDiagnostic::simple_error(msg, secondary, location.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 index dfa55a9d79b..e9e37243e07 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter.rs @@ -20,6 +20,7 @@ use crate::monomorphization::{ perform_impl_bindings, perform_instantiation_bindings, resolve_trait_method, undo_instantiation_bindings, }; +use crate::node_interner::GlobalValue; use crate::token::{FmtStrFragment, Tokens}; use crate::TypeVariable; use crate::{ @@ -568,26 +569,39 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { DefinitionKind::Local(_) => self.lookup(&ident), DefinitionKind::Global(global_id) => { // Avoid resetting the value if it is already known - if let Some(value) = &self.elaborator.interner.get_global(*global_id).value { - Ok(value.clone()) - } else { - let global_id = *global_id; - let crate_of_global = self.elaborator.interner.get_global(global_id).crate_id; - let let_ = - self.elaborator.interner.get_global_let_statement(global_id).ok_or_else( - || { + let global_id = *global_id; + let global_info = self.elaborator.interner.get_global(global_id); + let global_crate_id = global_info.crate_id; + match &global_info.value { + GlobalValue::Resolved(value) => Ok(value.clone()), + GlobalValue::Resolving => { + // Note that the error we issue here isn't very informative (it doesn't include the actual cycle) + // but the general dependency cycle detector will give a better error later on during compilation. + let location = self.elaborator.interner.expr_location(&id); + Err(InterpreterError::GlobalsDependencyCycle { location }) + } + GlobalValue::Unresolved => { + let let_ = self + .elaborator + .interner + .get_global_let_statement(global_id) + .ok_or_else(|| { let location = self.elaborator.interner.expr_location(&id); InterpreterError::VariableNotInScope { location } - }, - )?; + })?; - if let_.comptime || crate_of_global != self.crate_id { - self.evaluate_let(let_.clone())?; - } + self.elaborator.interner.get_global_mut(global_id).value = + GlobalValue::Resolving; + + if let_.runs_comptime() || global_crate_id != self.crate_id { + self.evaluate_let(let_.clone())?; + } - let value = self.lookup(&ident)?; - self.elaborator.interner.get_global_mut(global_id).value = Some(value.clone()); - Ok(value) + let value = self.lookup(&ident)?; + self.elaborator.interner.get_global_mut(global_id).value = + GlobalValue::Resolved(value.clone()); + Ok(value) + } } } DefinitionKind::NumericGeneric(type_variable, numeric_typ) => { @@ -1380,13 +1394,19 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { fn evaluate_cast(&mut self, cast: &HirCastExpression, id: ExprId) -> IResult { let evaluated_lhs = self.evaluate(cast.lhs)?; - Self::evaluate_cast_one_step(cast, id, evaluated_lhs, self.elaborator.interner) + let location = self.elaborator.interner.expr_location(&id); + Self::evaluate_cast_one_step( + &cast.r#type, + location, + evaluated_lhs, + self.elaborator.interner, + ) } /// evaluate_cast without recursion pub fn evaluate_cast_one_step( - cast: &HirCastExpression, - id: ExprId, + typ: &Type, + location: Location, evaluated_lhs: Value, interner: &NodeInterner, ) -> IResult { @@ -1417,7 +1437,6 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { (if value { FieldElement::one() } else { FieldElement::zero() }, false) } value => { - let location = interner.expr_location(&id); let typ = value.get_type().into_owned(); return Err(InterpreterError::NonNumericCasted { typ, location }); } @@ -1434,7 +1453,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } // Now actually cast the lhs, bit casting and wrapping as necessary - match cast.r#type.follow_bindings() { + match typ.follow_bindings() { Type::FieldElement => { if lhs_is_negative { lhs = FieldElement::zero() - lhs; @@ -1443,8 +1462,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } Type::Integer(sign, bit_size) => match (sign, bit_size) { (Signedness::Unsigned, IntegerBitSize::One) => { - let location = interner.expr_location(&id); - Err(InterpreterError::TypeUnsupported { typ: cast.r#type.clone(), location }) + Err(InterpreterError::TypeUnsupported { typ: typ.clone(), location }) } (Signedness::Unsigned, IntegerBitSize::Eight) => cast_to_int!(lhs, to_u128, u8, U8), (Signedness::Unsigned, IntegerBitSize::Sixteen) => { @@ -1457,8 +1475,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { cast_to_int!(lhs, to_u128, u64, U64) } (Signedness::Signed, IntegerBitSize::One) => { - let location = interner.expr_location(&id); - Err(InterpreterError::TypeUnsupported { typ: cast.r#type.clone(), location }) + Err(InterpreterError::TypeUnsupported { typ: typ.clone(), location }) } (Signedness::Signed, IntegerBitSize::Eight) => cast_to_int!(lhs, to_i128, i8, I8), (Signedness::Signed, IntegerBitSize::Sixteen) => { @@ -1472,10 +1489,7 @@ impl<'local, 'interner> Interpreter<'local, 'interner> { } }, Type::Bool => Ok(Value::Bool(!lhs.is_zero() || lhs_is_negative)), - typ => { - let location = interner.expr_location(&id); - Err(InterpreterError::CastToNonNumericType { typ, location }) - } + typ => Err(InterpreterError::CastToNonNumericType { typ, location }), } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 3d8ccf78926..2e672e3d0d5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -64,6 +64,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "array_len" => array_len(interner, arguments, location), "array_refcount" => Ok(Value::U32(0)), "assert_constant" => Ok(Value::Bool(true)), + "as_field" => as_field(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), "ctstring_eq" => ctstring_eq(arguments, location), "ctstring_hash" => ctstring_hash(arguments, location), @@ -116,6 +117,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "field_less_than" => field_less_than(arguments, location), "fmtstr_as_ctstring" => fmtstr_as_ctstring(interner, arguments, location), "fmtstr_quoted_contents" => fmtstr_quoted_contents(interner, arguments, location), + "from_field" => from_field(interner, arguments, return_type, location), "fresh_type_variable" => fresh_type_variable(interner), "function_def_add_attribute" => function_def_add_attribute(self, arguments, location), "function_def_body" => function_def_body(interner, arguments, location), @@ -290,6 +292,16 @@ fn array_as_str_unchecked( Ok(Value::String(Rc::new(string))) } +// fn as_field(x: T) -> Field {} +fn as_field( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let (value, value_location) = check_one_argument(arguments, location)?; + Interpreter::evaluate_cast_one_step(&Type::FieldElement, value_location, value, interner) +} + fn as_slice( interner: &NodeInterner, arguments: Vec<(Value, Location)>, @@ -2224,6 +2236,17 @@ fn fmtstr_quoted_contents( Ok(Value::Quoted(Rc::new(tokens))) } +// fn from_field(x: Field) -> T {} +fn from_field( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let (value, value_location) = check_one_argument(arguments, location)?; + Interpreter::evaluate_cast_one_step(&return_type, value_location, value, interner) +} + // fn fresh_type_variable() -> Type fn fresh_type_variable(interner: &NodeInterner) -> IResult { Ok(Value::Type(interner.next_type_variable_with_kind(Kind::Any))) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs index 945fb45026d..e5c55e2b0be 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, rc::Rc, vec}; -use acvm::{AcirField, FieldElement}; +use acvm::FieldElement; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::{Location, Span}; @@ -409,6 +409,9 @@ impl Value { Value::Pointer(element, true) => { return element.unwrap_or_clone().into_hir_expression(interner, location); } + Value::Closure(hir_lambda, _args, _typ, _opt_func_id, _module_id) => { + HirExpression::Lambda(hir_lambda) + } Value::TypedExpr(TypedExpr::StmtId(..)) | Value::Expr(..) | Value::Pointer(..) @@ -420,7 +423,6 @@ impl Value { | Value::Zeroed(_) | Value::Type(_) | Value::UnresolvedType(_) - | Value::Closure(..) | Value::ModuleDefinition(_) => { let typ = self.get_type().into_owned(); let value = self.display(interner).to_string(); @@ -502,19 +504,28 @@ impl Value { Ok(vec![token]) } - /// Converts any unsigned `Value` into a `u128`. - /// Returns `None` for negative integers. - pub(crate) fn to_u128(&self) -> Option { + /// Returns false for non-integral `Value`s. + pub(crate) fn is_integral(&self) -> bool { + use Value::*; + matches!( + self, + Field(_) | I8(_) | I16(_) | I32(_) | I64(_) | U8(_) | U16(_) | U32(_) | U64(_) + ) + } + + /// Converts any non-negative `Value` into a `FieldElement`. + /// Returns `None` for negative integers and non-integral `Value`s. + pub(crate) fn to_field_element(&self) -> Option { match self { - Self::Field(value) => Some(value.to_u128()), - Self::I8(value) => (*value >= 0).then_some(*value as u128), - Self::I16(value) => (*value >= 0).then_some(*value as u128), - Self::I32(value) => (*value >= 0).then_some(*value as u128), - Self::I64(value) => (*value >= 0).then_some(*value as u128), - Self::U8(value) => Some(*value as u128), - Self::U16(value) => Some(*value as u128), - Self::U32(value) => Some(*value as u128), - Self::U64(value) => Some(*value as u128), + Self::Field(value) => Some(*value), + Self::I8(value) => (*value >= 0).then_some((*value as u128).into()), + Self::I16(value) => (*value >= 0).then_some((*value as u128).into()), + Self::I32(value) => (*value >= 0).then_some((*value as u128).into()), + Self::I64(value) => (*value >= 0).then_some((*value as u128).into()), + Self::U8(value) => Some((*value as u128).into()), + Self::U16(value) => Some((*value as u128).into()), + Self::U32(value) => Some((*value as u128).into()), + Self::U64(value) => Some((*value as u128).into()), _ => None, } } 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 774836f8992..8817904c6f5 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 @@ -5,10 +5,13 @@ use thiserror::Error; use crate::{ ast::{Ident, UnsupportedNumericGenericType}, - hir::{comptime::InterpreterError, type_check::TypeCheckError}, + hir::{ + comptime::{InterpreterError, Value}, + type_check::TypeCheckError, + }, parser::ParserError, usage_tracker::UnusedItem, - Type, + Kind, Type, }; use super::import::PathResolutionError; @@ -101,6 +104,14 @@ pub enum ResolverError { MutableGlobal { span: Span }, #[error("Globals must have a specified type")] UnspecifiedGlobalType { span: Span, expected_type: Type }, + #[error("Global failed to evaluate")] + UnevaluatedGlobalType { span: Span }, + #[error("Globals used in a type position must be non-negative")] + NegativeGlobalType { span: Span, global_value: Value }, + #[error("Globals used in a type position must be integers")] + NonIntegralGlobalType { span: Span, global_value: Value }, + #[error("Global value `{global_value}` is larger than its kind's maximum value")] + GlobalLargerThanKind { span: Span, global_value: FieldElement, kind: Kind }, #[error("Self-referential structs are not supported")] SelfReferentialStruct { span: Span }, #[error("#[no_predicates] attribute is only allowed on constrained functions")] @@ -443,6 +454,34 @@ impl<'a> From<&'a ResolverError> for Diagnostic { *span, ) }, + ResolverError::UnevaluatedGlobalType { span } => { + Diagnostic::simple_error( + "Global failed to evaluate".to_string(), + String::new(), + *span, + ) + } + ResolverError::NegativeGlobalType { span, global_value } => { + Diagnostic::simple_error( + "Globals used in a type position must be non-negative".to_string(), + format!("But found value `{global_value:?}`"), + *span, + ) + } + ResolverError::NonIntegralGlobalType { span, global_value } => { + Diagnostic::simple_error( + "Globals used in a type position must be integers".to_string(), + format!("But found value `{global_value:?}`"), + *span, + ) + } + ResolverError::GlobalLargerThanKind { span, global_value, kind } => { + Diagnostic::simple_error( + format!("Global value `{global_value}` is larger than its kind's maximum value"), + format!("Global's kind inferred to be `{kind}`"), + *span, + ) + } ResolverError::SelfReferentialStruct { span } => { Diagnostic::simple_error( "Self-referential structs are not supported".into(), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/scope/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/scope/mod.rs index 1a9087a7408..ebb271b86b7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/scope/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/scope/mod.rs @@ -25,10 +25,10 @@ It's not implemented yet, because nothing has been benched pub struct Scope(pub HashMap); impl Scope { - pub fn find(&mut self, key: &Q) -> Option<&mut V> + pub fn find(&mut self, key: &Q) -> Option<&mut V> where K: std::borrow::Borrow, - Q: std::hash::Hash + Eq, + Q: ?Sized + std::hash::Hash + Eq, { self.0.get_mut(key) } @@ -75,10 +75,10 @@ impl ScopeTree { // Recursively search for a key in the scope tree. // Returns the value if found, along with the index it was found at. - pub fn find(&mut self, key: &Q) -> Option<(&mut V, usize)> + pub fn find(&mut self, key: &Q) -> Option<(&mut V, usize)> where K: std::borrow::Borrow, - Q: std::hash::Hash + Eq, + Q: ?Sized + std::hash::Hash + Eq, { for (i, scope) in self.0.iter_mut().enumerate().rev() { if let Some(value_found) = scope.find(key) { 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 15b8d50c78b..16422e0ef8b 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 @@ -66,9 +66,6 @@ pub enum TypeCheckError { from_value: FieldElement, span: Span, }, - // TODO(https://github.com/noir-lang/noir/issues/6238): implement handling for larger types - #[error("Expected type {expected_kind} when evaluating globals, but found {expr_kind} (this warning may become an error in the future)")] - EvaluatedGlobalIsntU32 { expected_kind: String, expr_kind: String, expr_span: Span }, #[error("Expected {expected:?} found {found:?}")] ArityMisMatch { expected: usize, found: usize, span: Span }, #[error("Return type in a function cannot be public")] @@ -208,6 +205,10 @@ pub enum TypeCheckError { CyclicType { typ: Type, span: Span }, #[error("Type annotations required before indexing this array or slice")] TypeAnnotationsNeededForIndex { span: Span }, + #[error("Unnecessary `unsafe` block")] + UnnecessaryUnsafeBlock { span: Span }, + #[error("Unnecessary `unsafe` block")] + NestedUnsafeBlock { span: Span }, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -275,15 +276,6 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { *span, ) } - // TODO(https://github.com/noir-lang/noir/issues/6238): implement - // handling for larger types - TypeCheckError::EvaluatedGlobalIsntU32 { expected_kind, expr_kind, expr_span } => { - Diagnostic::simple_warning( - format!("Expected type {expected_kind} when evaluating globals, but found {expr_kind} (this warning may become an error in the future)"), - String::new(), - *expr_span, - ) - } TypeCheckError::TraitMethodParameterTypeMismatch { method_name, expected_typ, actual_typ, parameter_index, parameter_span } => { Diagnostic::simple_error( format!("Parameter #{parameter_index} of method `{method_name}` must be of type {expected_typ}, not {actual_typ}"), @@ -529,6 +521,20 @@ impl<'a> From<&'a TypeCheckError> for Diagnostic { *span, ) }, + TypeCheckError::UnnecessaryUnsafeBlock { span } => { + Diagnostic::simple_warning( + "Unnecessary `unsafe` block".into(), + "".into(), + *span, + ) + }, + TypeCheckError::NestedUnsafeBlock { span } => { + Diagnostic::simple_warning( + "Unnecessary `unsafe` block".into(), + "Because it's nested inside another `unsafe` block".into(), + *span, + ) + }, } } } 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 0258cfd8ddb..c42b8230290 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 @@ -31,15 +31,31 @@ pub struct HirLetStatement { pub expression: ExprId, pub attributes: Vec, pub comptime: bool, + pub is_global_let: bool, } impl HirLetStatement { + pub fn new( + pattern: HirPattern, + r#type: Type, + expression: ExprId, + attributes: Vec, + comptime: bool, + is_global_let: bool, + ) -> HirLetStatement { + Self { pattern, r#type, expression, attributes, comptime, is_global_let } + } + pub fn ident(&self) -> HirIdent { match &self.pattern { HirPattern::Identifier(ident) => ident.clone(), _ => panic!("can only fetch hir ident from HirPattern::Identifier"), } } + + pub fn runs_comptime(&self) -> bool { + self.comptime || self.is_global_let + } } #[derive(Debug, Clone)] 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 2c9a44c079d..0ed6399296b 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 @@ -226,6 +226,10 @@ impl Kind { // Kind::Integer unifies with Kind::IntegerOrField (Kind::Integer | Kind::IntegerOrField, Kind::Integer | Kind::IntegerOrField) => true, + // Kind::IntegerOrField unifies with Kind::Numeric(_) + (Kind::IntegerOrField, Kind::Numeric(_typ)) + | (Kind::Numeric(_typ), Kind::IntegerOrField) => true, + // Kind::Numeric unifies along its Type argument (Kind::Numeric(lhs), Kind::Numeric(rhs)) => { let mut bindings = TypeBindings::new(); @@ -255,7 +259,8 @@ impl Kind { match self { Kind::IntegerOrField => Some(Type::default_int_or_field_type()), Kind::Integer => Some(Type::default_int_type()), - Kind::Any | Kind::Normal | Kind::Numeric(..) => None, + Kind::Numeric(typ) => Some(*typ.clone()), + Kind::Any | Kind::Normal => None, } } @@ -267,7 +272,7 @@ impl Kind { } /// Ensure the given value fits in self.integral_maximum_size() - fn ensure_value_fits( + pub(crate) fn ensure_value_fits( &self, value: FieldElement, span: Span, @@ -2210,6 +2215,7 @@ impl Type { Type::NamedGeneric(binding, _) | Type::TypeVariable(binding) => { substitute_binding(binding) } + // Do not substitute_helper fields, it can lead to infinite recursion // and we should not match fields when type checking anyway. Type::Struct(fields, args) => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs index a5c4b2cd772..99799b9a1c0 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs @@ -726,7 +726,7 @@ impl<'a> Lexer<'a> { } fn parse_comment(&mut self, start: u32) -> SpannedTokenResult { - let doc_style = match self.peek_char() { + let mut doc_style = match self.peek_char() { Some('!') => { self.next_char(); Some(DocStyle::Inner) @@ -735,9 +735,17 @@ impl<'a> Lexer<'a> { self.next_char(); Some(DocStyle::Outer) } + Some('@') => Some(DocStyle::Safety), _ => None, }; - let comment = self.eat_while(None, |ch| ch != '\n'); + let mut comment = self.eat_while(None, |ch| ch != '\n'); + if doc_style == Some(DocStyle::Safety) { + if comment.starts_with("@safety") { + comment = comment["@safety".len()..].to_string(); + } else { + doc_style = None; + } + } if !comment.is_ascii() { let span = Span::from(start..self.position); @@ -752,7 +760,7 @@ impl<'a> Lexer<'a> { } fn parse_block_comment(&mut self, start: u32) -> SpannedTokenResult { - let doc_style = match self.peek_char() { + let mut doc_style = match self.peek_char() { Some('!') => { self.next_char(); Some(DocStyle::Inner) @@ -761,6 +769,7 @@ impl<'a> Lexer<'a> { self.next_char(); Some(DocStyle::Outer) } + Some('@') => Some(DocStyle::Safety), _ => None, }; @@ -787,7 +796,13 @@ impl<'a> Lexer<'a> { ch => content.push(ch), } } - + if doc_style == Some(DocStyle::Safety) { + if content.starts_with("@safety") { + content = content["@safety".len()..].to_string(); + } else { + doc_style = None; + } + } if depth == 0 { if !content.is_ascii() { let span = Span::from(start..self.position); 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 f35515045db..ffd755e606f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -345,6 +345,7 @@ impl Display for FmtStrFragment { pub enum DocStyle { Outer, Inner, + Safety, } #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -424,11 +425,13 @@ impl fmt::Display for Token { Token::LineComment(ref s, style) => match style { Some(DocStyle::Inner) => write!(f, "//!{s}"), Some(DocStyle::Outer) => write!(f, "///{s}"), + Some(DocStyle::Safety) => write!(f, "//@safety{s}"), None => write!(f, "//{s}"), }, Token::BlockComment(ref s, style) => match style { Some(DocStyle::Inner) => write!(f, "/*!{s}*/"), Some(DocStyle::Outer) => write!(f, "/**{s}*/"), + Some(DocStyle::Safety) => write!(f, "/*@safety{s}*/"), None => write!(f, "/*{s}*/"), }, Token::Quote(ref stream) => { 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 b31a5744d09..8c07d71de21 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -11,7 +11,7 @@ use crate::ast::{FunctionKind, IntegerBitSize, Signedness, UnaryOp, Visibility}; use crate::hir::comptime::InterpreterError; use crate::hir::type_check::{NoMatchingImplFoundError, TypeCheckError}; -use crate::node_interner::{ExprId, ImplSearchErrorKind}; +use crate::node_interner::{ExprId, GlobalValue, ImplSearchErrorKind}; use crate::token::FmtStrFragment; use crate::{ debug::DebugInstrumenter, @@ -895,7 +895,7 @@ impl<'interner> Monomorphizer<'interner> { DefinitionKind::Global(global_id) => { let global = self.interner.get_global(*global_id); - let expr = if let Some(value) = global.value.clone() { + let expr = if let GlobalValue::Resolved(value) = global.value.clone() { value .into_hir_expression(self.interner, global.location) .map_err(MonomorphizationError::InterpreterError)? @@ -1133,7 +1133,7 @@ impl<'interner> Monomorphizer<'interner> { | HirType::Error | HirType::Quoted(_) => Ok(()), HirType::Constant(_value, kind) => { - if kind.is_error() { + if kind.is_error() || kind.default_type().is_none() { Err(MonomorphizationError::UnknownConstant { location }) } else { Ok(()) @@ -1154,7 +1154,6 @@ impl<'interner> Monomorphizer<'interner> { Ok(()) } - HirType::TypeVariable(ref binding) => { let type_var_kind = match &*binding.borrow() { TypeBinding::Bound(binding) => { 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 6d70ea2fd6d..1df2cd76721 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -414,7 +414,7 @@ pub struct DefinitionId(usize); impl DefinitionId { //dummy id for error reporting pub fn dummy_id() -> DefinitionId { - DefinitionId(std::usize::MAX) + DefinitionId(usize::MAX) } } @@ -425,7 +425,7 @@ pub struct GlobalId(usize); impl GlobalId { // Dummy id for error reporting pub fn dummy_id() -> Self { - GlobalId(std::usize::MAX) + GlobalId(usize::MAX) } } @@ -496,7 +496,7 @@ pub struct TypeAliasId(pub usize); impl TypeAliasId { pub fn dummy_id() -> TypeAliasId { - TypeAliasId(std::usize::MAX) + TypeAliasId(usize::MAX) } } @@ -609,7 +609,14 @@ pub struct GlobalInfo { pub crate_id: CrateId, pub location: Location, pub let_statement: StmtId, - pub value: Option, + pub value: GlobalValue, +} + +#[derive(Debug, Clone)] +pub enum GlobalValue { + Unresolved, + Resolving, + Resolved(comptime::Value), } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -849,6 +856,7 @@ impl NodeInterner { let id = GlobalId(self.globals.len()); let location = Location::new(ident.span(), file); let name = ident.to_string(); + let definition_id = self.push_definition(name, mutable, comptime, DefinitionKind::Global(id), location); @@ -860,7 +868,7 @@ impl NodeInterner { crate_id, let_statement, location, - value: None, + value: GlobalValue::Unresolved, }); self.global_attributes.insert(id, attributes); id @@ -884,6 +892,7 @@ impl NodeInterner { ) -> GlobalId { let statement = self.push_stmt(HirStatement::Error); let span = name.span(); + let id = self .push_global(name, local_id, crate_id, statement, file, attributes, mutable, comptime); self.push_stmt_location(statement, span, file); 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 899928528e6..7cb8731593b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -108,6 +108,8 @@ pub enum ParserErrorReason { WrongNumberOfAttributeArguments { name: String, min: usize, max: usize, found: usize }, #[error("The `deprecated` attribute expects a string argument")] DeprecatedAttributeExpectsAStringArgument, + #[error("Unsafe block must start with a safety comment")] + MissingSafetyComment, } /// Represents a parsing error, or a parsing error in the making. @@ -181,7 +183,11 @@ impl ParserError { } pub fn is_warning(&self) -> bool { - matches!(self.reason(), Some(ParserErrorReason::ExperimentalFeature(_))) + matches!( + self.reason(), + Some(ParserErrorReason::ExperimentalFeature(_)) + | Some(ParserErrorReason::MissingSafetyComment) + ) } } @@ -272,6 +278,11 @@ impl<'a> From<&'a ParserError> for Diagnostic { "Noir doesn't have immutable references, only mutable references".to_string(), error.span, ), + ParserErrorReason::MissingSafetyComment => Diagnostic::simple_warning( + "Missing Safety Comment".into(), + "Unsafe block must start with a safety comment: //@safety".into(), + error.span, + ), other => Diagnostic::simple_error(format!("{other}"), String::new(), error.span), }, None => { 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 fcc58c5d833..9ce288c97fa 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -154,10 +154,7 @@ impl<'a> Parser<'a> { where F: FnOnce(&mut Parser<'a>) -> Option, { - match self.parse_result(parsing_function) { - Ok(item) => item, - Err(_) => None, - } + self.parse_result(parsing_function).unwrap_or_default() } /// Bumps this parser by one token. Returns the token that was previously the "current" token. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs index 526a0c3dd6e..6b059bb0c8c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -8,7 +8,7 @@ use crate::{ MemberAccessExpression, MethodCallExpression, Statement, TypePath, UnaryOp, UnresolvedType, }, parser::{labels::ParsingRuleLabel, parser::parse_many::separated_by_comma, ParserErrorReason}, - token::{Keyword, Token, TokenKind}, + token::{DocStyle, Keyword, SpannedToken, Token, TokenKind}, }; use super::{ @@ -375,6 +375,20 @@ impl<'a> Parser<'a> { return None; } + let next_token = self.next_token.token(); + if matches!( + next_token, + Token::LineComment(_, Some(DocStyle::Safety)) + | Token::BlockComment(_, Some(DocStyle::Safety)) + ) { + //Checks the safety comment is there, and skip it + let span = self.current_token_span; + self.eat_left_brace(); + self.token = SpannedToken::new(Token::LeftBrace, span); + } else { + self.push_error(ParserErrorReason::MissingSafetyComment, self.current_token_span); + } + let start_span = self.current_token_span; if let Some(block) = self.parse_block() { Some(ExpressionKind::Unsafe(block, self.span_since(start_span))) @@ -389,9 +403,7 @@ impl<'a> Parser<'a> { /// /// VariableExpression = Path fn parse_path_expr(&mut self, allow_constructors: bool) -> Option { - let Some(path) = self.parse_path() else { - return None; - }; + let path = self.parse_path()?; if allow_constructors && self.eat_left_brace() { let typ = UnresolvedType::from_path(path); @@ -421,9 +433,7 @@ impl<'a> Parser<'a> { } fn parse_constructor_field(&mut self) -> Option<(Ident, Expression)> { - let Some(ident) = self.eat_ident() else { - return None; - }; + let ident = self.eat_ident()?; Some(if self.eat_colon() { let expression = self.parse_expression_or_error(); @@ -534,9 +544,7 @@ impl<'a> Parser<'a> { /// TypePathExpression = PrimitiveType '::' identifier ( '::' GenericTypeArgs )? fn parse_type_path_expr(&mut self) -> Option { let start_span = self.current_token_span; - let Some(typ) = self.parse_primitive_type() else { - return None; - }; + let typ = self.parse_primitive_type()?; let typ = UnresolvedType { typ, span: self.span_since(start_span) }; self.eat_or_error(Token::DoubleColon); @@ -963,7 +971,8 @@ mod tests { #[test] fn parses_unsafe_expression() { - let src = "unsafe { 1 }"; + let src = "unsafe { //@safety: test + 1 }"; let expr = parse_expression_no_errors(src); let ExpressionKind::Unsafe(block, _) = expr.kind else { panic!("Expected unsafe expression"); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs index 5ece510022a..3a4f6d922a4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs @@ -23,6 +23,7 @@ impl<'a> Parser<'a> { // and throw the error in name resolution. let attributes = self.validate_secondary_attributes(attributes); + let is_global_let = true; let Some(ident) = self.eat_ident() else { self.eat_semicolons(); @@ -35,6 +36,7 @@ impl<'a> Parser<'a> { expression: Expression { kind: ExpressionKind::Error, span: Span::default() }, attributes, comptime, + is_global_let, }; }; @@ -53,7 +55,7 @@ impl<'a> Parser<'a> { self.expected_token(Token::Semicolon); } - LetStatement { pattern, r#type: typ, expression, attributes, comptime } + LetStatement { pattern, r#type: typ, expression, attributes, comptime, is_global_let } } } @@ -105,6 +107,7 @@ mod tests { assert_eq!("foo", name.to_string()); assert!(matches!(let_statement.r#type.typ, UnresolvedTypeData::Unspecified)); assert!(!let_statement.comptime); + assert!(let_statement.is_global_let); assert_eq!(visibility, ItemVisibility::Private); } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs index c4dcab55d73..50779e9ccff 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs @@ -154,9 +154,7 @@ impl<'a> Parser<'a> { /// InternedPattern = interned_pattern fn parse_interned_pattern(&mut self) -> Option { - let Some(token) = self.eat_kind(TokenKind::InternedPattern) else { - return None; - }; + let token = self.eat_kind(TokenKind::InternedPattern)?; match token.into_token() { Token::InternedPattern(pattern) => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs index ff6117c9348..14c8a415a38 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -364,7 +364,14 @@ impl<'a> Parser<'a> { Expression { kind: ExpressionKind::Error, span: self.current_token_span } }; - Some(LetStatement { pattern, r#type, expression, attributes, comptime: false }) + Some(LetStatement { + pattern, + r#type, + expression, + attributes, + comptime: false, + is_global_let: false, + }) } /// ConstrainStatement @@ -373,9 +380,7 @@ impl<'a> Parser<'a> { /// | 'assert_eq' Arguments fn parse_constrain_statement(&mut self) -> Option { let start_span = self.current_token_span; - let Some(kind) = self.parse_constrain_kind() else { - return None; - }; + let kind = self.parse_constrain_kind()?; Some(match kind { ConstrainKind::Assert | ConstrainKind::AssertEq => { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs index 7dd59aedb45..83b04bb157a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs @@ -158,9 +158,7 @@ impl<'a> Parser<'a> { /// ConstantTypeExpression = int fn parse_constant_type_expression(&mut self) -> Option { - let Some(int) = self.eat_int() else { - return None; - }; + let int = self.eat_int()?; Some(UnresolvedTypeExpression::Constant(int, self.previous_token_span)) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs index a753ffb6fd2..8945e6f29f5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs @@ -36,9 +36,7 @@ impl<'a> Parser<'a> { } fn parse_single_where_clause(&mut self) -> Option<(UnresolvedType, Vec)> { - let Some(typ) = self.parse_type() else { - return None; - }; + let typ = self.parse_type()?; self.eat_or_error(Token::Colon); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 8ddf1b571e6..3d908d1aa0c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -1287,11 +1287,15 @@ fn lambda$f1(mut env$l1: (Field)) -> Field { check_rewrite(src, expected_rewrite); } +// TODO(https://github.com/noir-lang/noir/issues/6780): currently failing +// with a stack overflow #[test] +#[ignore] fn deny_cyclic_globals() { let src = r#" global A: u32 = B; global B: u32 = A; + fn main() {} "#; @@ -1991,9 +1995,6 @@ fn numeric_generic_u16_array_size() { )); } -// TODO(https://github.com/noir-lang/noir/issues/6238): -// The EvaluatedGlobalIsntU32 warning is a stopgap -// (originally from https://github.com/noir-lang/noir/issues/6125) #[test] fn numeric_generic_field_larger_than_u32() { let src = r#" @@ -2006,29 +2007,16 @@ fn numeric_generic_field_larger_than_u32() { } "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 2); - assert!(matches!( - errors[0].0, - CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), - )); - assert!(matches!( - errors[1].0, - CompilationError::ResolverError(ResolverError::IntegerTooLarge { .. }) - )); + assert_eq!(errors.len(), 0); } -// TODO(https://github.com/noir-lang/noir/issues/6238): -// The EvaluatedGlobalIsntU32 warning is a stopgap -// (originally from https://github.com/noir-lang/noir/issues/6126) #[test] fn numeric_generic_field_arithmetic_larger_than_u32() { let src = r#" struct Foo {} - impl Foo { - fn size(self) -> Field { - F - } + fn size(_x: Foo) -> Field { + F } // 2^32 - 1 @@ -2039,21 +2027,11 @@ fn numeric_generic_field_arithmetic_larger_than_u32() { } fn main() { - let _ = foo::().size(); + let _ = size(foo::()); } "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 2); - - assert!(matches!( - errors[0].0, - CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), - )); - - assert!(matches!( - errors[1].0, - CompilationError::ResolverError(ResolverError::UnusedVariable { .. }) - )); + assert_eq!(errors.len(), 0); } #[test] @@ -2180,25 +2158,11 @@ fn numeric_generics_type_kind_mismatch() { } "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 3); - - // TODO(https://github.com/noir-lang/noir/issues/6238): - // The EvaluatedGlobalIsntU32 warning is a stopgap + assert_eq!(errors.len(), 1); assert!(matches!( errors[0].0, - CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), - )); - - assert!(matches!( - errors[1].0, CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }), )); - - // TODO(https://github.com/noir-lang/noir/issues/6238): see above - assert!(matches!( - errors[2].0, - CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }), - )); } #[test] @@ -3219,20 +3183,20 @@ fn dont_infer_globals_to_u32_from_type_use() { } "#; - let errors = get_program_errors(src); - assert_eq!(errors.len(), 3); - assert!(matches!( - errors[0].0, - CompilationError::ResolverError(ResolverError::UnspecifiedGlobalType { .. }) - )); - assert!(matches!( - errors[1].0, - CompilationError::ResolverError(ResolverError::UnspecifiedGlobalType { .. }) - )); - assert!(matches!( - errors[2].0, - CompilationError::ResolverError(ResolverError::UnspecifiedGlobalType { .. }) - )); + let mut errors = get_program_errors(src); + assert_eq!(errors.len(), 6); + for (error, _file_id) in errors.drain(0..3) { + assert!(matches!( + error, + CompilationError::ResolverError(ResolverError::UnspecifiedGlobalType { .. }) + )); + } + for (error, _file_id) in errors { + assert!(matches!( + error, + CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }) + )); + } } #[test] @@ -3242,7 +3206,8 @@ fn dont_infer_partial_global_types() { pub global NESTED_ARRAY: [[Field; _]; 3] = [[]; 3]; pub global STR: str<_> = "hi"; pub global NESTED_STR: [str<_>] = &["hi"]; - pub global FMT_STR: fmtstr<_, _> = f"hi {ARRAY}"; + pub global FORMATTED_VALUE: str<5> = "there"; + pub global FMT_STR: fmtstr<_, _> = f"hi {FORMATTED_VALUE}"; pub global TUPLE_WITH_MULTIPLE: ([str<_>], [[Field; _]; 3]) = (&["hi"], [[]; 3]); fn main() { } @@ -3318,13 +3283,9 @@ fn non_u32_as_array_length() { "#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 2); + assert_eq!(errors.len(), 1); assert!(matches!( errors[0].0, - CompilationError::TypeError(TypeCheckError::EvaluatedGlobalIsntU32 { .. }) - )); - assert!(matches!( - errors[1].0, CompilationError::TypeError(TypeCheckError::TypeKindMismatch { .. }) )); } @@ -3894,3 +3855,66 @@ fn disallows_underscore_on_right_hand_side() { assert_eq!(name, "_"); } + +#[test] +fn errors_on_cyclic_globals() { + let src = r#" + pub comptime global A: u32 = B; + pub comptime global B: u32 = A; + + fn main() { } + "#; + let errors = get_program_errors(src); + + assert!(errors.iter().any(|(error, _)| matches!( + error, + CompilationError::InterpreterError(InterpreterError::GlobalsDependencyCycle { .. }) + ))); + assert!(errors.iter().any(|(error, _)| matches!( + error, + CompilationError::ResolverError(ResolverError::DependencyCycle { .. }) + ))); +} + +#[test] +fn warns_on_unneeded_unsafe() { + let src = r#" + fn main() { + unsafe { + //@safety: test + foo() + } + } + + fn foo() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + &errors[0].0, + CompilationError::TypeError(TypeCheckError::UnnecessaryUnsafeBlock { .. }) + )); +} + +#[test] +fn warns_on_nested_unsafe() { + let src = r#" + fn main() { + unsafe { + //@safety: test + unsafe { + //@safety: test + foo() + } + } + } + + unconstrained fn foo() {} + "#; + let errors = get_program_errors(src); + assert_eq!(errors.len(), 1); + assert!(matches!( + &errors[0].0, + CompilationError::TypeError(TypeCheckError::NestedUnsafeBlock { .. }) + )); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/aliases.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/aliases.rs index 8d3433299f6..1a5621c8adb 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/aliases.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/aliases.rs @@ -47,3 +47,31 @@ fn alias_in_let_pattern() { "#; assert_no_errors(src); } + +#[test] +fn double_alias_in_path() { + let src = r#" + struct Foo {} + + impl Foo { + fn new() -> Self { + Self {} + } + } + + type FooAlias1 = Foo; + type FooAlias2 = FooAlias1; + + fn main() { + let _ = FooAlias2::new(); + } + "#; + assert_no_errors(src); +} + +#[test] +fn double_generic_alias_in_path() { + let src = r#" + "#; + assert_no_errors(src); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs index 3328fe439ae..83de9c077ab 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/arithmetic_generics.rs @@ -153,3 +153,49 @@ fn arithmetic_generics_checked_cast_indirect_zeros() { panic!("unexpected error: {:?}", monomorphization_error); } } + +#[test] +fn global_numeric_generic_larger_than_u32() { + // Regression test for https://github.com/noir-lang/noir/issues/6125 + let source = r#" + global A: Field = 4294967297; + + fn foo() { } + + fn main() { + let _ = foo::(); + } + "#; + let errors = get_program_errors(source); + assert_eq!(errors.len(), 0); +} + +#[test] +fn global_arithmetic_generic_larger_than_u32() { + // Regression test for https://github.com/noir-lang/noir/issues/6126 + let source = r#" + struct Foo {} + + impl Foo { + fn size(self) -> Field { + let _ = self; + F + } + } + + // 2^32 - 1 + global A: Field = 4294967295; + + // Avoiding overflow succeeds: + // fn foo() -> Foo { + fn foo() -> Foo { + Foo {} + } + + fn main() { + let _ = foo::().size(); + } + "#; + let errors = get_program_errors(source); + assert_eq!(errors.len(), 0); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests/references.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests/references.rs index ce72240d146..7ae3974a125 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests/references.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests/references.rs @@ -88,6 +88,7 @@ fn constrained_reference_to_unconstrained() { let x_ref = &mut x; if x == 5 { unsafe { + //@safety: test context mut_ref_input(x_ref, y); } } diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index 5c707e92e21..9a4bca1e339 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -32,6 +32,7 @@ "boilerplates", "bridgekeeper", "brillig", + "bunx", "bytecount", "cachix", "callsite", @@ -242,9 +243,11 @@ "walkdir", "wasi", "wasmer", + "webapps", "Weierstraß", "whitespace", "whitespaces", + "YOLO", "zkhash", "zshell" ], diff --git a/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md b/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.mdx similarity index 64% rename from noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md rename to noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.mdx index 2cc0f8e57ce..da36b60920d 100644 --- a/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.md +++ b/noir/noir-repo/docs/docs/how_to/how-to-solidity-verifier.mdx @@ -20,24 +20,37 @@ sidebar_position: 0 pagination_next: tutorials/noirjs_app --- -Noir has the ability to generate a verifier contract in Solidity, which can be deployed in many EVM-compatible blockchains such as Ethereum. +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +Noir is universal. The witness and the compiled program can be fed into a proving backend such as Aztec's [Barretenberg](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg), which can then generate a verifier contract for deployment on blockchains. This allows for a powerful feature set, as one can make use of the conciseness and the privacy provided by Noir in an immutable ledger. Applications can range from simple P2P guessing games, to complex private DeFi interactions. -This guide shows you how to generate a Solidity Verifier and deploy it on the [Remix IDE](https://remix.ethereum.org/). It is assumed that: +Although not strictly in the domain of Noir itself, this guide shows how to generate a Solidity Verifier with Barretenberg and deploy it on the [Remix IDE](https://remix.ethereum.org/). It is assumed that: +- You will be using Barretenberg as your proving backend +- You will be using an EVM blockchain to verify your proof - You are comfortable with the Solidity programming language and understand how contracts are deployed on the Ethereum network - You have Noir installed and you have a Noir program. If you don't, [get started](../getting_started/quick_start.md) with Nargo and the example Hello Noir circuit - You are comfortable navigating RemixIDE. If you aren't or you need a refresher, you can find some video tutorials [here](https://www.youtube.com/channel/UCjTUPyFEr2xDGN6Cg8nKDaA) that could help you. ## Rundown -Generating a Solidity Verifier contract is actually a one-command process. However, compiling it and deploying it can have some caveats. Here's the rundown of this guide: +Generating a Solidity Verifier with Barretenberg contract is actually a one-command process. However, compiling it and deploying it can have some caveats. Here's the rundown of this guide: 1. How to generate a solidity smart contract 2. How to compile the smart contract in the RemixIDE 3. How to deploy it to a testnet +:::info[Which proving system to use?] + +Barretenberg currently provides two provers: `UltraPlonk` and `UltraHonk`. In a nutshell, `UltraHonk` is faster and uses less RAM, but its verifier contract is much more expensive. `UltraPlonk` is optimized for on-chain verification, but proving is more expensive. + +In any case, we provide instructions for both. Choose your poison ☠️ + +::: + ## Step 1 - Generate a contract This is by far the most straightforward step. Just run: @@ -46,25 +59,31 @@ This is by far the most straightforward step. Just run: nargo compile ``` -This will compile your source code into a Noir build artifact to be stored in the `./target` directory, you can then generate the smart contract using the commands: +This will compile your source code into a Noir build artifact to be stored in the `./target` directory. From here on, it's Barretenberg's work. You can generate the smart contract using the commands: + + + + +```sh +bb write_vk_ultra_keccak_honk -b ./target/.json +bb contract_ultra_honk +``` + + + ```sh -# Here we pass the path to the newly generated Noir artifact. bb write_vk -b ./target/.json bb contract ``` -replacing `` with the name of your Noir project. A new `contract` folder would then be generated in your project directory, containing the Solidity -file `contract.sol`. It can be deployed to any EVM blockchain acting as a verifier smart contract. + + -You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/quick_start.md#proving-backend). +replacing `` with the name of your Noir project. A `Verifier.sol` contract is now in the target folder and can be deployed to any EVM blockchain acting as a verifier smart contract. -:::info - -It is possible to generate verifier contracts of Noir programs for other smart contract platforms as long as the proving backend supplies an implementation. +You can find more information about `bb` and the default Noir proving backend on [this page](../getting_started/quick_start.md#proving-backend). -Barretenberg, the default proving backend for Nargo, supports generation of verifier contracts, for the time being these are only in Solidity. -::: ## Step 2 - Compiling @@ -85,17 +104,12 @@ To compile our the verifier, we can navigate to the compilation tab: ![Compilation Tab](@site/static/img/how-tos/solidity_verifier_2.png) -Remix should automatically match a suitable compiler version. However, hitting the "Compile" button will most likely generate a "Stack too deep" error: +Remix should automatically match a suitable compiler version. However, hitting the "Compile" button will most likely tell you the contract is too big to deploy on mainnet, or complain about a stack too deep: -![Stack too deep](@site/static/img/how-tos/solidity_verifier_3.png) +![Contract code too big](@site/static/img/how-tos/solidity_verifier_6.png) +![Stack too deep](@site/static/img/how-tos/solidity_verifier_8.png) -This is due to the verify function needing to put many variables on the stack, but enabling the optimizer resolves the issue. To do this, let's open the "Advanced Configurations" tab and enable optimization. The default 200 runs will suffice. - -:::info - -This time we will see a warning about an unused function parameter. This is expected, as the `verify` function doesn't use the `_proof` parameter inside a solidity block, it is loaded from calldata and used in assembly. - -::: +To avoid this, you can just use some optimization. Open the "Advanced Configurations" tab and enable optimization. The default 200 runs will suffice. ![Compilation success](@site/static/img/how-tos/solidity_verifier_4.png) @@ -103,54 +117,81 @@ This time we will see a warning about an unused function parameter. This is expe At this point we should have a compiled contract ready to deploy. If we navigate to the deploy section in Remix, we will see many different environments we can deploy to. The steps to deploy on each environment would be out-of-scope for this guide, so we will just use the default Remix VM. -Looking closely, we will notice that our "Solidity Verifier" is actually three contracts working together: - -- An `UltraVerificationKey` library which simply stores the verification key for our circuit. -- An abstract contract `BaseUltraVerifier` containing most of the verifying logic. -- A main `UltraVerifier` contract that inherits from the Base and uses the Key contract. - -Remix will take care of the dependencies for us so we can simply deploy the UltraVerifier contract by selecting it and hitting "deploy": +Looking closely, we will notice that our "Solidity Verifier" is composed on multiple contracts working together. Remix will take care of the dependencies for us so we can simply deploy the Verifier contract by selecting it and hitting "deploy": -![Deploying UltraVerifier](@site/static/img/how-tos/solidity_verifier_5.png) + + -A contract will show up in the "Deployed Contracts" section, where we can retrieve the Verification Key Hash. This is particularly useful for double-checking that the deployer contract is the correct one. +![Deploying HonkVerifier](@site/static/img/how-tos/solidity_verifier_7.png) -:::note + + -Why "UltraVerifier"? +![Deploying PlonkVerifier](@site/static/img/how-tos/solidity_verifier_9.png) -To be precise, the Noir compiler (`nargo`) doesn't generate the verifier contract directly. It compiles the Noir code into an intermediate language (ACIR), which is then executed by the backend. So it is the backend that returns the verifier smart contract, not Noir. + + -In this case, the Barretenberg Backend uses the UltraPlonk proving system, hence the "UltraVerifier" name. - -::: +A contract will show up in the "Deployed Contracts" section. ## Step 4 - Verifying -To verify a proof using the Solidity verifier contract, we call the `verify` function in this extended contract: +To verify a proof using the Solidity verifier contract, we call the `verify` function: ```solidity function verify(bytes calldata _proof, bytes32[] calldata _publicInputs) external view returns (bool) ``` -When using the default example in the [Hello Noir](../getting_started/quick_start.md) guide, the easiest way to confirm that the verifier contract is doing its job is by calling the `verify` function via remix with the required parameters. Note that the public inputs must be passed in separately to the rest of the proof so we must split the proof as returned from `bb`. +First generate a proof with `bb`. We need a `Prover.toml` file for our inputs. Run: -First generate a proof with `bb` at the location `./proof` using the steps in [get started](../getting_started/quick_start.md), this proof is in a binary format but we want to convert it into a hex string to pass into Remix, this can be done with the +```bash +nargo check +``` + +This will generate a `Prover.toml` you can fill with the values you want to prove. We can now execute the circuit with `nargo` and then use the proving backend to prove: + + + + +```bash +nargo execute +bb prove_ultra_keccak_honk -b ./target/.json -w ./target/ -o ./target/proof +``` + +:::tip[Public inputs] +Barretenberg attaches the public inputs to the proof, which in this case it's not very useful. If you're up for some JS, `bb.js` has [a method for it](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/ts/src/proof/index.ts), but in the CLI you can use this ugly snippet: + +```bash +cat ./target/proof | od -An -v -t x1 | tr -d $' \n' | sed 's/^.\{8\}//' | (read hex; echo "${hex:0:192}${hex:256}") +``` + +Beautiful. This assumes a circuit with one public input (32 bytes, for Barretenberg). For more inputs, you can just increment `hex:256` with 32 more bytes for each public input. + +::: + + + ```bash -# This value must be changed to match the number of public inputs (including return values!) in your program. -NUM_PUBLIC_INPUTS=1 -PUBLIC_INPUT_BYTES=32*NUM_PUBLIC_INPUTS -HEX_PUBLIC_INPUTS=$(head -c $PUBLIC_INPUT_BYTES ./proof | od -An -v -t x1 | tr -d $' \n') -HEX_PROOF=$(tail -c +$(($PUBLIC_INPUT_BYTES + 1)) ./proof | od -An -v -t x1 | tr -d $' \n') +nargo execute +bb prove -b ./target/.json -w ./target/ -o ./target/proof +``` + -echo "Public inputs:" -echo $HEX_PUBLIC_INPUTS +:::tip[Public inputs] +Barretenberg attaches the public inputs to the proof, which in this case it's not very useful. If you're up for some JS, `bb.js` has [a method for it](https://github.com/AztecProtocol/aztec-packages/blob/master/barretenberg/ts/src/proof/index.ts), but in the CLI you can use this ugly snippet: -echo "Proof:" -echo "0x$HEX_PROOF" +```bash +tail -c +33 ./target/proof | od -An -v -t x1 | tr -d $' \n' ``` +Beautiful. This assumes a circuit with one public input (32 bytes, for Barretenberg). For more inputs, you can just add 32 more bytes for each public input to the `tail` command. + +::: + + + + Remix expects that the public inputs will be split into an array of `bytes32` values so `HEX_PUBLIC_INPUTS` needs to be split up into 32 byte chunks which are prefixed with `0x` accordingly. A programmatic example of how the `verify` function is called can be seen in the example zk voting application [here](https://github.com/noir-lang/noir-examples/blob/33e598c257e2402ea3a6b68dd4c5ad492bce1b0a/foundry-voting/src/zkVote.sol#L35): @@ -188,7 +229,7 @@ the `verify` function will expect the public inputs array (second function param Passing only two inputs will result in an error such as `PUBLIC_INPUT_COUNT_INVALID(3, 2)`. -In this case, the inputs parameter to `verify` would be an array ordered as `[pubkey_x, pubkey_y, return`. +In this case, the inputs parameter to `verify` would be an array ordered as `[pubkey_x, pubkey_y, return]`. ::: diff --git a/noir/noir-repo/docs/docs/noir/concepts/data_types/fields.md b/noir/noir-repo/docs/docs/noir/concepts/data_types/fields.md index b5af20f7d7e..c339e3d05e4 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/data_types/fields.md +++ b/noir/noir-repo/docs/docs/noir/concepts/data_types/fields.md @@ -132,7 +132,7 @@ example: ```rust fn main() { let field = 2 - field.assert_max_bit_size(32); + field.assert_max_bit_size::<32>(); } ``` diff --git a/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md b/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md index b5221b8d2dd..8e07d719f4b 100644 --- a/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md +++ b/noir/noir-repo/docs/docs/noir/concepts/unconstrained.md @@ -67,6 +67,7 @@ We can then run `u72_to_u8` as unconstrained brillig code in order to calculate ```rust fn main(num: u72) -> pub [u8; 8] { let out = unsafe { + //@safety: 'out' is properly constrained below in 'assert(num == reconstructed_num);' u72_to_u8(num) }; @@ -96,6 +97,7 @@ This ends up taking off another ~250 gates from our circuit! We've ended up with Note that in order to invoke unconstrained functions we need to wrap them in an `unsafe` block, to make it clear that the call is unconstrained. +Furthermore, a warning is emitted unless the `unsafe` block starts with a `//@safety: ...` comment explaining why it is fine to call the unconstrained function. Generally we want to use brillig whenever there's something that's easy to verify but hard to compute within the circuit. For example, if you wanted to calculate a square root of a number it'll be a much better idea to calculate this in brillig and then assert that if you square the result you get back your number. diff --git a/noir/noir-repo/docs/docs/tutorials/noirjs_app.md b/noir/noir-repo/docs/docs/tutorials/noirjs_app.md index 6e69ea0bbed..8967ee005ce 100644 --- a/noir/noir-repo/docs/docs/tutorials/noirjs_app.md +++ b/noir/noir-repo/docs/docs/tutorials/noirjs_app.md @@ -1,186 +1,91 @@ --- -title: Building a web app with NoirJS +title: Building a web app with Noir and Barretenberg description: Learn how to setup a new app that uses Noir to generate and verify zero-knowledge SNARK proofs in a typescript or javascript environment. keywords: [how to, guide, javascript, typescript, noir, barretenberg, zero-knowledge, proofs, app] sidebar_position: 0 pagination_next: noir/concepts/data_types/index --- -NoirJS is a set of packages meant to work both in a browser and a server environment. In this tutorial, we will build a simple web app using them. From here, you should get an idea on how to proceed with your own Noir projects! +NoirJS is a Typescript package meant to work both in a browser and a server environment. -You can find the complete app code for this guide [here](https://github.com/noir-lang/tiny-noirjs-app). - -## Setup - -:::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.31.x matches `noir_js@0.31.x`, etc. - -In this guide, we will be pinned to 0.31.0. - -::: - -Before we start, we want to make sure we have Node, Nargo and the Barretenberg proving system (`bb`) installed. - -We start by opening a terminal and executing `node --version`. If we don't get an output like `v20.10.0`, that means node is not installed. Let's do that by following the handy [nvm guide](https://github.com/nvm-sh/nvm?tab=readme-ov-file#install--update-script). - -As for `Nargo`, we can follow the [Nargo guide](../getting_started/quick_start.md) to install it. If you're lazy, just paste this on a terminal and run `noirup`: - -```sh -curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash -``` +In this tutorial, we will combine NoirJS with Aztec's Barretenberg backend to build a simple web app. From here, you should get an idea on how to proceed with your own Noir projects! -Follow the instructions on [this page](https://github.com/AztecProtocol/aztec-packages/tree/master/barretenberg/cpp/src/barretenberg/bb#installation) to install `bb`. -Version 0.41.0 is compatible with `nargo` version 0.31.0, which you can install with `bbup -v 0.41.0` once `bbup` is installed. - -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. - -In fact, it's so simple that it comes nicely packaged in `nargo`. Let's do that! +You can find the complete app code for this guide [here](https://github.com/noir-lang/tiny-noirjs-app). -### Nargo +## Dependencies -Run: +Before we start, we want to make sure we have Node installed. For convenience (and speed), we can just install [Bun](https://bun.sh) as our package manager, and Node will work out-of-the-box: ```bash -nargo new circuit +curl -fsSL https://bun.sh/install | bash ``` -And... That's about it. Your program is ready to be compiled and run. +Let's go barebones. Doing the bare minimum is not only simple, but also allows you to easily adapt it to almost any frontend framework. -To compile, let's `cd` into the `circuit` folder to enter our project, and call: +Barebones means we can immediately start with the dependencies even on an empty folder 😈: ```bash -nargo compile +bun i @noir-lang/noir_wasm@1.0.0-beta.0 @noir-lang/noir_js@1.0.0-beta.0 @aztec/bb.js@0.63.1 ``` -This compiles our circuit into `json` format and add it to a new `target` folder. +Wait, what are these dependencies? -:::info +- `noir_wasm` is the `wasm` version of the Noir compiler. Although most developers prefer to use `nargo` for compiling, there's nothing wrong with `noir_wasm`. We like `noir_wasm`. +- `noir_js` is the main Noir package. It will execute our program, and generate the witness that will be sent to the backend. +- `bb.js` is the Typescript interface for Aztec's Barretenberg proving backend. It also uses the `wasm` version in order to run on the browser. -At this point in the tutorial, your folder structure should look like this: +:::info -```tree -. -└── circuit <---- our working directory - ├── Nargo.toml - ├── src - │ └── main.nr - └── target - └── circuit.json -``` +In this guide, we will install versions pinned to 1.0.0-beta.0. These work with Barretenberg version 0.63.1, so we are using that one version too. Feel free to try with older or later versions, though! ::: -### Node and Vite - -If you want to explore Nargo, feel free to go on a side-quest now and follow the steps in the -[getting started](../getting_started/quick_start.md) guide. However, we want our app to run on the browser, so we need Vite. +## Setting up our Noir program -Vite is a powerful tool to generate static websites. While it provides all kinds of features, let's just go barebones with some good old vanilla JS. +ZK is a powerful technology. An app that reveals computational correctness but doesn't reveal some of its inputs is almost unbelievable, yet Noir makes it as easy as a single line of code. -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". +:::tip -A wild `vite-project` directory should now appear in your root folder! Let's not waste any time and dive right in: +It's not just you. We also enjoy syntax highlighting. [Check out the Language Server](../tooling/language_server.md) -```bash -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'; -import fs from 'fs'; -import path from 'path'; - -const wasmContentTypePlugin = { - name: 'wasm-content-type-plugin', - configureServer(server) { - server.middlewares.use(async (req, res, next) => { - if (req.url.endsWith('.wasm')) { - res.setHeader('Content-Type', 'application/wasm'); - const newPath = req.url.replace('deps', 'dist'); - const targetPath = path.join(__dirname, newPath); - const wasmContent = fs.readFileSync(targetPath); - return res.end(wasmContent); - } - next(); - }); - }, -}; +All you need is a `main.nr` and a `Nargo.toml` file. You can follow the [noirup](../getting_started/noir_installation.md) installation and just run `noirup -v 1.0.0-beta.0`, or just create them by hand: -export default defineConfig(({ command }) => { - if (command === 'serve') { - return { - build: { - target: 'esnext', - rollupOptions: { - external: ['@aztec/bb.js'] - } - }, - optimizeDeps: { - esbuildOptions: { - target: 'esnext' - } - }, - plugins: [ - copy({ - targets: [{ src: 'node_modules/**/*.wasm', dest: 'node_modules/.vite/dist' }], - copySync: true, - hook: 'buildStart', - }), - command === 'serve' ? wasmContentTypePlugin : [], - ], - }; - } - - return {}; -}); +```bash +mkdir -p circuit/src +touch circuit/src/main.nr circuit/Nargo.toml ``` -#### Install Dependencies - -Now that our stage is set, install the necessary NoirJS packages along with our other dependencies: +To make our program interesting, let's give it a real use-case scenario: Bob wants to prove he is older than 18, without disclosing his age. Open `main.nr` and write: -```bash -npm install && npm install @noir-lang/backend_barretenberg@0.31.0 @noir-lang/noir_js@0.31.0 -npm install rollup-plugin-copy --save-dev +```rust +fn main(age: u8) { + assert(age >= 18); +} ``` -:::info - -At this point in the tutorial, your folder structure should look like this: +This program accepts a private input called age, and simply proves this number is higher than 18. But to run this code, we need to give the compiler a `Nargo.toml` with at least a name and a type: -```tree -. -└── circuit - └── ...etc... -└── vite-project <---- our working directory - └── ...etc... +```toml +[package] +name = "circuit" +type = "bin" ``` -::: +This is all that we need to get started with Noir. -#### Some cleanup +![my heart is ready for you, noir.js](@site/static/img/memes/titanic.jpeg) -`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. +## Setting up our app -![my heart is ready for you, noir.js](@site/static/img/memes/titanic.jpeg) +Remember when apps only had one `html` and one `js` file? Well, that's enough for Noir webapps. Let's create them: -## HTML +```bash +touch index.html index.js +``` -Our app won't run like this, of course. We need some working HTML, at least. Let's open our broken-hearted `index.html` and replace everything with this code snippet: +And add something useful to our HTML file: ```html @@ -200,11 +105,11 @@ Our app won't run like this, of course. We need some working HTML, at least. Let - +

Noir app

- - + +

Logs

@@ -214,32 +119,26 @@ 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. - -## Some good old vanilla Javascript - -Our love for Noir needs undivided attention, so let's just open `main.js` and delete everything (this is where the romantic scenery becomes a bit creepy). +It _could_ be a beautiful UI... Depending on which universe you live in. In any case, we're using some scary CSS to make two boxes that will show cool things on the screen. -Start by pasting in this boilerplate code: +As for the JS, real madmen could just `console.log` everything, but let's say we want to see things happening (the true initial purpose of JS... right?). Here's some boilerplate for that. Just paste it in `index.js`: ```js -function display(container, msg) { - const c = document.getElementById(container); - const p = document.createElement('p'); - p.textContent = msg; - c.appendChild(p); -} +const show = (id, content) => { + const container = document.getElementById(id); + container.appendChild(document.createTextNode(content)); + container.appendChild(document.createElement("br")); +}; -document.getElementById('submitGuess').addEventListener('click', async () => { - try { - // here's where love happens - } catch (err) { - display('logs', 'Oh 💔 Wrong guess'); - } +document.getElementById("submit").addEventListener("click", async () => { + try { + // noir goes here + } catch { + show("logs", "Oh 💔"); + } }); -``` -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 😢 +``` :::info @@ -248,30 +147,56 @@ At this point in the tutorial, your folder structure should look like this: ```tree . └── circuit - └── ...same as above -└── vite-project - ├── vite.config.js - ├── main.js - ├── package.json - └── index.html + └── src + └── main.nr + Nargo.toml + index.js + package.json + index.html + ...etc ``` -You'll see other files and folders showing up (like `package-lock.json`, `node_modules`) but you shouldn't have to care about those. - ::: -## Some NoirJS +## Compile compile compile -We're starting with the good stuff now. If you've compiled the circuit as described above, you should have a `json` file we want to import at the very top of our `main.js` file: +Finally we're up for something cool. But before we can execute a Noir program, we need to compile it into ACIR: an abstract representation. Here's where `noir_wasm` comes in. -```ts -import circuit from '../circuit/target/circuit.json'; +`noir_wasm` expects a filesystem so it can resolve dependencies. While we could use the `public` folder, let's just import those using the nice `?url` syntax provided by vite. At the top of the file: + +```js +import { compile, createFileManager } from "@noir-lang/noir_wasm" + +import main from "./circuit/src/main.nr?url"; +import nargoToml from "./circuit/Nargo.toml?url"; ``` -[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: +Compiling on the browser is common enough that `createFileManager` already gives us a nice in-memory filesystem we can use. So all we need to compile is fetching these files, writing them to our filesystem, and compile. Add this function: ```js -import { BarretenbergBackend, BarretenbergVerifier as Verifier } from '@noir-lang/backend_barretenberg'; +export async function getCircuit() { + const fm = createFileManager("/"); + const { body } = await fetch(main); + const { body: nargoTomlBody } = await fetch(nargoToml); + + fm.writeFile("./src/main.nr", body); + fm.writeFile("./Nargo.toml", nargoTomlBody); + return await compile(fm); +} +``` + +:::tip + +As you can imagine, with `node` it's all conveniently easier since you get native access to `fs`... + +::: + +## Some more JS + +We're starting with the good stuff now. We want to execute our circuit to get the witness, and then feed that witness to Barretenberg. Luckily, both packages are quite easy to work with. Let's import them at the top of the file: + +```js +import { UltraHonkBackend } from '@aztec/bb.js'; import { Noir } from '@noir-lang/noir_js'; ``` @@ -279,88 +204,103 @@ And instantiate them inside our try-catch block: ```ts // try { -const backend = new BarretenbergBackend(circuit); -const noir = new Noir(circuit); +const { program } = await getCircuit(); +const noir = new Noir(program); +const backend = new UltraHonkBackend(program.bytecode); // } ``` -:::note +:::warning -For the remainder of the tutorial, everything will be happening inside the `try` block +WASMs are not always easy to work with. In our case, `vite` likes serving them with the wrong MIME type. There are different fixes but we found the easiest one is just YOLO instantiating the WASMs manually. Paste this at the top of the file, just below the other imports, and it will work just fine: + +```js +import initNoirC from "@noir-lang/noirc_abi"; +import initACVM from "@noir-lang/acvm_js"; +import acvm from "@noir-lang/acvm_js/web/acvm_js_bg.wasm?url"; +import noirc from "@noir-lang/noirc_abi/web/noirc_abi_wasm_bg.wasm?url"; +await Promise.all([initACVM(fetch(acvm)), initNoirC(fetch(noirc))]); +``` ::: -## Our app +## Executing and proving -Now for the app itself. We're capturing whatever is in the input when people press the submit button. Just add this: +Now for the app itself. We're capturing whatever is in the input when people press the submit button. Inside our `try` block, let's just grab that input and get its value. Noir will gladly execute it, and give us a witness: ```js -const x = parseInt(document.getElementById('guessInput').value); -const input = { x, y: 2 }; +const age = document.getElementById("age").value; +show("logs", "Generating witness... ⏳"); +const { witness } = await noir.execute({ age }); +show("logs", "Generated witness... ✅"); + ``` +:::note + +For the remainder of the tutorial, everything will be happening inside the `try` block + +::: + Now we're ready to prove stuff! Let's feed some inputs to our circuit and calculate the proof: ```js -await setup(); // let's squeeze our wasm inits here - -display('logs', 'Generating proof... ⌛'); -const { witness } = await noir.execute(input); +show("logs", "Generating proof... ⏳"); const proof = await backend.generateProof(witness); -display('logs', 'Generating proof... ✅'); -display('results', proof.proof); +show("logs", "Generated proof... ✅"); +show("results", proof.proof); ``` -You're probably eager to see stuff happening, so go and run your app now! +Our program is technically **done** . You're probably eager to see stuff happening! To serve this in a convenient way, we can use a bundler like `vite` by creating a `vite.config.js` file: + +```bash +touch vite.config.js +``` -From your terminal, run `npm run dev`. If it doesn't open a browser for you, just visit `localhost:5173`. You should now see the worst UI ever, with an ugly input. +`vite` helps us with a little catch: `bb.js` in particular uses top-level awaits which aren't supported everywhere. So we can add this to the `vite.config.js` to make the bundler optimize them: -![Getting Started 0](@site/static/img/noir_getting_started_1.png) +```js +export default { optimizeDeps: { esbuildOptions: { target: "esnext" } } }; +``` + +This should be enough for vite. We don't even need to install it, just run: + +```bash +bunx vite +``` -Now, our circuit says `fn main(x: Field, y: pub Field)`. This means only the `y` value is public, and it's hardcoded above: `input = { x, y: 2 }`. In other words, you won't need to send your secret`x` to the verifier! +If it doesn't open a browser for you, just visit `localhost:5173`. You should now see the worst UI ever, with an ugly input. -By inputting any number other than 2 in the input box and clicking "submit", you should get a valid proof. Otherwise the proof won't even generate correctly. By the way, if you're human, you shouldn't be able to understand anything on the "proof" box. That's OK. We like you, human ❤️. +![Noir Webapp UI](@site/static/img/tutorials/noirjs_webapp/webapp1.png) + +Now, our circuit requires a private input `fn main(age: u8)`, and fails if it is less than 18. Let's see if it works. Submit any number above 18 (as long as it fits in 8 bits) and you should get a valid proof. Otherwise the proof won't even generate correctly. + +By the way, if you're human, you shouldn't be able to understand anything on the "proof" box. That's OK. We like you, human ❤️. ## Verifying Time to celebrate, yes! But we shouldn't trust machines so blindly. Let's add these lines to see our proof being verified: ```js -display('logs', 'Verifying proof... ⌛'); +show('logs', 'Verifying proof... ⌛'); const isValid = await backend.verifyProof(proof); - -// or to cache and use the verification key: -// const verificationKey = await backend.getVerificationKey(); -// const verifier = new Verifier(); -// const isValid = await verifier.verifyProof(proof, verificationKey); - -if (isValid) display('logs', 'Verifying proof... ✅'); +show("logs", `Proof is ${isValid ? "valid" : "invalid"}... ✅`); ``` You have successfully generated a client-side Noir web app! ![coded app without math knowledge](@site/static/img/memes/flextape.jpeg) -## Further Reading - -You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. - -You should also check out the more advanced examples in the [noir-examples repo](https://github.com/noir-lang/noir-examples), where you'll find reference usage for some cool apps. +## Next steps -## UltraHonk Backend +At this point, you have a working ZK app that works on the browser. Actually, it works on a mobile phone too! -Barretenberg has recently exposed a new UltraHonk backend. We can use UltraHonk in NoirJS after version 0.33.0. Everything will be the same as the tutorial above, except that the class we need to import will change: +If you want to continue learning by doing, here are some challenges for you: -```js -import { UltraHonkBackend, UltraHonkVerifier as Verifier } from '@noir-lang/backend_barretenberg'; -``` - -The backend will then be instantiated as such: - -```js -const backend = new UltraHonkBackend(circuit); -``` +- Install [nargo](https://noir-lang.org/docs/getting_started/noir_installation) and write [Noir tests](../tooling/testing) +- Change the circuit to accept a [public input](../noir/concepts/data_types/#private--public-types) as the cutoff age. It could be different depending on the purpose, for example! +- Enjoy Noir's Rust-like syntax and write a struct `Country` that implements a trait `MinAge` with a method `get_min_age`. Then, make a struct `Person` have an `u8` as its age and a country of type `Country`. You can pass a `person` in JS just like a JSON object `person: { age, country: { min_age: 18 }}` -Then all the commands to prove and verify your circuit will be same. +The world is your stage, just have fun with ZK! You can see how noirjs is used in a full stack Next.js hardhat application in the [noir-starter repo here](https://github.com/noir-lang/noir-starter/tree/main/vite-hardhat). The example shows how to calculate a proof in the browser and verify it with a deployed Solidity verifier contract from noirjs. -The only feature currently unsupported with UltraHonk are [recursive proofs](../explainers/explainer-recursion.md). +Check out other starters, tools, or just cool projects in the [awesome noir repository](https://github.com/noir-lang/awesome-noir). diff --git a/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_6.png b/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_6.png new file mode 100644 index 0000000000000000000000000000000000000000..c92e874740a24dba9194f886f1e3b7c27aec99b4 GIT binary patch literal 40585 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 literal 0 HcmV?d00001 diff --git a/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_7.png b/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_7.png new file mode 100644 index 0000000000000000000000000000000000000000..c8541fa544cb504f8c66751c88d4d0919a61cff3 GIT binary patch literal 73765 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 B7OemP literal 0 HcmV?d00001 diff --git a/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_8.png b/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_8.png new file mode 100644 index 0000000000000000000000000000000000000000..125c140acec6c07e83f3b5454d993e8a171d3b7a GIT binary patch literal 41640 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 literal 0 HcmV?d00001 diff --git a/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_9.png b/noir/noir-repo/docs/static/img/how-tos/solidity_verifier_9.png new file mode 100644 index 0000000000000000000000000000000000000000..150812e33209cc583369358cab74dfe40bf2ad1e GIT binary patch literal 58960 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�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 ME9)rLzP66~AECO8)Bpeg literal 0 HcmV?d00001 diff --git a/noir/noir-repo/docs/static/img/tutorials/noirjs_webapp/webapp1.png b/noir/noir-repo/docs/static/img/tutorials/noirjs_webapp/webapp1.png new file mode 100644 index 0000000000000000000000000000000000000000..7591cd827e3ef91b0dde36e492f7636fb3744640 GIT binary patch literal 15009 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, ordering: fn[Env](T, T) -> bool) -> Self { unsafe { - // Safety: `sorted` array is checked to be: - // a. a permutation of `input`'s elements - // b. satisfying the predicate `ordering` + /*@safety: `sorted` array is checked to be: + a. a permutation of `input`'s elements + b. satisfying the predicate `ordering` + */ let sorted = quicksort::quicksort(self, ordering); if !is_unconstrained() { diff --git a/noir/noir-repo/noir_stdlib/src/collections/map.nr b/noir/noir-repo/noir_stdlib/src/collections/map.nr index 2b0da1b90ec..bc0b80124db 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/map.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/map.nr @@ -271,7 +271,7 @@ impl HashMap { for slot in self._table { if slot.is_valid() { - let (_, value) = unsafe { slot.key_value_unchecked() }; + let (_, value) = slot.key_value_unchecked(); values.push(value); } } diff --git a/noir/noir-repo/noir_stdlib/src/collections/umap.nr b/noir/noir-repo/noir_stdlib/src/collections/umap.nr index 7aebeb437cf..27af14caa24 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/umap.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/umap.nr @@ -113,7 +113,11 @@ impl UHashMap { H: Hasher, { // docs:end:contains_key - unsafe { self.get(key) }.is_some() + unsafe { + //@safety : unconstrained context + self.get(key) + } + .is_some() } // Returns true if the map contains no elements. @@ -432,7 +436,10 @@ where // Not marked as deleted and has key-value. if equal & slot.is_valid() { let (key, value) = slot.key_value_unchecked(); - let other_value = unsafe { other.get(key) }; + let other_value = unsafe { + //@safety : unconstrained context + other.get(key) + }; if other_value.is_none() { equal = false; diff --git a/noir/noir-repo/noir_stdlib/src/convert.nr b/noir/noir-repo/noir_stdlib/src/convert.nr index d4bea8b6390..1e5c1484b5f 100644 --- a/noir/noir-repo/noir_stdlib/src/convert.nr +++ b/noir/noir-repo/noir_stdlib/src/convert.nr @@ -117,3 +117,51 @@ impl From for Field { } } // docs:end:from-impls + +/// A generic interface for casting between primitive types, +/// equivalent of using the `as` keyword between values. +/// +/// # Example +/// +/// ``` +/// let x: Field = 1234567890; +/// let y: u8 = x as u8; +/// let z: u8 = x.as_(); +/// assert_eq(y, z); +/// ``` +pub trait AsPrimitive { + /// The equivalent of doing `self as T`. + fn as_(self) -> T; +} + +#[generate_as_primitive_impls] +comptime fn generate_as_primitive_impls(_: FunctionDefinition) -> Quoted { + let types = [ + quote { bool }, + quote { u8 }, + quote { u16 }, + quote { u32 }, + quote { u64 }, + quote { i8 }, + quote { i16 }, + quote { i32 }, + quote { i64 }, + quote { Field }, + ]; + + let mut impls = &[]; + for type1 in types { + for type2 in types { + impls = impls.push_back( + quote { + impl AsPrimitive<$type1> for $type2 { + fn as_(self) -> $type1 { + self as $type1 + } + } + }, + ); + } + } + impls.join(quote {}) +} diff --git a/noir/noir-repo/noir_stdlib/src/field/bn254.nr b/noir/noir-repo/noir_stdlib/src/field/bn254.nr index a7ca7d77373..f3cbdca2762 100644 --- a/noir/noir-repo/noir_stdlib/src/field/bn254.nr +++ b/noir/noir-repo/noir_stdlib/src/field/bn254.nr @@ -39,6 +39,10 @@ fn assert_gt_limbs(a: (Field, Field), b: (Field, Field)) { let (alo, ahi) = a; let (blo, bhi) = b; unsafe { + /*@safety: borrow is enforced to be boolean due to its type. + if borrow is 0, it asserts that (alo > blo && ahi >= bhi) + if borrow is 1, it asserts that (alo <= blo && ahi > bhi) + */ let borrow = lte_hint(alo, blo); let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; @@ -55,6 +59,7 @@ pub fn decompose(x: Field) -> (Field, Field) { compute_decomposition(x) } else { unsafe { + /*@safety: decomposition is properly checked below*/ // Take hints of the decomposition let (xlo, xhi) = decompose_hint(x); @@ -74,7 +79,12 @@ pub fn decompose(x: Field) -> (Field, Field) { pub fn assert_gt(a: Field, b: Field) { if is_unconstrained() { - assert(unsafe { field_less_than(b, a) }); + assert( + unsafe { + //@safety: already unconstrained + field_less_than(b, a) + }, + ); } else { // Decompose a and b let a_limbs = decompose(a); @@ -92,13 +102,14 @@ pub fn assert_lt(a: Field, b: Field) { pub fn gt(a: Field, b: Field) -> bool { if is_unconstrained() { unsafe { + //@safety: unsafe in unconstrained field_less_than(b, a) } } else if a == b { false } else { - // Take a hint of the comparison and verify it unsafe { + //@safety: Take a hint of the comparison and verify it if field_less_than(a, b) { assert_gt(b, a); false diff --git a/noir/noir-repo/noir_stdlib/src/field/mod.nr b/noir/noir-repo/noir_stdlib/src/field/mod.nr index a5c1ff7bb62..7e5ad97d64f 100644 --- a/noir/noir-repo/noir_stdlib/src/field/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/field/mod.nr @@ -238,6 +238,7 @@ pub fn bytes32_to_field(bytes32: [u8; 32]) -> Field { fn lt_fallback(x: Field, y: Field) -> bool { if is_unconstrained() { unsafe { + //@safety : unconstrained context field_less_than(x, y) } } else { diff --git a/noir/noir-repo/noir_stdlib/src/hash/mod.nr b/noir/noir-repo/noir_stdlib/src/hash/mod.nr index 5f7017b60b7..89f6d2a1363 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mod.nr @@ -96,7 +96,10 @@ fn __derive_generators( // does not assert the limbs are 128 bits // does not assert the decomposition does not overflow the EmbeddedCurveScalar fn from_field_unsafe(scalar: Field) -> EmbeddedCurveScalar { - let (xlo, xhi) = unsafe { crate::field::bn254::decompose_hint(scalar) }; + let (xlo, xhi) = unsafe { + //@safety : xlo and xhi decomposition is checked below + crate::field::bn254::decompose_hint(scalar) + }; // Check that the decomposition is correct assert_eq(scalar, xlo + crate::field::bn254::TWO_POW_128 * xhi); EmbeddedCurveScalar { lo: xlo, hi: xhi } diff --git a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr index b9a2b02c9d9..a4e21304912 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/sha256.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/sha256.nr @@ -74,8 +74,10 @@ pub fn sha256_var(msg: [u8; N], message_size: u64) -> HASH { let mut msg_byte_ptr = 0; for i in 0..num_blocks { let msg_start = BLOCK_SIZE * i; - let (new_msg_block, new_msg_byte_ptr) = - unsafe { build_msg_block(msg, message_size, msg_start) }; + let (new_msg_block, new_msg_byte_ptr) = unsafe { + /*@safety : the msg_block is checked below in verify_msg_block*/ + build_msg_block(msg, message_size, msg_start) + }; if msg_start < message_size { msg_block = new_msg_block; @@ -104,8 +106,10 @@ pub fn sha256_var(msg: [u8; N], message_size: u64) -> HASH { // or our message cannot be evenly split into blocks. if modulo != 0 { let msg_start = BLOCK_SIZE * num_blocks; - let (new_msg_block, new_msg_byte_ptr) = - unsafe { build_msg_block(msg, message_size, msg_start) }; + let (new_msg_block, new_msg_byte_ptr) = unsafe { + //@safety : the msg_block is checked below in verify_msg_block + build_msg_block(msg, message_size, msg_start) + }; if msg_start < message_size { msg_block = new_msg_block; @@ -146,7 +150,10 @@ pub fn sha256_var(msg: [u8; N], message_size: u64) -> HASH { msg_byte_ptr = 0; } - msg_block = unsafe { attach_len_to_msg_block(msg_block, msg_byte_ptr, message_size) }; + msg_block = unsafe { + //@safety : the msg_len is checked below in verify_msg_len + attach_len_to_msg_block(msg_block, msg_byte_ptr, message_size) + }; if !is_unconstrained() { verify_msg_len(msg_block, last_block, msg_byte_ptr, message_size); @@ -798,7 +805,10 @@ mod tests { 101, 115, 46, 48, ]; assert_eq(input.len(), 22); - let (msg_block, msg_byte_ptr) = unsafe { build_msg_block(input, input.len(), 0) }; + let (msg_block, msg_byte_ptr) = unsafe { + //@safety : testing context + build_msg_block(input, input.len(), 0) + }; assert_eq(msg_byte_ptr, input.len()); assert_eq(msg_block[0], make_item(input[0], input[1], input[2], input[3])); assert_eq(msg_block[1], make_item(input[4], input[5], input[6], input[7])); @@ -815,7 +825,10 @@ mod tests { 108, 97, 105, 110, 59, 32, 99, 104, 97, 114, 115, 101, 116, ]; assert_eq(input.len(), 68); - let (msg_block, msg_byte_ptr) = unsafe { build_msg_block(input, input.len(), 64) }; + let (msg_block, msg_byte_ptr) = unsafe { + //@safety : testing context + build_msg_block(input, input.len(), 64) + }; assert_eq(msg_byte_ptr, 4); assert_eq(msg_block[0], make_item(input[64], input[65], input[66], input[67])); assert_eq(msg_block[1], 0); @@ -828,7 +841,10 @@ mod tests { 1919905082, 1131376244, 1701737517, 1417244773, 978151789, 1697470053, 1920166255, 1849316213, 1651139939, ]; - let msg_block = unsafe { attach_len_to_msg_block(input, 1, 448) }; + let msg_block = unsafe { + //@safety : testing context + attach_len_to_msg_block(input, 1, 448) + }; assert_eq(msg_block[0], ((1 << 7) as u32) * 256 * 256 * 256); assert_eq(msg_block[1], 0); assert_eq(msg_block[15], 3584); diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index c3a289a8fa2..cdc82b20509 100644 --- a/noir/noir-repo/noir_stdlib/src/lib.nr +++ b/noir/noir-repo/noir_stdlib/src/lib.nr @@ -28,6 +28,8 @@ pub mod mem; pub mod panic; pub mod hint; +use convert::AsPrimitive; + // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident #[oracle(print)] @@ -39,12 +41,14 @@ unconstrained fn print_unconstrained(with_newline: bool, input: T) { pub fn println(input: T) { unsafe { + //@safety: a print statement cannot be constrained print_unconstrained(true, input); } } pub fn print(input: T) { unsafe { + //@safety: a print statement cannot be constrained print_unconstrained(false, input); } } @@ -89,28 +93,29 @@ pub fn assert_constant(x: T) {} #[builtin(static_assert)] pub fn static_assert(predicate: bool, message: str) {} -// from_field and as_field are private since they are not valid for every type. -// `as` should be the default for users to cast between primitive types, and in the future -// traits can be used to work with generic types. -#[builtin(from_field)] -fn from_field(x: Field) -> T {} - -#[builtin(as_field)] -fn as_field(x: T) -> Field {} - -pub fn wrapping_add(x: T, y: T) -> T { - crate::from_field(crate::as_field(x) + crate::as_field(y)) +pub fn wrapping_add(x: T, y: T) -> T +where + T: AsPrimitive, + Field: AsPrimitive, +{ + AsPrimitive::as_(x.as_() + y.as_()) } -pub fn wrapping_sub(x: T, y: T) -> T { +pub fn wrapping_sub(x: T, y: T) -> T +where + T: AsPrimitive, + Field: AsPrimitive, +{ //340282366920938463463374607431768211456 is 2^128, it is used to avoid underflow - crate::from_field( - crate::as_field(x) + 340282366920938463463374607431768211456 - crate::as_field(y), - ) + AsPrimitive::as_(x.as_() + 340282366920938463463374607431768211456 - y.as_()) } -pub fn wrapping_mul(x: T, y: T) -> T { - crate::from_field(crate::as_field(x) * crate::as_field(y)) +pub fn wrapping_mul(x: T, y: T) -> T +where + T: AsPrimitive, + Field: AsPrimitive, +{ + AsPrimitive::as_(x.as_() * y.as_()) } #[builtin(as_witness)] diff --git a/noir/noir-repo/noir_stdlib/src/meta/expr.nr b/noir/noir-repo/noir_stdlib/src/meta/expr.nr index bb795a520d3..a6bb4630e81 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/expr.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/expr.nr @@ -669,7 +669,12 @@ comptime fn new_unary_op(op: UnaryOp, rhs: Expr) -> Expr { comptime fn new_unsafe(exprs: [Expr]) -> Expr { let exprs = join_expressions(exprs, quote { ; }); - quote { unsafe { $exprs }}.as_expr().unwrap() + quote { unsafe { + //@safety: generated by macro + $exprs + }} + .as_expr() + .unwrap() } comptime fn join_expressions(exprs: [Expr], separator: Quoted) -> Quoted { diff --git a/noir/noir-repo/noir_stdlib/src/meta/mod.nr b/noir/noir-repo/noir_stdlib/src/meta/mod.nr index 5d2164a510d..f2234300ab2 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/mod.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/mod.nr @@ -52,7 +52,7 @@ pub comptime fn derive(s: StructDefinition, traits: [TraitDefinition]) -> Quoted let mut result = quote {}; for trait_to_derive in traits { - let handler = unsafe { HANDLERS.get(trait_to_derive) }; + let handler = HANDLERS.get(trait_to_derive); assert(handler.is_some(), f"No derive function registered for `{trait_to_derive}`"); let trait_impl = handler.unwrap()(s); diff --git a/noir/noir-repo/noir_stdlib/src/uint128.nr b/noir/noir-repo/noir_stdlib/src/uint128.nr index 06febb3ee00..fab8cacc055 100644 --- a/noir/noir-repo/noir_stdlib/src/uint128.nr +++ b/noir/noir-repo/noir_stdlib/src/uint128.nr @@ -1,5 +1,6 @@ use crate::cmp::{Eq, Ord, Ordering}; use crate::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Not, Rem, Shl, Shr, Sub}; +use super::convert::AsPrimitive; global pow64: Field = 18446744073709551616; //2^64; global pow63: Field = 9223372036854775808; // 2^63; @@ -103,8 +104,16 @@ impl U128 { if ascii < 58 { ascii - 48 } else { - let ascii = - ascii + 32 * (unsafe { U128::uconstrained_check_is_upper_ascii(ascii) as u8 }); + let ascii = ascii + + 32 + * ( + unsafe { + /*@safety : optionally adds 32 and then check (below) the result is in 'a..f' range + This enforces that the input is in either 'A..F' or 'a..f' range + */ + U128::uconstrained_check_is_upper_ascii(ascii) as u8 + } + ); assert(ascii >= 97); // enforce >= 'a' assert(ascii <= 102); // enforce <= 'f' ascii - 87 @@ -139,8 +148,11 @@ impl U128 { } } - pub fn from_integer(i: T) -> U128 { - let f = crate::as_field(i); + pub fn from_integer(i: T) -> U128 + where + T: AsPrimitive, + { + let f = i.as_(); // Reject values which would overflow a u128 f.assert_max_bit_size::<128>(); let lo = f as u64 as Field; @@ -148,8 +160,11 @@ impl U128 { U128 { lo, hi } } - pub fn to_integer(self) -> T { - crate::from_field(self.lo + self.hi * pow64) + pub fn to_integer(self) -> T + where + Field: AsPrimitive, + { + AsPrimitive::as_(self.lo + self.hi * pow64) } fn wrapping_mul(self: Self, b: U128) -> U128 { @@ -206,6 +221,9 @@ impl Mul for U128 { impl Div for U128 { fn div(self: Self, b: U128) -> U128 { unsafe { + /*@safety : euclidian division is asserted to be correct: assert(a == b * q + r); and assert(r < b); + Furthermore, U128 addition and multiplication ensures that b * q + r does not overflow + */ let (q, r) = self.unconstrained_div(b); let a = b * q + r; assert_eq(self, a); @@ -218,6 +236,7 @@ impl Div for U128 { impl Rem for U128 { fn rem(self: Self, b: U128) -> U128 { unsafe { + //@safety : cf div() above let (q, r) = self.unconstrained_div(b); let a = b * q + r; assert_eq(self, a); @@ -437,6 +456,7 @@ mod tests { let c = U128::one(); let d = U128::from_u64s_le(0x0, 0x1); unsafe { + //@safety: testing context let (q, r) = a.unconstrained_div(b); assert_eq(q, c); assert_eq(r, d); @@ -446,12 +466,14 @@ mod tests { let b = U128::one(); // Check the case where a is a multiple of b unsafe { + //@safety: testing context let (c, d) = a.unconstrained_div(b); assert_eq((c, d), (a, U128::zero())); } // Check where b is a multiple of a unsafe { + //@safety: testing context let (c, d) = b.unconstrained_div(a); assert_eq((c, d), (U128::zero(), b)); } @@ -460,6 +482,7 @@ mod tests { let a = U128::from_u64s_le(0x1, 0x0); let b = U128::zero(); unsafe { + //@safety: testing context let (c, d) = a.unconstrained_div(b); assert_eq((c, d), (U128::zero(), U128::zero())); } @@ -467,6 +490,7 @@ mod tests { let a = U128::from_u64s_le(0x0, pow63 as u64); let b = U128::from_u64s_le(0x0, pow63 as u64); unsafe { + //@safety: testing context let (c, d) = a.unconstrained_div(b); assert_eq((c, d), (U128::one(), U128::zero())); } diff --git a/noir/noir-repo/test_programs/compilation_report.sh b/noir/noir-repo/test_programs/compilation_report.sh index d050e7c9c34..360eecd2849 100755 --- a/noir/noir-repo/test_programs/compilation_report.sh +++ b/noir/noir-repo/test_programs/compilation_report.sh @@ -10,7 +10,7 @@ tests_to_profile=("sha256_regression" "regression_4709" "ram_blowup_regression") echo "{\"compilation_reports\": [ " > $current_dir/compilation_report.json # If there is an argument that means we want to generate a report for only the current directory -if [ "$#" -ne 0 ]; then +if [ "$1" == "1" ]; then base_path="$current_dir" tests_to_profile=(".") fi @@ -32,12 +32,30 @@ for dir in ${tests_to_profile[@]}; do # The default package to run is the supplied list hardcoded at the top of the script PACKAGE_NAME=$dir # Otherwise default to the current directory as the package we want to run - if [ "$#" -ne 0 ]; then + if [ "$1" == "1" ]; then PACKAGE_NAME=$(basename $current_dir) fi - COMPILE_TIME=$((time nargo compile --force --silence-warnings) 2>&1 | grep real | grep -oE '[0-9]+m[0-9]+.[0-9]+s') - echo -e " {\n \"artifact_name\":\"$PACKAGE_NAME\",\n \"time\":\"$COMPILE_TIME\"" >> $current_dir/compilation_report.json + NUM_RUNS=1 + TOTAL_TIME=0 + + # Passing a second argument will take an average of five runs + # rather than + if [ "$2" == "1" ]; then + NUM_RUNS=5 + fi + + for ((i = 1; i <= NUM_RUNS; i++)); do + COMPILE_TIME=$((time nargo compile --force --silence-warnings) 2>&1 | grep real | grep -oE '[0-9]+m[0-9]+.[0-9]+s') + # Convert time to seconds and add to total time + TOTAL_TIME=$(echo "$TOTAL_TIME + $(echo $COMPILE_TIME | sed -E 's/([0-9]+)m([0-9.]+)s/\1 * 60 + \2/' | bc)" | bc) + done + + AVG_TIME=$(echo "$TOTAL_TIME / $NUM_RUNS" | bc -l) + # Keep only last three decimal points + AVG_TIME=$(awk '{printf "%.3f\n", $1}' <<< "$AVG_TIME") + + echo -e " {\n \"artifact_name\":\"$PACKAGE_NAME\",\n \"time\":\""$AVG_TIME"s\"" >> $current_dir/compilation_report.json if (($ITER == $NUM_ARTIFACTS)); then echo "}" >> $current_dir/compilation_report.json diff --git a/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr b/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr index f262635e508..29431005c29 100644 --- a/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/brillig_mut_ref_from_acir/src/main.nr @@ -4,6 +4,7 @@ unconstrained fn mut_ref_identity(value: &mut Field) -> Field { fn main(mut x: Field, y: pub Field) { let returned_x = unsafe { + //@safety: testing context mut_ref_identity(&mut x) }; assert(returned_x == x); diff --git a/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr b/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr index cf79c22683f..be1adb119aa 100644 --- a/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/regression_5008/src/main.nr @@ -14,6 +14,7 @@ fn main() { let foo = Foo { bar: &mut Bar { value: 0 } }; unsafe { + //@safety: testing context foo.crash_fn() }; } diff --git a/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr b/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr index 9a4f42469b5..282d598b484 100644 --- a/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr +++ b/noir/noir-repo/test_programs/compile_failure/unconstrained_ref/src/main.nr @@ -5,6 +5,7 @@ unconstrained fn uncon_ref() -> &mut Field { fn main() { let e = unsafe { + //@safety: testing context uncon_ref() }; } diff --git a/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/acir_inside_brillig_recursion/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/acir_inside_brillig_recursion/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/acir_inside_brillig_recursion/src/main.nr similarity index 88% rename from noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/acir_inside_brillig_recursion/src/main.nr index 49b7c00b6b9..b4cc69bb00a 100644 --- a/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/acir_inside_brillig_recursion/src/main.nr @@ -1,5 +1,6 @@ fn main() { unsafe { + //@safety: testing context assert_eq(fibonacci(3), fibonacci_hint(3)); } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr index f1a6814f4e7..581ca8dc16a 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_cast/src/main.nr @@ -3,6 +3,7 @@ // The features being tested are cast operations on brillig fn main() { unsafe { + //@safety: testing context bool_casts(); field_casts(); uint_casts(); diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr index fedfe48cb0d..15f1b23853d 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_field_binary_operations/src/main.nr @@ -1,6 +1,7 @@ // Tests arithmetic operations on fields fn main() { unsafe { + //@safety: testing context let x = 4; let y = 2; assert((x + y) == add(x, y)); diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr index 7ecc21dbd2f..4ee04807355 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_integer_binary_operations/src/main.nr @@ -4,6 +4,7 @@ fn main() { let y: u32 = 2; unsafe { + //@safety: testing context assert((x + y) == add(x, y)); assert((x - y) == sub(x, y)); diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr index 2ad43bdb7eb..47f2466f453 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_modulo/src/main.nr @@ -3,6 +3,7 @@ // The features being tested is modulo operations on brillig fn main() { unsafe { + //@safety: testing context assert(modulo(47, 3) == 2); assert(modulo(2, 3) == 2); assert(signed_modulo(5, 3) == 2); diff --git a/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr index 436a939767e..41bd2d8c3ef 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/brillig_slice_input/src/main.nr @@ -17,12 +17,14 @@ fn main() { let mut slice = &[]; slice = slice.push_back([Point { x: 13, y: 14 }, Point { x: 20, y: 8 }]); unsafe { + //@safety: testing context let brillig_sum = sum_slice(slice); assert_eq(brillig_sum, 55); } slice = slice.push_back([Point { x: 15, y: 5 }, Point { x: 12, y: 13 }]); unsafe { + //@safety: testing context let brillig_sum = sum_slice(slice); assert_eq(brillig_sum, 100); } diff --git a/noir/noir-repo/test_programs/execution_success/cast_and_shift_global/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/cast_and_shift_global/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/cast_and_shift_global/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/cast_and_shift_global/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/cast_and_shift_global/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/cast_and_shift_global/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/cast_and_shift_global/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/cast_and_shift_global/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/check_large_field_bits/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/check_large_field_bits/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/check_large_field_bits/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/check_large_field_bits/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/check_large_field_bits/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/check_large_field_bits/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/check_large_field_bits/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/check_large_field_bits/src/main.nr diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/Nargo.toml new file mode 100644 index 00000000000..294832ba329 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "comptime_as_field" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/src/main.nr new file mode 100644 index 00000000000..e13db54b80b --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_as_field/src/main.nr @@ -0,0 +1,6 @@ +fn main() { + comptime { + let _: U128 = U128::from_integer(1); + } +} + diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/Nargo.toml new file mode 100644 index 00000000000..38a46ba0dbe --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "comptime_from_field" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/src/main.nr new file mode 100644 index 00000000000..722c5c7eb32 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_from_field/src/main.nr @@ -0,0 +1,6 @@ +fn main() { + comptime { + let _: Field = U128::from_hex("0x0").to_integer(); + } +} + diff --git a/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_slice_equality/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/comptime_slice_equality/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/comptime_slice_equality/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/comptime_slice_equality/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_slice_equality/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/comptime_slice_equality/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/comptime_slice_equality/src/main.nr diff --git a/noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/Nargo.toml new file mode 100644 index 00000000000..aaebee8d6ef --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "double_generic_alias_in_path" +type = "bin" +authors = [""] +compiler_version = ">=0.32.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/src/main.nr new file mode 100644 index 00000000000..09f2e5c4b43 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/double_generic_alias_in_path/src/main.nr @@ -0,0 +1,14 @@ +struct Foo {} + +impl Foo { + fn new() -> Self { + Self {} + } +} + +type FooAlias1 = Foo; +type FooAlias2 = FooAlias1; + +fn main() { + let _ = FooAlias2::new(); +} diff --git a/noir/noir-repo/test_programs/execution_success/empty/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/empty/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/empty/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/empty/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/empty/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/empty/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/empty/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/empty/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/is_unconstrained/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/is_unconstrained/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/is_unconstrained/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/is_unconstrained/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/is_unconstrained/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/is_unconstrained/src/main.nr similarity index 89% rename from noir/noir-repo/test_programs/execution_success/is_unconstrained/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/is_unconstrained/src/main.nr index d06366cf642..dddf2dfec2e 100644 --- a/noir/noir-repo/test_programs/execution_success/is_unconstrained/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/is_unconstrained/src/main.nr @@ -10,6 +10,7 @@ unconstrained fn unconstrained_intermediate() { fn main() { unsafe { + //@safety: testing context unconstrained_intermediate(); } check(false); diff --git a/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr index 799091fca09..9e4439e67cd 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/macros_in_comptime/src/main.nr @@ -6,7 +6,10 @@ global three_field: Field = 3; fn main() { comptime { - unsafe { foo::(5) }; + unsafe { + //@safety: testing context + foo::(5) + }; submodule::bar(); } } diff --git a/noir/noir-repo/test_programs/execution_success/regression_5462/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/regression_5462/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/regression_5462/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/regression_5462/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/regression_5462/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_5462/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/regression_5462/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/regression_5462/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/trait_inheritance/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/trait_inheritance/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/trait_inheritance/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/trait_inheritance/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/trait_inheritance/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/trait_inheritance/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/trait_inheritance/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/trait_inheritance/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/unit_value/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/unit_value/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/execution_success/unit_value/Nargo.toml rename to noir/noir-repo/test_programs/compile_success_empty/unit_value/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_success/unit_value/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/unit_value/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/execution_success/unit_value/src/main.nr rename to noir/noir-repo/test_programs/compile_success_empty/unit_value/src/main.nr diff --git a/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr index 33b84c2b702..b8c4137aa74 100644 --- a/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_no_bug/check_uncostrained_regression/src/main.nr @@ -16,6 +16,7 @@ unconstrained fn convert(trigger: Trigger) -> ResultType { impl Trigger { fn execute(self) -> ResultType { let result = unsafe { + //@safety: testing context convert(self) }; assert(result.a == self.x + 1); diff --git a/noir/noir-repo/test_programs/compile_success_with_bug/underconstrained_value_detector_5425/src/main.nr b/noir/noir-repo/test_programs/compile_success_with_bug/underconstrained_value_detector_5425/src/main.nr index 1d9269eebd5..7047be3a577 100644 --- a/noir/noir-repo/test_programs/compile_success_with_bug/underconstrained_value_detector_5425/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_with_bug/underconstrained_value_detector_5425/src/main.nr @@ -10,6 +10,7 @@ unconstrained fn maximum_price(options: [u32; 3]) -> u32 { fn main(sandwiches: pub [u32; 3], drinks: pub [u32; 3], snacks: pub [u32; 3], best_value: u32) { unsafe { + //@safety: testing context let meal_deal_cost: u32 = 390; let most_expensive_sandwich = maximum_price(sandwiches); let mut sandwich_exists = false; diff --git a/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr b/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr index 07256f0c398..4ca70baae2e 100644 --- a/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/brillig_assert_fail/src/main.nr @@ -4,6 +4,7 @@ fn main(x: Field) { assert( 1 == unsafe { + //@safety: testing context conditional(x as bool) } ); diff --git a/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr b/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr index 4dd06ceb743..d726e699bab 100644 --- a/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/brillig_assert_msg_runtime/src/main.nr @@ -1,6 +1,7 @@ fn main(x: Field) { assert( 1 == unsafe { + //@safety: testing context conditional(x) } ); diff --git a/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr b/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr index 1c1563ffe1d..b87744399c6 100644 --- a/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr @@ -15,6 +15,7 @@ fn fold_conditional_wrapper(x: bool) -> Field { #[fold] fn fold_conditional(x: bool) -> Field { unsafe { + //@safety: testing context conditional_wrapper(x) } } diff --git a/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr b/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr index fbcdba43355..a270a9fccad 100644 --- a/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr +++ b/noir/noir-repo/test_programs/execution_failure/regression_5202/src/main.nr @@ -14,12 +14,14 @@ unconstrained fn should_i_assert() -> bool { fn get_magical_boolean() -> bool { let option = unsafe { + //@safety: testing context get_unconstrained_option() }; let pre_assert = option.is_some().to_field(); if unsafe { + //@safety: testing context should_i_assert() } { // Note that `should_i_assert` is unconstrained, so Noir should not be able to infer diff --git a/noir/noir-repo/test_programs/execution_report.sh b/noir/noir-repo/test_programs/execution_report.sh new file mode 100755 index 00000000000..073871e60f8 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_report.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -e + +current_dir=$(pwd) +base_path="$current_dir/execution_success" + +# Tests to be profiled for execution report +tests_to_profile=("sha256_regression" "regression_4709" "ram_blowup_regression") + +echo "{\"execution_reports\": [ " > $current_dir/execution_report.json + +# If there is an argument that means we want to generate a report for only the current directory +if [ "$1" == "1" ]; then + base_path="$current_dir" + tests_to_profile=(".") +fi + +ITER="1" +NUM_ARTIFACTS=${#tests_to_profile[@]} + +for dir in ${tests_to_profile[@]}; do + if [[ " ${excluded_dirs[@]} " =~ " ${dir} " ]]; then + continue + fi + + if [[ ${CI-false} = "true" ]] && [[ " ${ci_excluded_dirs[@]} " =~ " ${dir} " ]]; then + continue + fi + + cd $base_path/$dir + + # The default package to run is the supplied list hardcoded at the top of the script + PACKAGE_NAME=$dir + # Otherwise default to the current directory as the package we want to run + if [ "$1" == "1" ]; then + PACKAGE_NAME=$(basename $current_dir) + fi + + # Check whether a compilation artifact exists. + # Any programs part of this benchmark should already be compiled. + # We want to make sure that compilation time is not included in the execution time. + if [ ! -e ./target/*.json ]; then + echo "Missing compilation artifact for $PACKAGE_NAME" + exit 1 + fi + + + NUM_RUNS=1 + TOTAL_TIME=0 + + if [ "$2" == "1" ]; then + NUM_RUNS=5 + fi + + for ((i = 1; i <= NUM_RUNS; i++)); do + EXECUTION_TIME=$((time nargo execute --silence-warnings) 2>&1 | grep real | grep -oE '[0-9]+m[0-9]+.[0-9]+s') + # Convert to seconds and add to total time + TOTAL_TIME=$(echo "$TOTAL_TIME + $(echo $EXECUTION_TIME | sed -E 's/([0-9]+)m([0-9.]+)s/\1 * 60 + \2/' | bc)" | bc) + done + + AVG_TIME=$(echo "$TOTAL_TIME / $NUM_RUNS" | bc -l) + # Keep only last three decimal points + AVG_TIME=$(awk '{printf "%.3f\n", $1}' <<< "$AVG_TIME") + + echo -e " {\n \"artifact_name\":\"$PACKAGE_NAME\",\n \"time\":\""$AVG_TIME"s\"" >> $current_dir/execution_report.json + + if (($ITER == $NUM_ARTIFACTS)); then + echo "}" >> $current_dir/execution_report.json + else + echo "}, " >> $current_dir/execution_report.json + fi + + ITER=$(( $ITER + 1 )) +done + +echo "]}" >> $current_dir/execution_report.json diff --git a/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/Prover.toml b/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/Prover.toml deleted file mode 100644 index 8b137891791..00000000000 --- a/noir/noir-repo/test_programs/execution_success/acir_inside_brillig_recursion/Prover.toml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr b/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr index 3d3992971f7..7738bed5255 100644 --- a/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/aes128_encrypt/src/main.nr @@ -29,9 +29,15 @@ fn main(inputs: str<12>, iv: str<16>, key: str<16>, output: str<32>) { let result: [u8; 16] = std::aes128::aes128_encrypt(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()).as_array(); - let output_bytes: [u8; 16] = unsafe { decode_hex(output) }; + let output_bytes: [u8; 16] = unsafe { + //@safety: testing context + decode_hex(output) + }; assert(result == output_bytes); - let unconstrained_result = unsafe { cipher(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()) }; + let unconstrained_result = unsafe { + //@safety: testing context + cipher(inputs.as_bytes(), iv.as_bytes(), key.as_bytes()) + }; assert(unconstrained_result == output_bytes); } diff --git a/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr index 09f99622939..425c48c21f1 100644 --- a/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr @@ -5,6 +5,7 @@ unconstrained fn return_array(val: Field) -> [Field; 1] { fn main(val: Field) { unsafe { + //@safety: testing context let array = return_array(val); assert_constant(array.as_slice().len()); } diff --git a/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr b/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr index 5a8b9fb67ef..65cf990dcaf 100644 --- a/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/bigint/src/main.nr @@ -18,6 +18,7 @@ fn main(mut x: [u8; 5], y: [u8; 5]) { test_unconstrained1(a, b) } else { unsafe { + //@safety: testing context test_unconstrained2(a, b) } }; diff --git a/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr index 1a595ecfb38..bcb995591c7 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_acir_as_brillig/src/main.nr @@ -1,5 +1,6 @@ fn main(x: u32) { unsafe { + //@safety: testing context assert(entry_point(x) == 2); swap_entry_point(x, x + 1); assert(deep_entry_point(x) == 4); diff --git a/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr index 00d43e2b10d..b4cd13e1091 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_arrays/src/main.nr @@ -3,6 +3,7 @@ // The features being tested are array reads and writes fn main(x: [Field; 3]) { unsafe { + //@safety: testing context read_array(x); read_write_array(x); } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr index c4412bd2bba..58f45e8d667 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_blake2s/src/main.nr @@ -3,6 +3,7 @@ // The features being tested is blake2s in brillig fn main(x: [u8; 5], result: [u8; 32]) { unsafe { + //@safety: testing context assert(blake2s(x) == result); } } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr index c76d7651b0a..5bd3458da51 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_calls/src/main.nr @@ -3,6 +3,7 @@ // The features being tested is brillig calls fn main(x: u32) { unsafe { + /*@safety : testing context*/ assert(entry_point(x) == 2); swap_entry_point(x, x + 1); assert(deep_entry_point(x) == 4); diff --git a/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr index 90f588ab988..8d5e01dd7c4 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_calls_array/src/main.nr @@ -3,6 +3,7 @@ // The features being tested is brillig calls passing arrays around fn main(x: [u32; 3]) { unsafe { + //@safety: testing context assert(entry_point(x) == 9); another_entry_point(x); } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr index 0fbfd84968f..e03f04a0cbc 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_calls_conditionals/src/main.nr @@ -3,6 +3,7 @@ // The features being tested is brillig calls with conditionals fn main(x: [u32; 3]) { unsafe { + //@safety: testing context assert(entry_point(x[0]) == 7); assert(entry_point(x[1]) == 8); assert(entry_point(x[2]) == 9); diff --git a/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr index 17484f6e8c5..5934f982f88 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_conditional/src/main.nr @@ -3,6 +3,7 @@ // The features being tested is basic conditonal on brillig fn main(x: Field) { unsafe { + //@safety: testing context assert(4 == conditional(x == 1)); } } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr index 03a92b4b10d..b56542fd0ae 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_fns_as_values/src/main.nr @@ -4,6 +4,7 @@ struct MyStruct { fn main(x: u32) { unsafe { + //@safety: testing context assert(wrapper(increment, x) == x + 1); assert(wrapper(increment_acir, x) == x + 1); assert(wrapper(decrement, x) == x - 1); diff --git a/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr index f3b45d0de4e..7bf584579d3 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_identity_function/src/main.nr @@ -7,6 +7,7 @@ struct myStruct { // The features being tested is the identity function in Brillig fn main(x: Field) { unsafe { + //@safety: testing context assert(x == identity(x)); // TODO: add support for array comparison let arr = identity_array([x, x]); diff --git a/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr index 77ab4ea19a6..e97db35f800 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_nested_arrays/src/main.nr @@ -29,6 +29,7 @@ unconstrained fn create_and_assert_inside_brillig(x: Field, y: Field) { fn main(x: Field, y: Field) { unsafe { + //@safety: testing context let header = Header { params: [1, 2, 3] }; let note0 = MyNote { array: [1, 2], plain: 3, header }; let note1 = MyNote { array: [4, 5], plain: 6, header }; diff --git a/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr index 4e61b6a54ea..38641f5ee5f 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_not/src/main.nr @@ -3,6 +3,7 @@ // The features being tested is not instruction on brillig fn main(x: Field, y: Field) { unsafe { + //@safety: testing context assert(false == not_operator(x as bool)); assert(true == not_operator(y as bool)); } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr index 8f5b2fa7566..e59c908fd6f 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_oracle/src/main.nr @@ -4,6 +4,7 @@ use std::test::OracleMock; // Tests oracle usage in brillig/unconstrained functions fn main(_x: Field) { unsafe { + //@safety: testing context let size = 20; // TODO: Add a method along the lines of `(0..size).to_array()`. let mut mock_oracle_response = [0; 20]; diff --git a/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr index 5354777a1de..fa048c30abe 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_recursion/src/main.nr @@ -3,6 +3,7 @@ // The feature being tested is brillig recursion fn main(x: u32) { unsafe { + //@safety: testing context assert(fibonacci(x) == 55); } } diff --git a/noir/noir-repo/test_programs/execution_success/brillig_uninitialized_arrays/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_uninitialized_arrays/src/main.nr index d4b74162cfb..839ac489f2d 100644 --- a/noir/noir-repo/test_programs/execution_success/brillig_uninitialized_arrays/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/brillig_uninitialized_arrays/src/main.nr @@ -1,5 +1,6 @@ fn main(x: Field, y: Field) -> pub Field { unsafe { + //@safety: testing context let notes = create_notes(x, y); sum_x(notes, x, y) } diff --git a/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr b/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr index 2eaab810d6a..70423810327 100644 --- a/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/global_consts/src/main.nr @@ -25,7 +25,10 @@ unconstrained fn calculate_global_value() -> Field { } // Regression test for https://github.com/noir-lang/noir/issues/4318 -global CALCULATED_GLOBAL: Field = unsafe { calculate_global_value() }; +global CALCULATED_GLOBAL: Field = unsafe { + //@safety: testing context + calculate_global_value() +}; fn main( a: [Field; M + N - N], diff --git a/noir/noir-repo/test_programs/execution_success/hint_black_box/src/main.nr b/noir/noir-repo/test_programs/execution_success/hint_black_box/src/main.nr index 1109c54301f..d53cf335878 100644 --- a/noir/noir-repo/test_programs/execution_success/hint_black_box/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/hint_black_box/src/main.nr @@ -23,7 +23,10 @@ fn main(a: u32, b: u32) { //assert_eq(slice_sum(black_box(arr.as_slice())), b); // But we can pass a blackboxed slice to Brillig. - let s = unsafe { brillig_slice_sum(black_box(arr.as_slice())) }; + let s = unsafe { + //@safety: testing context + brillig_slice_sum(black_box(arr.as_slice())) + }; assert_eq(s, b); let mut d = b; diff --git a/noir/noir-repo/test_programs/execution_success/is_unconstrained/Prover.toml b/noir/noir-repo/test_programs/execution_success/is_unconstrained/Prover.toml deleted file mode 100644 index 8b137891791..00000000000 --- a/noir/noir-repo/test_programs/execution_success/is_unconstrained/Prover.toml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr b/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr index f8070aa3ef4..aec0ba3be33 100644 --- a/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/nested_arrays_from_brillig/src/main.nr @@ -20,7 +20,10 @@ unconstrained fn create_inside_brillig(values: [Field; 6]) -> [MyNote; 2] { } fn main(values: [Field; 6]) { - let notes = unsafe { create_inside_brillig(values) }; + let notes = unsafe { + //@safety: testing context + create_inside_brillig(values) + }; assert(access_nested(notes) == (2 + 4 + 3 + 1)); } diff --git a/noir/noir-repo/test_programs/execution_success/reference_only_used_as_alias/src/main.nr b/noir/noir-repo/test_programs/execution_success/reference_only_used_as_alias/src/main.nr index 8b5b804cf94..c310f052fce 100644 --- a/noir/noir-repo/test_programs/execution_success/reference_only_used_as_alias/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/reference_only_used_as_alias/src/main.nr @@ -62,6 +62,7 @@ where { |e: Event| { unsafe { + //@safety: testing context func(context.a); } emit_with_keys(context, randomness, e, compute); diff --git a/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr index 895a6983ad9..4e90002e2a2 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/regression_5435/src/main.nr @@ -3,7 +3,10 @@ fn main(input: Field, enable: bool) { let hash = no_predicate_function(input); // `EnableSideEffects` instruction from above instruction leaks out and removes the predicate from this call, // resulting in execution failure. - unsafe { fail(hash) }; + unsafe { + //@safety: testing context + fail(hash) + }; } } diff --git a/noir/noir-repo/test_programs/execution_success/regression_6451/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_6451/src/main.nr index b13b6c25a7e..192e09fe3f5 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_6451/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/regression_6451/src/main.nr @@ -10,7 +10,10 @@ fn main(x: Field) { value.assert_max_bit_size::<1>(); // Regression test for #6447 (Aztec Packages issue #9488) - let y = unsafe { empty(x + 1) }; + let y = unsafe { + //@safety: testing context + empty(x + 1) + }; let z = y + x + 1; let z1 = z + y; assert(z + z1 != 3); diff --git a/noir/noir-repo/test_programs/execution_success/regression_6674_3/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_6674_3/src/main.nr index 2c87a4c679c..a0f2d0668d5 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_6674_3/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/regression_6674_3/src/main.nr @@ -166,7 +166,10 @@ pub unconstrained fn sort_by_counter_desc(array: [T; N]) -> [T; N pub unconstrained fn sort_by(array: [T; N]) -> [T; N] { let mut result = array; - unsafe { get_sorting_index(array) }; + unsafe { + //@safety: testing context + get_sorting_index(array) + }; result } diff --git a/noir/noir-repo/test_programs/execution_success/regression_6834/Nargo.toml b/noir/noir-repo/test_programs/execution_success/regression_6834/Nargo.toml new file mode 100644 index 00000000000..edc3257d52d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_6834/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_6834" +version = "0.1.0" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/regression_6834/Prover.toml b/noir/noir-repo/test_programs/execution_success/regression_6834/Prover.toml new file mode 100644 index 00000000000..9ef840487ad --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_6834/Prover.toml @@ -0,0 +1,2 @@ +year = 1 +min_age = 1 diff --git a/noir/noir-repo/test_programs/execution_success/regression_6834/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_6834/src/main.nr new file mode 100644 index 00000000000..cea8d163ab5 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_6834/src/main.nr @@ -0,0 +1,9 @@ +fn main(year: u32, min_age: u8) -> pub u32 { + if min_age == 0 { + (year - 2) / 4 + } else if year > 1 { + (year - 2) / 4 + } else { + 0 + } +} diff --git a/noir/noir-repo/test_programs/execution_success/regression_unsafe_no_predicates/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_unsafe_no_predicates/src/main.nr index fc1e55ee641..c0d8b336a04 100644 --- a/noir/noir-repo/test_programs/execution_success/regression_unsafe_no_predicates/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/regression_unsafe_no_predicates/src/main.nr @@ -7,7 +7,10 @@ fn main(x: u8, nest: bool) { #[no_predicates] pub fn unsafe_assert(msg: [u8; N]) -> u8 { - let block = unsafe { get_block(msg) }; + let block = unsafe { + //@safety: testing context + get_block(msg) + }; verify_block(msg, block); block[0] } diff --git a/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr b/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr index ca41c708077..c6a212f908e 100644 --- a/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/u16_support/src/main.nr @@ -1,6 +1,7 @@ fn main(x: u16) { test_u16(x); unsafe { + //@safety: testing context test_u16_unconstrained(x); } } diff --git a/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr b/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr index 689ba9d4a04..d1ba1c6175d 100644 --- a/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/uhashmap/src/main.nr @@ -293,7 +293,10 @@ unconstrained fn doc_tests() { // docs:start:get_example fn get_example(map: UHashMap>) { - let x = unsafe { map.get(12) }; + let x = unsafe { + //@safety: testing context + map.get(12) + }; if x.is_some() { assert(x.unwrap() == 42); @@ -318,7 +321,11 @@ fn entries_examples(map: UHashMap {value}"); } // docs:end:keys_example diff --git a/noir/noir-repo/test_programs/gates_report_brillig.sh b/noir/noir-repo/test_programs/gates_report_brillig.sh index d3f6344dbf4..7343857a3c5 100755 --- a/noir/noir-repo/test_programs/gates_report_brillig.sh +++ b/noir/noir-repo/test_programs/gates_report_brillig.sh @@ -28,6 +28,6 @@ done echo "]" >> Nargo.toml -nargo info --force-brillig --json > gates_report_brillig.json +nargo info --force-brillig --json | jq -r ".programs[].functions = []" > gates_report_brillig.json rm Nargo.toml diff --git a/noir/noir-repo/test_programs/gates_report_brillig_execution.sh b/noir/noir-repo/test_programs/gates_report_brillig_execution.sh index b3f4a8bda98..6a5a699e2d8 100755 --- a/noir/noir-repo/test_programs/gates_report_brillig_execution.sh +++ b/noir/noir-repo/test_programs/gates_report_brillig_execution.sh @@ -38,6 +38,6 @@ done echo "]" >> Nargo.toml -nargo info --profile-execution --json > gates_report_brillig_execution.json +nargo info --profile-execution --json | jq -r ".programs[].functions = []" > gates_report_brillig_execution.json -rm Nargo.toml \ No newline at end of file +rm Nargo.toml diff --git a/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr index 709180879a0..39167242a7b 100644 --- a/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/comptime_expr/src/main.nr @@ -597,7 +597,12 @@ mod tests { fn test_expr_as_unsafe() { comptime { - let expr = quote { unsafe { 1; 4; 23 } }.as_expr().unwrap(); + let expr = quote { + unsafe + { + //@safety" test + 1; 4; 23 } + }.as_expr().unwrap(); let exprs = expr.as_unsafe().unwrap(); assert_eq(exprs.len(), 3); } @@ -607,7 +612,9 @@ mod tests { fn test_expr_modify_for_unsafe() { comptime { - let expr = quote { unsafe { 1; 4; 23 } }.as_expr().unwrap(); + let expr = quote { unsafe { + //@safety: test + 1; 4; 23 } }.as_expr().unwrap(); let expr = expr.modify(times_two); let exprs = expr.as_unsafe().unwrap(); assert_eq(exprs.len(), 3); diff --git a/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr index 1b427043e91..b026f510418 100644 --- a/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/mock_oracle/src/main.nr @@ -35,6 +35,7 @@ unconstrained fn struct_field(point: Point, array: [Field; 4]) -> Field { #[test(should_fail)] fn test_mock_no_returns() { unsafe { + //@safety: testing context OracleMock::mock("void_field"); void_field(); // Some return value must be set } @@ -43,6 +44,7 @@ fn test_mock_no_returns() { #[test] fn test_mock() { unsafe { + //@safety: testing context OracleMock::mock("void_field").returns(10); assert_eq(void_field(), 10); } @@ -51,6 +53,7 @@ fn test_mock() { #[test] fn test_multiple_mock() { unsafe { + //@safety: testing context let first_mock = OracleMock::mock("void_field").returns(10); OracleMock::mock("void_field").returns(42); @@ -65,6 +68,7 @@ fn test_multiple_mock() { #[test] fn test_multiple_mock_times() { unsafe { + //@safety: testing context OracleMock::mock("void_field").returns(10).times(2); OracleMock::mock("void_field").returns(42); @@ -77,6 +81,7 @@ fn test_multiple_mock_times() { #[test] fn test_mock_with_params() { unsafe { + //@safety: testing context OracleMock::mock("field_field").with_params((5,)).returns(10); assert_eq(field_field(5), 10); } @@ -85,6 +90,7 @@ fn test_mock_with_params() { #[test] fn test_multiple_mock_with_params() { unsafe { + //@safety: testing context OracleMock::mock("field_field").with_params((5,)).returns(10); OracleMock::mock("field_field").with_params((7,)).returns(14); @@ -96,6 +102,7 @@ fn test_multiple_mock_with_params() { #[test] fn test_mock_last_params() { unsafe { + //@safety: testing context let mock = OracleMock::mock("field_field").returns(10); assert_eq(field_field(5), 10); @@ -106,6 +113,7 @@ fn test_mock_last_params() { #[test] fn test_mock_last_params_many_calls() { unsafe { + //@safety: testing context let mock = OracleMock::mock("field_field").returns(10); assert_eq(field_field(5), 10); assert_eq(field_field(7), 10); @@ -123,6 +131,7 @@ fn test_mock_struct_field() { let point = Point { x: 14, y: 27 }; unsafe { + //@safety: testing context OracleMock::mock("struct_field").returns(42).times(2); let timeless_mock = OracleMock::mock("struct_field").returns(0); assert_eq(42, struct_field(point, array)); diff --git a/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr b/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr index fb90e3d96c5..99277b62939 100644 --- a/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr +++ b/noir/noir-repo/test_programs/noir_test_success/out_of_bounds_alignment/src/main.nr @@ -14,6 +14,7 @@ fn test_acir() { #[test(should_fail)] fn test_brillig() { unsafe { + //@safety: testing context assert_eq(out_of_bounds_unconstrained_wrapper([0; 50], [0; 50]), 0); } } diff --git a/noir/noir-repo/tooling/acvm_cli/src/cli/mod.rs b/noir/noir-repo/tooling/acvm_cli/src/cli/mod.rs index a610b08ab77..f31e123d0cd 100644 --- a/noir/noir-repo/tooling/acvm_cli/src/cli/mod.rs +++ b/noir/noir-repo/tooling/acvm_cli/src/cli/mod.rs @@ -22,7 +22,6 @@ enum ACVMCommand { Execute(execute_cmd::ExecuteCommand), } -#[cfg(not(feature = "codegen-docs"))] pub(crate) fn start_cli() -> eyre::Result<()> { let ACVMCli { command } = ACVMCli::parse(); @@ -32,10 +31,3 @@ pub(crate) fn start_cli() -> eyre::Result<()> { Ok(()) } - -#[cfg(feature = "codegen-docs")] -pub(crate) fn start_cli() -> eyre::Result<()> { - let markdown: String = clap_markdown::help_markdown::(); - println!("{markdown}"); - Ok(()) -} diff --git a/noir/noir-repo/tooling/debugger/build.rs b/noir/noir-repo/tooling/debugger/build.rs index ebdf2036894..63924de8336 100644 --- a/noir/noir-repo/tooling/debugger/build.rs +++ b/noir/noir-repo/tooling/debugger/build.rs @@ -2,7 +2,6 @@ use std::collections::HashSet; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; -use std::{env, fs}; const GIT_COMMIT: &&str = &"GIT_COMMIT"; @@ -14,7 +13,7 @@ fn main() { build_data::no_debug_rebuilds(); } - let out_dir = env::var("OUT_DIR").unwrap(); + let out_dir = std::env::var("OUT_DIR").unwrap(); let destination = Path::new(&out_dir).join("debug.rs"); let mut test_file = File::create(destination).unwrap(); @@ -39,8 +38,8 @@ fn generate_debugger_tests(test_file: &mut File, test_data_dir: &Path) { let test_data_dir = test_data_dir.join(test_sub_dir); let test_case_dirs = - fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); - let ignored_tests_contents = fs::read_to_string("ignored-tests.txt").unwrap(); + std::fs::read_dir(test_data_dir).unwrap().flatten().filter(|c| c.path().is_dir()); + let ignored_tests_contents = std::fs::read_to_string("ignored-tests.txt").unwrap(); let ignored_tests = ignored_tests_contents.lines().collect::>(); for test_dir in test_case_dirs { diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 77c186fe707..256bb148ae9 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -1015,10 +1015,10 @@ mod tests { outputs: vec![], predicate: None, }]; - let brillig_funcs = &vec![brillig_bytecode]; + let brillig_funcs = &[brillig_bytecode]; let current_witness_index = 2; let circuit = Circuit { current_witness_index, opcodes, ..Circuit::default() }; - let circuits = &vec![circuit]; + let circuits = &[circuit]; let debug_symbols = vec![]; let file_map = BTreeMap::new(); @@ -1185,7 +1185,7 @@ mod tests { ]; let current_witness_index = 3; let circuit = Circuit { current_witness_index, opcodes, ..Circuit::default() }; - let circuits = &vec![circuit]; + let circuits = &[circuit]; let debug_symbols = vec![]; let file_map = BTreeMap::new(); @@ -1197,7 +1197,7 @@ mod tests { PrintOutput::Stdout, debug_artifact, )); - let brillig_funcs = &vec![brillig_bytecode]; + let brillig_funcs = &[brillig_bytecode]; let mut context = DebugContext::new( &StubbedBlackBoxSolver, circuits, @@ -1283,7 +1283,7 @@ mod tests { }; let circuits = vec![circuit_one, circuit_two]; let debug_artifact = DebugArtifact { debug_symbols: vec![], file_map: BTreeMap::new() }; - let brillig_funcs = &vec![brillig_one, brillig_two]; + let brillig_funcs = &[brillig_one, brillig_two]; let context = DebugContext::new( &StubbedBlackBoxSolver, diff --git a/noir/noir-repo/tooling/lsp/src/modules.rs b/noir/noir-repo/tooling/lsp/src/modules.rs index cadf71b2eec..b023f3886c3 100644 --- a/noir/noir-repo/tooling/lsp/src/modules.rs +++ b/noir/noir-repo/tooling/lsp/src/modules.rs @@ -41,9 +41,7 @@ pub(crate) fn relative_module_full_path( interner, ); } else { - let Some(parent_module) = get_parent_module(interner, module_def_id) else { - return None; - }; + let parent_module = get_parent_module(interner, module_def_id)?; full_path = relative_module_id_path( parent_module, diff --git a/noir/noir-repo/tooling/lsp/src/requests/hover.rs b/noir/noir-repo/tooling/lsp/src/requests/hover.rs index bb1ea661719..78be09653fc 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/hover.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/hover.rs @@ -5,7 +5,6 @@ use fm::{FileMap, PathString}; use lsp_types::{Hover, HoverContents, HoverParams, MarkupContent, MarkupKind}; use noirc_frontend::{ ast::{ItemVisibility, Visibility}, - elaborator::types::try_eval_array_length_id, hir::def_map::ModuleId, hir_def::{ expr::{HirArrayLiteral, HirExpression, HirLiteral}, @@ -93,9 +92,7 @@ fn format_module(id: ModuleId, args: &ProcessRequestCallbackArgs) -> Option String { string.push('\n'); } + let mut print_comptime = definition.comptime; + let mut opt_value = None; + + // See if we can figure out what's the global's value + if let Some(stmt) = args.interner.get_global_let_statement(id) { + print_comptime = stmt.comptime; + opt_value = get_global_value(args.interner, stmt.expression); + } + string.push_str(" "); - if definition.comptime { + if print_comptime { string.push_str("comptime "); } if definition.mutable { @@ -217,12 +223,9 @@ fn format_global(id: GlobalId, args: &ProcessRequestCallbackArgs) -> String { string.push_str(": "); string.push_str(&format!("{}", typ)); - // See if we can figure out what's the global's value - if let Some(stmt) = args.interner.get_global_let_statement(id) { - if let Some(value) = get_global_value(args.interner, stmt.expression) { - string.push_str(" = "); - string.push_str(&value); - } + if let Some(value) = opt_value { + string.push_str(" = "); + string.push_str(&value); } string.push_str(&go_to_type_links(&typ, args.interner, args.files)); @@ -233,13 +236,6 @@ fn format_global(id: GlobalId, args: &ProcessRequestCallbackArgs) -> String { } fn get_global_value(interner: &NodeInterner, expr: ExprId) -> Option { - let span = interner.expr_span(&expr); - - // Globals as array lengths are extremely common, so we try that first. - if let Ok(result) = try_eval_array_length_id(interner, expr, span) { - return Some(result.to_string()); - } - match interner.expression(&expr) { HirExpression::Literal(literal) => match literal { HirLiteral::Array(hir_array_literal) => { diff --git a/noir/noir-repo/tooling/lsp/src/use_segment_positions.rs b/noir/noir-repo/tooling/lsp/src/use_segment_positions.rs index 246ff653245..2cd406ee773 100644 --- a/noir/noir-repo/tooling/lsp/src/use_segment_positions.rs +++ b/noir/noir-repo/tooling/lsp/src/use_segment_positions.rs @@ -193,10 +193,13 @@ impl UseSegmentPositions { } fn insert_use_segment_position(&mut self, segment: String, position: UseSegmentPosition) { - if self.use_segment_positions.get(&segment).is_none() { - self.use_segment_positions.insert(segment, position); - } else { - self.use_segment_positions.insert(segment, UseSegmentPosition::NoneOrMultiple); + match self.use_segment_positions.entry(segment) { + std::collections::hash_map::Entry::Vacant(e) => { + e.insert(position); + } + std::collections::hash_map::Entry::Occupied(mut e) => { + e.insert(UseSegmentPosition::NoneOrMultiple); + } } } } diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index 8db2c1786d8..17ce6d4dbfd 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -7,7 +7,7 @@ const GIT_COMMIT: &&str = &"GIT_COMMIT"; fn main() { // Only use build_data if the environment variable isn't set. - if std::env::var(GIT_COMMIT).is_err() { + if env::var(GIT_COMMIT).is_err() { build_data::set_GIT_COMMIT(); build_data::set_GIT_DIRTY(); build_data::no_debug_rebuilds(); @@ -19,9 +19,9 @@ fn main() { // Try to find the directory that Cargo sets when it is running; otherwise fallback to assuming the CWD // is the root of the repository and append the crate path - let root_dir = match std::env::var("CARGO_MANIFEST_DIR") { + let root_dir = match env::var("CARGO_MANIFEST_DIR") { Ok(dir) => PathBuf::from(dir).parent().unwrap().parent().unwrap().to_path_buf(), - Err(_) => std::env::current_dir().unwrap(), + Err(_) => env::current_dir().unwrap(), }; let test_dir = root_dir.join("test_programs"); @@ -215,6 +215,9 @@ fn test_{test_name}(force_brillig: ForceBrillig, inliner_aggressiveness: Inliner // Set the maximum increase so that part of the optimization is exercised (it might fail). nargo.arg("--max-bytecode-increase-percent"); nargo.arg("50"); + + // Check whether the test case is non-deterministic + nargo.arg("--check-non-determinism"); }} {test_content} diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs index 2ecf6959a94..f718021c351 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -216,6 +216,20 @@ fn compile_programs( cached_program, )?; + if compile_options.check_non_determinism { + let (program_two, _) = compile_program( + file_manager, + parsed_files, + workspace, + package, + compile_options, + load_cached_program(package), + )?; + if fxhash::hash64(&program) != fxhash::hash64(&program_two) { + panic!("Non deterministic result compiling {}", package.name); + } + } + // Choose the target width for the final, backend specific transformation. let target_width = get_target_width(package.expression_width, compile_options.expression_width); diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs index 49a23a7ea62..709caf71185 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -162,7 +162,7 @@ pub(crate) fn execute_program( diagnostic.report(&debug_artifact, false); } - Err(crate::errors::CliError::NargoError(err)) + Err(CliError::NargoError(err)) } } } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs index 1b9b2d50378..75cf14ba120 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd/formatters.rs @@ -417,6 +417,7 @@ impl Formatter for JsonFormatter { let mut json = Map::new(); json.insert("type".to_string(), json!("test")); json.insert("name".to_string(), json!(&test_result.name)); + json.insert("suite".to_string(), json!(&test_result.package_name)); json.insert("exec_time".to_string(), json!(test_result.time_to_run.as_secs_f64())); let mut stdout = String::new(); diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs index a19408bd5fd..9750eb823a6 100644 --- a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs @@ -202,7 +202,7 @@ fn fuzz_keccak256_equivalence() { }}" ) }, - |data| sha3::Keccak256::digest(data).try_into().unwrap(), + |data| sha3::Keccak256::digest(data).into(), ); } @@ -219,7 +219,7 @@ fn fuzz_keccak256_equivalence_over_135() { }}" ) }, - |data| sha3::Keccak256::digest(data).try_into().unwrap(), + |data| sha3::Keccak256::digest(data).into(), ); } @@ -235,7 +235,7 @@ fn fuzz_sha256_equivalence() { }}" ) }, - |data| sha2::Sha256::digest(data).try_into().unwrap(), + |data| sha2::Sha256::digest(data).into(), ); } @@ -251,7 +251,7 @@ fn fuzz_sha512_equivalence() { }}" ) }, - |data| sha2::Sha512::digest(data).try_into().unwrap(), + |data| sha2::Sha512::digest(data).into(), ); } diff --git a/noir/noir-repo/tooling/nargo_fmt/build.rs b/noir/noir-repo/tooling/nargo_fmt/build.rs index bd2db5f5b18..a95cbe16525 100644 --- a/noir/noir-repo/tooling/nargo_fmt/build.rs +++ b/noir/noir-repo/tooling/nargo_fmt/build.rs @@ -1,10 +1,9 @@ use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; -use std::{env, fs}; fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); + let out_dir = std::env::var("OUT_DIR").unwrap(); let destination = Path::new(&out_dir).join("execute.rs"); let mut test_file = File::create(destination).unwrap(); @@ -24,7 +23,7 @@ fn generate_formatter_tests(test_file: &mut File, test_data_dir: &Path) { let outputs_dir = test_data_dir.join("expected"); let test_case_files = - fs::read_dir(inputs_dir).unwrap().flatten().filter(|c| c.path().is_file()); + std::fs::read_dir(inputs_dir).unwrap().flatten().filter(|c| c.path().is_file()); for file in test_case_files { let file_path = file.path(); diff --git a/noir/noir-repo/tooling/nargo_fmt/src/formatter.rs b/noir/noir-repo/tooling/nargo_fmt/src/formatter.rs index 9a9386e1911..4184ff288d7 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/formatter.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/formatter.rs @@ -228,13 +228,11 @@ impl<'a> Formatter<'a> { /// Writes the current indentation to the buffer, but only if the buffer /// is empty or it ends with a newline (otherwise we'd be indenting when not needed). pub(crate) fn write_indentation(&mut self) { - if !(self.buffer.is_empty() || self.buffer.ends_with_newline()) { - return; - } - - for _ in 0..self.indentation { - for _ in 0..self.config.tab_spaces { - self.write(" "); + if self.buffer.is_empty() || self.buffer.ends_with_newline() { + for _ in 0..self.indentation { + for _ in 0..self.config.tab_spaces { + self.write(" "); + } } } } diff --git a/noir/noir-repo/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs b/noir/noir-repo/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs index e20eb4291d1..57512d049df 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/formatter/comments_and_whitespace.rs @@ -1,4 +1,4 @@ -use noirc_frontend::token::Token; +use noirc_frontend::token::{DocStyle, Token}; use super::Formatter; @@ -113,7 +113,8 @@ impl<'a> Formatter<'a> { last_was_block_comment = false; } - Token::LineComment(comment, None) => { + Token::LineComment(comment, None) + | Token::LineComment(comment, Some(DocStyle::Safety)) => { if comment.trim() == "noir-fmt:ignore" { ignore_next = true; } @@ -142,7 +143,8 @@ impl<'a> Formatter<'a> { last_was_block_comment = false; self.written_comments_count += 1; } - Token::BlockComment(comment, None) => { + Token::BlockComment(comment, None) + | Token::BlockComment(comment, Some(DocStyle::Safety)) => { if comment.trim() == "noir-fmt:ignore" { ignore_next = true; } diff --git a/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs b/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs index ecc9fab18ce..2f71d3209a7 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/formatter/expression.rs @@ -1910,15 +1910,21 @@ global y = 1; #[test] fn format_unsafe_one_expression() { - let src = "global x = unsafe { 1 } ;"; - let expected = "global x = unsafe { 1 };\n"; + let src = "global x = unsafe { //@safety: testing + 1 } ;"; + let expected = "global x = unsafe { + //@safety: testing + 1 +};\n"; assert_format(src, expected); } #[test] fn format_unsafe_two_expressions() { - let src = "global x = unsafe { 1; 2 } ;"; + let src = "global x = unsafe { //@safety: testing + 1; 2 } ;"; let expected = "global x = unsafe { + //@safety: testing 1; 2 }; @@ -2198,6 +2204,7 @@ global y = 1; let src = "mod moo { fn foo() { let mut sorted_write_tuples = unsafe { + //@safety: testing get_sorted_tuple( final_public_data_writes.storage, |(_, leaf_a): (u32, PublicDataTreeLeaf), (_, leaf_b): (u32, PublicDataTreeLeaf)| full_field_less_than( @@ -2211,6 +2218,7 @@ global y = 1; let expected = "mod moo { fn foo() { let mut sorted_write_tuples = unsafe { + //@safety: testing get_sorted_tuple( final_public_data_writes.storage, |(_, leaf_a): (u32, PublicDataTreeLeaf), (_, leaf_b): (u32, PublicDataTreeLeaf)| { diff --git a/noir/noir-repo/tooling/nargo_fmt/src/formatter/statement.rs b/noir/noir-repo/tooling/nargo_fmt/src/formatter/statement.rs index 50c286ff161..116d68e308f 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/formatter/statement.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/formatter/statement.rs @@ -489,9 +489,12 @@ mod tests { #[test] fn format_unsafe_statement() { - let src = " fn foo() { unsafe { 1 } } "; + let src = " fn foo() { unsafe { + //@safety: testing context + 1 } } "; let expected = "fn foo() { unsafe { + //@safety: testing context 1 } } diff --git a/noir/noir-repo/tooling/nargo_fmt/src/formatter/use_tree_merge.rs b/noir/noir-repo/tooling/nargo_fmt/src/formatter/use_tree_merge.rs index 834280ddba3..76fdcd0d0f8 100644 --- a/noir/noir-repo/tooling/nargo_fmt/src/formatter/use_tree_merge.rs +++ b/noir/noir-repo/tooling/nargo_fmt/src/formatter/use_tree_merge.rs @@ -1,8 +1,4 @@ -use std::{ - cmp::Ordering, - collections::BTreeMap, - fmt::{self, Display}, -}; +use std::{cmp::Ordering, collections::BTreeMap, fmt::Display}; use noirc_frontend::ast::{ItemVisibility, PathKind, UseTree, UseTreeKind}; @@ -157,7 +153,7 @@ impl Segment { } impl Display for Segment { - fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Segment::Crate => write!(f, "crate"), Segment::Super => write!(f, "super"), diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr index 7d733c203de..ec99d90c662 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/unsafe.nr @@ -1,7 +1,9 @@ fn main(x: pub u8, y: u8) { - unsafe {} + unsafe { //@safety: testing + } unsafe { + //@safety: testing assert_eq(x, y); } } diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr index 6e12ef975ee..65ed061835f 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/unsafe.nr @@ -1,7 +1,8 @@ fn main(x: pub u8, y: u8) { - unsafe { } + unsafe {//@safety: testing + } - unsafe { + unsafe {//@safety: testing assert_eq(x, y); } } diff --git a/noir/noir-repo/tooling/nargo_toml/src/lib.rs b/noir/noir-repo/tooling/nargo_toml/src/lib.rs index b5c45977618..fa556d445a4 100644 --- a/noir/noir-repo/tooling/nargo_toml/src/lib.rs +++ b/noir/noir-repo/tooling/nargo_toml/src/lib.rs @@ -93,7 +93,7 @@ pub fn find_package_root(current_path: &Path) -> Result /// * `C:\foo\bar` -> `C:\foo` /// * `//shared/foo/bar` -> `//shared/foo` /// * `/foo` -> `/foo` -/// otherwise empty path. +/// otherwise empty path. fn path_root(path: &Path) -> PathBuf { let mut components = path.components();