diff --git a/noir/noir-repo/.envrc b/noir/noir-repo/.envrc deleted file mode 100644 index b2f868b1898..00000000000 --- a/noir/noir-repo/.envrc +++ /dev/null @@ -1,20 +0,0 @@ -# Based on https://github.com/direnv/direnv-vscode/blob/158e8302c2594cc0eaa5f8b4f0cafedd4e1c0315/.envrc - -# You can define your system-specific logic (like Git settings or GH tokens) in .envrc.local -# If that logic is usable by other people and might improve development environment, consider -# contributing it to this file! - -source_env_if_exists .envrc.local - -if [[ -z "${SKIP_NIX:-}" ]] && has nix; then - - if nix flake metadata &>/dev/null && has use_flake; then - # use flakes if possible - use flake - - else - # Otherwise fall back to pure nix - use nix - fi - -fi diff --git a/noir/noir-repo/.github/Cross.toml b/noir/noir-repo/.github/Cross.toml index 09e6316a59c..d8516b9ae09 100644 --- a/noir/noir-repo/.github/Cross.toml +++ b/noir/noir-repo/.github/Cross.toml @@ -2,7 +2,6 @@ passthrough = [ "HOME", "RUST_BACKTRACE", - "BARRETENBERG_BIN_DIR" ] volumes = [ "HOME", diff --git a/noir/noir-repo/.github/scripts/acvm_js-build.sh b/noir/noir-repo/.github/scripts/acvm_js-build.sh index 95bd1efc8b9..e79967e3a8f 100755 --- a/noir/noir-repo/.github/scripts/acvm_js-build.sh +++ b/noir/noir-repo/.github/scripts/acvm_js-build.sh @@ -2,4 +2,5 @@ set -eu .github/scripts/wasm-bindgen-install.sh +.github/scripts/wasm-opt-install.sh yarn workspace @noir-lang/acvm_js build diff --git a/noir/noir-repo/.github/scripts/noir-wasm-build.sh b/noir/noir-repo/.github/scripts/noir-wasm-build.sh index 48e3ad73769..6c0336ee0c5 100755 --- a/noir/noir-repo/.github/scripts/noir-wasm-build.sh +++ b/noir/noir-repo/.github/scripts/noir-wasm-build.sh @@ -2,5 +2,6 @@ set -eu .github/scripts/wasm-pack-install.sh +.github/scripts/wasm-opt-install.sh yarn workspace @noir-lang/types build yarn workspace @noir-lang/noir_wasm build diff --git a/noir/noir-repo/.github/scripts/noirc-abi-build.sh b/noir/noir-repo/.github/scripts/noirc-abi-build.sh index 23b8393088e..99de474eb75 100755 --- a/noir/noir-repo/.github/scripts/noirc-abi-build.sh +++ b/noir/noir-repo/.github/scripts/noirc-abi-build.sh @@ -2,4 +2,5 @@ set -eu .github/scripts/wasm-bindgen-install.sh +.github/scripts/wasm-opt-install.sh yarn workspace @noir-lang/noirc_abi build diff --git a/noir/noir-repo/.github/scripts/wasm-opt-install.sh b/noir/noir-repo/.github/scripts/wasm-opt-install.sh index cbdeb8f2bfe..218778edac6 100755 --- a/noir/noir-repo/.github/scripts/wasm-opt-install.sh +++ b/noir/noir-repo/.github/scripts/wasm-opt-install.sh @@ -5,4 +5,4 @@ cd $(dirname "$0") ./cargo-binstall-install.sh -cargo-binstall wasm-opt --version 0.116.0 -y +cargo-binstall wasm-opt --version 0.116.0 -y --force diff --git a/noir/noir-repo/.github/workflows/docker-test-flow.yml b/noir/noir-repo/.github/workflows/docker-test-flow.yml deleted file mode 100644 index c8b4f53fadd..00000000000 --- a/noir/noir-repo/.github/workflows/docker-test-flow.yml +++ /dev/null @@ -1,808 +0,0 @@ -name: Test Nargo and JS packages - -on: - push: - branches: - - 'master' - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref || github.run_id }} - cancel-in-progress: true - -jobs: - build-base-nargo: - name: Build base nargo docker image - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Get current date - id: date - run: echo "date=$(date +'%Y.%m.%d.%H.%M')" >> $GITHUB_STATE - - name: prepare docker images tags - id: prep - run: | - REGISTRY="ghcr.io" - IMG_RAW="${REGISTRY}/${{ github.repository }}" - IMAGE=$(echo "$IMG_RAW" | tr '[:upper:]' '[:lower:]') - TAGS="${IMAGE}:${{ github.sha }}-nargo" - FULL_TAGS="${TAGS},${IMAGE}:latest-nargo,${IMAGE}:v${{ steps.date.outputs.date }}-nargo" - echo "tags=$FULL_TAGS" >> $GITHUB_OUTPUT - echo "image=$IMAGE" >> $GITHUB_OUTPUT - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build nargo base dockerfile - uses: docker/build-push-action@v5 - with: - context: . - file: Dockerfile.ci - tags: ${{ steps.prep.outputs.tags }} - target: base-nargo - cache-from: type=gha - cache-to: type=gha,mode=max - push: true - - build-base-js: - name: Build base js docker image - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Get current date - id: date - run: echo "date=$(date +'%Y.%m.%d.%H.%M')" >> $GITHUB_STATE - - name: Prepare docker image tags - id: prep - run: | - REGISTRY="ghcr.io" - IMG_RAW="${REGISTRY}/${{ github.repository }}" - IMAGE=$(echo "$IMG_RAW" | tr '[:upper:]' '[:lower:]') - TAGS="${IMAGE}:${{ github.sha }}-js" - FULL_TAGS="${TAGS},${IMAGE}:latest-js,${IMAGE}:v${{ steps.date.outputs.date }}-js" - echo "tags=$FULL_TAGS" >> $GITHUB_OUTPUT - echo "image=$IMAGE" >> $GITHUB_OUTPUT - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v3 - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build js base dockerfile - uses: docker/build-push-action@v5 - with: - context: . - file: Dockerfile.ci - tags: ${{ steps.prep.outputs.tags }} - target: base-js - cache-from: type=gha - cache-to: type=gha,mode=max - push: true - - artifact-nargo: - name: Artifact nargo - runs-on: ubuntu-latest - needs: [build-base-nargo] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-nargo - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Artifact nargo - uses: actions/upload-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release/nargo - if-no-files-found: error - compression-level: 0 - - test-nargo: - name: Test nargo - runs-on: ubuntu-latest - needs: [build-base-nargo] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-nargo - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Test - working-directory: /usr/src/noir - run: | - .github/scripts/nargo-test.sh - - build-noir-wasm: - name: Build noir wasm - runs-on: ubuntu-latest - needs: [build-base-js, build-noirc-abi] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Build - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-wasm-build.sh - - name: Artifact - uses: actions/upload-artifact@v4 - with: - name: noir_wasm - path: /usr/src/noir/compiler/wasm - retention-days: 10 - - test-noir-wasm: - name: Test noir wasm - runs-on: ubuntu-latest - needs: [build-base-js, artifact-nargo, build-noir-wasm] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download nargo - uses: actions/download-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release - - name: Prep downloaded artifact - run: | - chmod +x /usr/src/noir/target/release/nargo - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Download noir_wasm artifact - uses: actions/download-artifact@v4 - with: - name: noir_wasm - path: /usr/src/noir/compiler/wasm - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-wasm-test.sh - - test-noir-wasm-browser: - name: Test noir wasm browser - runs-on: ubuntu-latest - needs: [build-base-js, artifact-nargo, build-noir-wasm] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download nargo - uses: actions/download-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release - - name: Prep downloaded artifact - run: | - chmod +x /usr/src/noir/target/release/nargo - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Download noir_wasm artifact - uses: actions/download-artifact@v4 - with: - name: noir_wasm - path: /usr/src/noir/compiler/wasm - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-wasm-test-browser.sh - - build-acvm_js: - name: Build acvm js - runs-on: ubuntu-latest - needs: [build-base-js] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Build - working-directory: /usr/src/noir - run: | - ./.github/scripts/acvm_js-build.sh - - name: Artifact - uses: actions/upload-artifact@v4 - with: - name: acvm_js - path: - /usr/src/noir/acvm-repo/acvm_js/outputs/out/acvm_js - if-no-files-found: error - compression-level: 0 - - test-acvm_js: - name: Test acvm js - runs-on: ubuntu-latest - needs: [build-base-js, build-acvm_js] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: | - /usr/src/noir/acvm-repo/acvm_js - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/acvm_js-test.sh - - test-acvm_js-browser: - name: Test acvm js browser - runs-on: ubuntu-latest - needs: [build-base-js, build-acvm_js] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: | - /usr/src/noir/acvm-repo/acvm_js - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/acvm_js-test-browser.sh - - build-noirc-abi: - name: Build noirc abi - runs-on: ubuntu-latest - needs: [build-base-js] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Build - working-directory: /usr/src/noir - run: | - ./.github/scripts/noirc-abi-build.sh - - name: Artifact - uses: actions/upload-artifact@v4 - with: - name: noirc_abi_wasm - path: - /usr/src/noir/tooling/noirc_abi_wasm/outputs/out/noirc_abi_wasm - if-no-files-found: error - compression-level: 0 - - test-noirc-abi: - name: Test noirc abi - runs-on: ubuntu-latest - needs: [build-base-js, build-noirc-abi] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/noirc-abi-test.sh - - test-noirc-abi-browser: - name: Test noirc abi browser - runs-on: ubuntu-latest - needs: [build-base-js, build-noirc-abi] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/noirc-abi-test-browser.sh - - build-noir-js-types: - name: Build noir js types - runs-on: ubuntu-latest - needs: [build-base-js, build-noirc-abi] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Build - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-js-types-build.sh - - name: Artifact - uses: actions/upload-artifact@v4 - with: - name: noir-js-types - path: | - /usr/src/noir/tooling/noir_js_types/lib - if-no-files-found: error - compression-level: 0 - - build-barretenberg-backend: - name: Build Barretenberg backend - runs-on: ubuntu-latest - needs: [build-base-js, build-noirc-abi, build-noir-js-types] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: /usr/src/noir/tooling/noir_js_types/lib/ - - name: Build - working-directory: /usr/src/noir - run: | - ./.github/scripts/backend-barretenberg-build.sh - - name: Artifact - uses: actions/upload-artifact@v4 - with: - name: barretenberg-backend - path: - /usr/src/noir/tooling/noir_js_backend_barretenberg/lib - if-no-files-found: error - compression-level: 0 - - test-barretenberg-backend: - name: Test Barretenberg backend - runs-on: ubuntu-latest - needs: [build-base-js, build-noirc-abi, build-noir-js-types, build-barretenberg-backend] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: /usr/src/noir/tooling/noir_js_types/lib/ - - name: Download Backend barretenberg - uses: actions/download-artifact@v4 - with: - name: barretenberg-backend - path: - /usr/src/noir/tooling/noir_js_backend_barretenberg/lib - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/backend-barretenberg-test.sh - - build-noir_js: - name: Build noirjs - runs-on: ubuntu-latest - needs: [build-base-js, artifact-nargo, build-noirc-abi, build-acvm_js, build-barretenberg-backend, build-noir-js-types] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download nargo - uses: actions/download-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release - - name: prep downloaded artifact - run: | - chmod +x /usr/src/noir/target/release/nargo - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: | - /usr/src/noir/acvm-repo/acvm_js - - name: Download Barretenberg backend - uses: actions/download-artifact@v4 - with: - name: barretenberg-backend - path: - /usr/src/noir/tooling/noir_js_backend_barretenberg/lib - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: | - /usr/src/noir/tooling/noir_js_types/lib - - name: Build - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-js-build.sh - - name: Artifact - uses: actions/upload-artifact@v4 - with: - name: noir_js - path: - /usr/src/noir/tooling/noir_js/lib - - test-noir_js: - name: Test noirjs - runs-on: ubuntu-latest - needs: [ - build-base-js, - build-noirc-abi, - artifact-nargo, - build-acvm_js, - build-barretenberg-backend, - build-noir_js, - build-noir-js-types - ] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download nargo - uses: actions/download-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release - - name: Prep downloaded artifact - run: | - chmod +x /usr/src/noir/target/release/nargo - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: | - /usr/src/noir/tooling/noirc_abi_wasm - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: | - /usr/src/noir/acvm-repo/acvm_js - - name: Download Barretenberg backend - uses: actions/download-artifact@v4 - with: - name: barretenberg-backend - path: - /usr/src/noir/tooling/noir_js_backend_barretenberg/lib - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: | - /usr/src/noir/tooling/noir_js_types/lib - - name: Download noir js - uses: actions/download-artifact@v4 - with: - name: noir_js - path: - /usr/src/noir/tooling/noir_js/lib - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-js-test.sh - - build-noir_codegen: - name: Build noir codegen - runs-on: ubuntu-latest - needs: [build-base-js, build-noirc-abi, build-acvm_js, build-noir-js-types, build-noir_js] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download nargo - uses: actions/download-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release - - name: Prep downloaded artifact - run: | - chmod +x /usr/src/noir/target/release/nargo - - name: Download noirc abi package - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: /usr/src/noir/tooling/noirc_abi_wasm - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: /usr/src/noir/acvm-repo/acvm_js - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: | - /usr/src/noir/tooling/noir_js_types/lib - - name: Download noir js - uses: actions/download-artifact@v4 - with: - name: noir_js - path: - /usr/src/noir/tooling/noir_js/lib - - name: Build - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-codegen-build.sh - - name: Artifact - uses: actions/upload-artifact@v4 - with: - name: noir_codegen - path: - /usr/src/noir/tooling/noir_codegen/lib - - test-noir_codegen: - name: Test noir codegen - runs-on: ubuntu-latest - needs: [build-base-js, artifact-nargo, build-noirc-abi, build-acvm_js, build-noir-js-types, build-noir_js, build-noir_codegen] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download nargo - uses: actions/download-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release - - name: Prep downloaded artifact - run: | - chmod +x /usr/src/noir/target/release/nargo - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: /usr/src/noir/tooling/noirc_abi_wasm - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: /usr/src/noir/acvm-repo/acvm_js - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: | - /usr/src/noir/tooling/noir_js_types/lib - - name: Download noir js - uses: actions/download-artifact@v4 - with: - name: noir_js - path: - /usr/src/noir/tooling/noir_js/lib - - name: Download noir codegen - uses: actions/download-artifact@v4 - with: - name: noir_codegen - path: - /usr/src/noir/tooling/noir_codegen/lib - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/noir-codegen-test.sh - - test-integration: - name: Integration test - runs-on: ubuntu-latest - needs: [ - build-base-js, - artifact-nargo, - build-noir-wasm, - build-noirc-abi, - build-acvm_js, - build-noir-js-types, - build-noir_js, - build-barretenberg-backend - ] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download nargo - uses: actions/download-artifact@v4 - with: - name: nargo - path: /usr/src/noir/target/release - - name: Prep downloaded artifact - run: | - chmod +x /usr/src/noir/target/release/nargo - - name: Download noir wasm - uses: actions/download-artifact@v4 - with: - name: noir_wasm - path: /usr/src/noir/compiler/wasm - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: /usr/src/noir/tooling/noirc_abi_wasm - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: /usr/src/noir/acvm-repo/acvm_js - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: | - /usr/src/noir/tooling/noir_js_types/lib - - name: Download noir js - uses: actions/download-artifact@v4 - with: - name: noir_js - path: - /usr/src/noir/tooling/noir_js/lib - - name: Download Barretenberg backend - uses: actions/download-artifact@v4 - with: - name: barretenberg-backend - path: - /usr/src/noir/tooling/noir_js_backend_barretenberg/lib - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/integration-test-node.sh - - test-integration-browser: - name: Integration test browser - runs-on: ubuntu-latest - needs: [ - build-base-js, - build-noir-wasm, - build-noirc-abi, - build-acvm_js, - build-noir-js-types, - build-noir_js, - build-barretenberg-backend - ] - container: - image: ghcr.io/noir-lang/noir:${{ github.sha }}-js - credentials: - username: ${{ github.actor }} - password: ${{ secrets.github_token }} - steps: - - name: Download noir wasm - uses: actions/download-artifact@v4 - with: - name: noir_wasm - path: /usr/src/noir/compiler/wasm - - name: Download noirc abi - uses: actions/download-artifact@v4 - with: - name: noirc_abi_wasm - path: /usr/src/noir/tooling/noirc_abi_wasm - - name: Download acvm js - uses: actions/download-artifact@v4 - with: - name: acvm_js - path: /usr/src/noir/acvm-repo/acvm_js - - name: Download noir js types - uses: actions/download-artifact@v4 - with: - name: noir-js-types - path: | - /usr/src/noir/tooling/noir_js_types/lib - - name: Download noir js - uses: actions/download-artifact@v4 - with: - name: noir_js - path: - /usr/src/noir/tooling/noir_js/lib - - name: Download Barretenberg backend - uses: actions/download-artifact@v4 - with: - name: barretenberg-backend - path: - /usr/src/noir/tooling/noir_js_backend_barretenberg/lib - - name: Test - working-directory: /usr/src/noir - run: | - ./.github/scripts/integration-test-browser.sh - - tests-end: - name: End - runs-on: ubuntu-latest - if: ${{ always() }} - needs: - - test-nargo - - test-noirc-abi - - test-noirc-abi-browser - - test-noir-wasm - - test-noir-wasm-browser - - test-integration - - test-integration-browser - - test-noir_codegen - - test-acvm_js - - test-acvm_js-browser - - test-barretenberg-backend - - test-noir_js - - steps: - - name: Report overall success - run: | - if [[ $FAIL == true ]]; then - exit 1 - else - exit 0 - fi - env: - FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} diff --git a/noir/noir-repo/.github/workflows/docs-pr.yml b/noir/noir-repo/.github/workflows/docs-pr.yml index 5d0b72c6ad8..1f9ccdd946b 100644 --- a/noir/noir-repo/.github/workflows/docs-pr.yml +++ b/noir/noir-repo/.github/workflows/docs-pr.yml @@ -75,9 +75,11 @@ jobs: run: | npm i wasm-opt -g + - name: Query active docs versions + run: yarn workspace docs version::stables + - name: Build docs - run: - yarn workspaces foreach -Rpt --from docs run build + run: yarn workspaces foreach -Rpt --from docs run build - name: Upload artifact uses: actions/upload-artifact@v4 @@ -126,4 +128,4 @@ jobs: with: message: | FYI @noir-lang/developerrelations on Noir doc changes. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/noir/noir-repo/.github/workflows/gates_report.yml b/noir/noir-repo/.github/workflows/gates_report.yml index f3f798fc5ea..ebf17f7374c 100644 --- a/noir/noir-repo/.github/workflows/gates_report.yml +++ b/noir/noir-repo/.github/workflows/gates_report.yml @@ -74,7 +74,7 @@ jobs: - name: Compare gates reports id: gates_diff - uses: TomAFrench/noir-gates-diff@e7cf131b7e7f044c01615f93f0b855f65ddc02d4 + uses: TomAFrench/noir-gates-diff@df05f34e2ab275ddc4f2cac065df1c88f8a05e5d with: report: gates_report.json summaryQuantile: 0.9 # only display the 10% most significant circuit size diffs in the summary (defaults to 20%) diff --git a/noir/noir-repo/.github/workflows/publish-docs.yml b/noir/noir-repo/.github/workflows/publish-docs.yml index a56583b34eb..8896e613608 100644 --- a/noir/noir-repo/.github/workflows/publish-docs.yml +++ b/noir/noir-repo/.github/workflows/publish-docs.yml @@ -28,10 +28,11 @@ jobs: run: | npm i wasm-opt -g + - name: Query active docs versions + run: yarn workspace docs version::stables + - name: Build docs for deploying - working-directory: docs - run: - yarn workspaces foreach -Rt run build + run: yarn workspaces foreach -Rpt --from docs run build - name: Deploy to Netlify uses: nwtgck/actions-netlify@v2.1 diff --git a/noir/noir-repo/.github/workflows/publish-es-packages.yml b/noir/noir-repo/.github/workflows/publish-es-packages.yml index 470db3b78f7..819be308169 100644 --- a/noir/noir-repo/.github/workflows/publish-es-packages.yml +++ b/noir/noir-repo/.github/workflows/publish-es-packages.yml @@ -44,7 +44,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: noirc_abi_wasm - path: ./tooling/noirc_abi_wasm/outputs/out/noirc_abi_wasm + path: | + ./tooling/noirc_abi_wasm/nodejs + ./tooling/noirc_abi_wasm/web retention-days: 10 build-noir_wasm: @@ -113,7 +115,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: acvm-js - path: ./acvm-repo/acvm_js/outputs/out/acvm_js + path: | + ./acvm-repo/acvm_js/nodejs + ./acvm-repo/acvm_js/web retention-days: 3 publish-es-packages: diff --git a/noir/noir-repo/.github/workflows/release.yml b/noir/noir-repo/.github/workflows/release.yml index 249d83afecc..d27fac0e039 100644 --- a/noir/noir-repo/.github/workflows/release.yml +++ b/noir/noir-repo/.github/workflows/release.yml @@ -79,6 +79,15 @@ jobs: - name: Install Yarn dependencies uses: ./.github/actions/setup + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@0.2.86 + + - name: Install wasm-opt + run: | + npm i wasm-opt -g + - name: Query new noir version id: noir-version run: | @@ -107,19 +116,22 @@ jobs: if: ${{ always() }} needs: + - release-please - update-acvm-workspace-package-versions - update-docs env: # We treat any skipped or failing jobs as a failure for the workflow as a whole. - FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') } + FAIL: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }} steps: - name: Add warning to sticky comment uses: marocchino/sticky-pull-request-comment@v2 with: + # We need to specify the PR on which to make the comment as workflow is triggered by push. + number: ${{ fromJSON(needs.release-please.outputs.release-pr).number }} # delete the comment in case failures have been fixed - delete: ${{ !env.FAIL }} + delete: ${{ env.FAIL == false }} message: "The release workflow has not completed successfully. Releasing now will result in a broken release" - name: Report overall success diff --git a/noir/noir-repo/.github/workflows/test-js-packages.yml b/noir/noir-repo/.github/workflows/test-js-packages.yml index b3908ee5d3e..06a96ee8932 100644 --- a/noir/noir-repo/.github/workflows/test-js-packages.yml +++ b/noir/noir-repo/.github/workflows/test-js-packages.yml @@ -66,9 +66,6 @@ jobs: - name: Install Yarn dependencies uses: ./.github/actions/setup - - name: Install wasm-opt - run: ./.github/scripts/wasm-opt-install.sh - - name: Build noirc_abi run: ./.github/scripts/noirc-abi-build.sh @@ -76,7 +73,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: noirc_abi_wasm - path: ./tooling/noirc_abi_wasm/outputs/out/noirc_abi_wasm + path: | + ./tooling/noirc_abi_wasm/nodejs + ./tooling/noirc_abi_wasm/web retention-days: 10 @@ -100,9 +99,6 @@ jobs: - name: Install Yarn dependencies uses: ./.github/actions/setup - - name: Install wasm-opt - run: ./.github/scripts/wasm-opt-install.sh - - name: Build noir_js_types run: yarn workspace @noir-lang/types build @@ -138,9 +134,6 @@ jobs: - name: Install Yarn dependencies uses: ./.github/actions/setup - - name: Install wasm-opt - run: ./.github/scripts/wasm-opt-install.sh - - name: Build acvm_js run: ./.github/scripts/acvm_js-build.sh @@ -148,7 +141,9 @@ jobs: uses: actions/upload-artifact@v4 with: name: acvm-js - path: ./acvm-repo/acvm_js/outputs/out/acvm_js + path: | + ./acvm-repo/acvm_js/nodejs + ./acvm-repo/acvm_js/web retention-days: 3 test-acvm_js-node: diff --git a/noir/noir-repo/.gitignore b/noir/noir-repo/.gitignore index 5f41566c94b..9a829afab8b 100644 --- a/noir/noir-repo/.gitignore +++ b/noir/noir-repo/.gitignore @@ -17,12 +17,6 @@ pkg/ # Noir.js tooling/noir_js/lib -# Nix stuff -**/outputs -result -.envrc.local -.direnv/ - # Nargo output *.proof *.acir diff --git a/noir/noir-repo/.vscode/extensions.json b/noir/noir-repo/.vscode/extensions.json index 1fb1def7ce1..efb17cb0085 100644 --- a/noir/noir-repo/.vscode/extensions.json +++ b/noir/noir-repo/.vscode/extensions.json @@ -4,7 +4,6 @@ // List of extensions which should be recommended for users of this workspace. "recommendations": [ "mkhl.direnv", - "jnoortheen.nix-ide", "rust-lang.rust-analyzer", "redhat.vscode-yaml", "esbenp.prettier-vscode", diff --git a/noir/noir-repo/.vscode/settings.json b/noir/noir-repo/.vscode/settings.json index 171d36f4e04..fb8ea527881 100644 --- a/noir/noir-repo/.vscode/settings.json +++ b/noir/noir-repo/.vscode/settings.json @@ -2,17 +2,6 @@ "direnv.restart.automatic": true, "redhat.telemetry.enabled": false, "yaml.recommendations.show": false, - "nix.serverPath": "nil", - "nix.enableLanguageServer": true, - "nix.serverSettings": { - "nil": { - "formatting": { - "command": [ - "nixpkgs-fmt" - ] - } - } - }, "yaml.schemas": { "https://json.schemastore.org/github-workflow.json": "${workspaceRoot}/.github/workflows/*.yml" }, diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 8c47f03b267..170b23be189 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -93,7 +93,6 @@ dependencies = [ "acvm", "bn254_blackbox_solver", "build-data", - "cfg-if 1.0.0", "console_error_panic_hook", "const-str", "gloo-utils", @@ -606,16 +605,12 @@ dependencies = [ "acvm_blackbox_solver", "ark-ec", "ark-ff", - "flate2", + "cfg-if 1.0.0", "getrandom 0.2.10", "js-sys", "noir_grumpkin", "num-bigint", "num-traits", - "pkg-config", - "reqwest", - "rust-embed", - "tar", "thiserror", "wasm-bindgen-futures", "wasmer", @@ -789,9 +784,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -799,7 +794,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.1", + "windows-targets 0.52.4", ] [[package]] @@ -1044,9 +1039,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "corosensei" @@ -1988,9 +1983,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -3070,6 +3065,7 @@ name = "noirc_evaluator" version = "0.26.0" dependencies = [ "acvm", + "chrono", "fxhash", "im", "iter-extended", @@ -3087,6 +3083,7 @@ version = "0.26.0" dependencies = [ "acvm", "arena", + "base64 0.21.2", "chumsky", "fm", "iter-extended", @@ -4035,7 +4032,6 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "shellexpand", "syn 2.0.32", "walkdir", ] @@ -4046,7 +4042,6 @@ version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d38ff6bf570dc3bb7100fce9f7b60c33fa71d80e88da3f2580df4ff2bdded74" dependencies = [ - "globset", "sha2", "walkdir", ] @@ -4467,15 +4462,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" -[[package]] -name = "shellexpand" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" -dependencies = [ - "dirs", -] - [[package]] name = "signature" version = "1.6.4" @@ -4554,9 +4540,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" diff --git a/noir/noir-repo/Dockerfile.ci b/noir/noir-repo/Dockerfile.ci deleted file mode 100644 index e0dc030980c..00000000000 --- a/noir/noir-repo/Dockerfile.ci +++ /dev/null @@ -1,30 +0,0 @@ -FROM rust:1.73.0-slim-bookworm as base -RUN apt-get update && apt-get upgrade -y && apt-get install build-essential git -y -WORKDIR /usr/src/noir -ENV PATH="${PATH}:/usr/src/noir/target/release" - -FROM base as base-nargo -COPY . . -RUN .github/scripts/nargo-build.sh - -FROM base as base-js -RUN apt-get install -y ca-certificates curl gnupg -RUN mkdir -p /etc/apt/keyrings -RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg -RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list -RUN apt-get update && apt-get install nodejs -y -RUN corepack enable -RUN apt-get install -y jq -COPY yarn.lock package.json .yarnrc.yml ./ -COPY .yarn/ ./.yarn/ -COPY ./acvm-repo/acvm_js/package.json ./acvm-repo/acvm_js/ -COPY ./tooling/noirc_abi_wasm/package.json ./tooling/noirc_abi_wasm/ -COPY ./compiler/wasm/package.json ./compiler/wasm/ -COPY ./tooling/noir_js_types/package.json ./tooling/noir_js_types/ -COPY ./tooling/noir_js_backend_barretenberg/package.json ./tooling/noir_js_backend_barretenberg/ -COPY ./tooling/noir_js/package.json ./tooling/noir_js/ -COPY ./tooling/noir_codegen/package.json ./tooling/noir_codegen/ -COPY ./compiler/integration-tests/package.json ./compiler/integration-tests/ -COPY ./docs/package.json ./docs/ -RUN yarn --immutable -COPY . . diff --git a/noir/noir-repo/README.md b/noir/noir-repo/README.md index 5c93512ae26..adf68b290ef 100644 --- a/noir/noir-repo/README.md +++ b/noir/noir-repo/README.md @@ -56,26 +56,6 @@ Concretely the following items are on the road map: This crate's minimum supported rustc version is 1.73.0. -## Working on this project - -This project uses [Nix](https://nixos.org/) and [direnv](https://direnv.net/) to streamline the development experience. Please follow [our guidelines](https://noir-lang.org/docs/getting_started/installation/other_install_methods#option-3-compile-from-source) to setup your environment for working on the project. - -### Building against a different local/remote version of Barretenberg - -If you are working on this project and want a different version of Barretenberg (instead of the version this project is pinned against), you'll want to replace the lockfile version with your version. This can be done by running: - -```sh -nix flake lock --override-input barretenberg /absolute/path/to/your/barretenberg -``` - -You can also point at a fork and/or branch on GitHub using: - -```sh -nix flake lock --override-input barretenberg github:username/barretenberg/branch_name -``` - -__Note:__ You don't want to commit the updated lockfile, as it will fail in CI! - ## License Noir is free and open source. It is distributed under a dual license. (MIT/APACHE) 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 b5d6348d34f..cb846bdaffa 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/mod.rs @@ -216,25 +216,33 @@ impl std::fmt::Display for Circuit { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "current witness index : {}", self.current_witness_index)?; - let write_public_inputs = |f: &mut std::fmt::Formatter<'_>, - public_inputs: &PublicInputs| - -> Result<(), std::fmt::Error> { - write!(f, "[")?; - let public_input_indices = public_inputs.indices(); - for (index, public_input) in public_input_indices.iter().enumerate() { - write!(f, "{public_input}")?; - if index != public_input_indices.len() - 1 { - write!(f, ", ")?; + let write_witness_indices = + |f: &mut std::fmt::Formatter<'_>, indices: &[u32]| -> Result<(), std::fmt::Error> { + write!(f, "[")?; + for (index, witness_index) in indices.iter().enumerate() { + write!(f, "{witness_index}")?; + if index != indices.len() - 1 { + write!(f, ", ")?; + } } - } - writeln!(f, "]") - }; + writeln!(f, "]") + }; + + write!(f, "private parameters indices : ")?; + write_witness_indices( + f, + &self + .private_parameters + .iter() + .map(|witness| witness.witness_index()) + .collect::>(), + )?; write!(f, "public parameters indices : ")?; - write_public_inputs(f, &self.public_parameters)?; + write_witness_indices(f, &self.public_parameters.indices())?; write!(f, "return value indices : ")?; - write_public_inputs(f, &self.return_values)?; + write_witness_indices(f, &self.return_values.indices())?; for opcode in &self.opcodes { writeln!(f, "{opcode}")?; diff --git a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index 8a0c4692282..c955e435b37 100644 --- a/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/noir/noir-repo/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -10,12 +10,6 @@ pub struct FunctionInput { pub num_bits: u32, } -impl FunctionInput { - pub fn dummy() -> Self { - Self { witness: Witness(0), num_bits: 0 } - } -} - #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum BlackBoxFuncCall { AND { diff --git a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs index a9e8f219b3e..7c79e3db431 100644 --- a/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs +++ b/noir/noir-repo/acvm-repo/acir/src/native_types/witness_stack.rs @@ -21,7 +21,7 @@ pub struct WitnessStackError(#[from] SerializationError); /// An ordered set of witness maps for separate circuits #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] pub struct WitnessStack { - pub stack: Vec, + stack: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Serialize, Deserialize)] @@ -37,6 +37,10 @@ impl WitnessStack { self.stack.push(StackItem { index, witness }); } + pub fn pop(&mut self) -> Option { + self.stack.pop() + } + pub fn peek(&self) -> Option<&StackItem> { self.stack.last() } diff --git a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs index 8b04292dfaa..a5b683c15e1 100644 --- a/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs +++ b/noir/noir-repo/acvm-repo/acir/tests/test_program_serialization.rs @@ -362,3 +362,96 @@ fn memory_op_circuit() { assert_eq!(bytes, expected_serialization) } + +#[test] +fn nested_acir_call_circuit() { + // Circuit for the following program: + // fn main(x: Field, y: pub Field) { + // let z = nested_call(x, y); + // let z2 = nested_call(x, y); + // assert(z == z2); + // } + // #[fold] + // fn nested_call(x: Field, y: Field) -> Field { + // inner_call(x + 2, y) + // } + // #[fold] + // fn inner_call(x: Field, y: Field) -> Field { + // assert(x == y); + // x + // } + let nested_call = + Opcode::Call { id: 1, inputs: vec![Witness(0), Witness(1)], outputs: vec![Witness(2)] }; + let nested_call_two = + Opcode::Call { id: 1, inputs: vec![Witness(0), Witness(1)], outputs: vec![Witness(3)] }; + + let assert_nested_call_results = Opcode::AssertZero(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(2)), + (-FieldElement::one(), Witness(3)), + ], + q_c: FieldElement::zero(), + }); + + let main = Circuit { + current_witness_index: 3, + private_parameters: BTreeSet::from([Witness(0)]), + public_parameters: PublicInputs([Witness(1)].into()), + opcodes: vec![nested_call, nested_call_two, assert_nested_call_results], + ..Circuit::default() + }; + + let call_parameter_addition = Opcode::AssertZero(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(0)), + (-FieldElement::one(), Witness(2)), + ], + q_c: FieldElement::one() + FieldElement::one(), + }); + let call = + Opcode::Call { id: 2, inputs: vec![Witness(2), Witness(1)], outputs: vec![Witness(3)] }; + + let nested_call = Circuit { + current_witness_index: 3, + private_parameters: BTreeSet::from([Witness(0), Witness(1)]), + return_values: PublicInputs([Witness(3)].into()), + opcodes: vec![call_parameter_addition, call], + ..Circuit::default() + }; + + let assert_param_equality = Opcode::AssertZero(Expression { + mul_terms: Vec::new(), + linear_combinations: vec![ + (FieldElement::one(), Witness(0)), + (-FieldElement::one(), Witness(1)), + ], + q_c: FieldElement::zero(), + }); + + let inner_call = Circuit { + current_witness_index: 1, + private_parameters: BTreeSet::from([Witness(0), Witness(1)]), + return_values: PublicInputs([Witness(0)].into()), + opcodes: vec![assert_param_equality], + ..Circuit::default() + }; + + let program = Program { functions: vec![main, nested_call, inner_call] }; + + let bytes = Program::serialize_program(&program); + + let expected_serialization: Vec = vec![ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, + 24, 109, 227, 191, 93, 101, 50, 123, 255, 35, 172, 99, 25, 83, 17, 250, 99, 14, 250, 224, + 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, 57, 108, 14, 91, 248, 202, 168, 65, + 255, 207, 122, 28, 180, 250, 244, 221, 244, 197, 223, 68, 182, 154, 197, 184, 134, 80, 54, + 95, 136, 233, 142, 62, 101, 137, 24, 98, 94, 133, 132, 162, 196, 135, 23, 230, 34, 65, 182, + 148, 211, 134, 137, 2, 23, 218, 99, 226, 93, 135, 185, 121, 123, 33, 84, 12, 234, 218, 192, + 64, 174, 3, 248, 47, 88, 48, 17, 150, 157, 183, 151, 95, 244, 86, 91, 221, 61, 10, 81, 31, + 178, 190, 110, 194, 102, 96, 76, 251, 202, 80, 13, 204, 77, 224, 25, 176, 70, 79, 197, 128, + 18, 64, 3, 4, 0, 0, + ]; + assert_eq!(bytes, expected_serialization); +} diff --git a/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/mod.rs b/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/mod.rs index 1ba261b09a3..003cd4279a1 100644 --- a/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/noir/noir-repo/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -142,7 +142,11 @@ pub(super) fn transform_internal( new_acir_opcode_positions.push(acir_opcode_positions[index]); transformed_opcodes.push(opcode); } - Opcode::Call { .. } => { + Opcode::Call { ref outputs, .. } => { + for witness in outputs { + transformer.mark_solvable(*witness); + } + // `Call` does not write values to the `WitnessMap` // A separate ACIR function should have its own respective `WitnessMap` new_acir_opcode_positions.push(acir_opcode_positions[index]); diff --git a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml index 65c072b1d96..8319c38aee2 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml +++ b/noir/noir-repo/acvm-repo/acvm_js/Cargo.toml @@ -16,20 +16,17 @@ repository.workspace = true crate-type = ["cdylib"] [dependencies] -cfg-if = "1.0.0" - -[target.'cfg(target_arch = "wasm32")'.dependencies] acvm.workspace = true bn254_blackbox_solver = { workspace = true, optional = true } wasm-bindgen.workspace = true wasm-bindgen-futures.workspace = true console_error_panic_hook.workspace = true gloo-utils.workspace = true -js-sys.workspace = true +js-sys.workspace = true +serde.workspace = true tracing-subscriber.workspace = true tracing-web.workspace = true -serde = { version = "1.0.136", features = ["derive"] } const-str = "0.5.5" [build-dependencies] diff --git a/noir/noir-repo/acvm-repo/acvm_js/build.sh b/noir/noir-repo/acvm-repo/acvm_js/build.sh index 24af149bcea..fe0b4dcbfff 100755 --- a/noir/noir-repo/acvm-repo/acvm_js/build.sh +++ b/noir/noir-repo/acvm-repo/acvm_js/build.sh @@ -6,13 +6,6 @@ function require_command { exit 1 fi } -function check_installed { - if ! command -v "$1" >/dev/null 2>&1; then - echo "$1 is not installed. Please install it." >&2 - return 1 - fi - return 0 -} function run_or_fail { "$@" local status=$? @@ -25,23 +18,29 @@ function run_or_fail { require_command jq require_command cargo require_command wasm-bindgen -check_installed wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") -export pname=$(cargo read-manifest | jq -r '.name') -export CARGO_TARGET_DIR=$self_path/target +pname=$(cargo read-manifest | jq -r '.name') -rm -rf $self_path/outputs >/dev/null 2>&1 -rm -rf $self_path/result >/dev/null 2>&1 +NODE_DIR=$self_path/nodejs +BROWSER_DIR=$self_path/web -if [ -n "$out" ]; then - echo "Will install package to $out (defined outside installPhase.sh script)" -else - export out="$self_path/outputs/out" - echo "Will install package to $out" +# Clear out the existing build artifacts as these aren't automatically removed by wasm-bindgen. +if [ -d ./pkg/ ]; then + rm -r $NODE_DIR + rm -r $BROWSER_DIR fi -run_or_fail $self_path/buildPhaseCargoCommand.sh -run_or_fail $self_path/installPhase.sh +TARGET=wasm32-unknown-unknown +WASM_BINARY=${self_path}/../../target/$TARGET/release/${pname}.wasm + +NODE_WASM=${NODE_DIR}/${pname}_bg.wasm +BROWSER_WASM=${BROWSER_DIR}/${pname}_bg.wasm -ln -s $out $self_path/result +# Build the new wasm package +run_or_fail cargo build --lib --release --target $TARGET --package ${pname} +run_or_fail wasm-bindgen $WASM_BINARY --out-dir $NODE_DIR --typescript --target nodejs +run_or_fail wasm-bindgen $WASM_BINARY --out-dir $BROWSER_DIR --typescript --target web +run_or_fail wasm-opt $NODE_WASM -o $NODE_WASM -O +run_or_fail wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O diff --git a/noir/noir-repo/acvm-repo/acvm_js/buildPhaseCargoCommand.sh b/noir/noir-repo/acvm-repo/acvm_js/buildPhaseCargoCommand.sh deleted file mode 100755 index 6c710bc938f..00000000000 --- a/noir/noir-repo/acvm-repo/acvm_js/buildPhaseCargoCommand.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -function run_or_fail { - "$@" - local status=$? - if [ $status -ne 0 ]; then - echo "Command '$*' failed with exit code $status" >&2 - exit $status - fi -} -function run_if_available { - if command -v "$1" >/dev/null 2>&1; then - "$@" - else - echo "$1 is not installed. Please install it to use this feature." >&2 - fi -} - -export self_path=$(dirname "$(readlink -f "$0")") - - -NODE_DIR=$self_path/nodejs/ -BROWSER_DIR=$self_path/web/ - -# Clear out the existing build artifacts as these aren't automatically removed by wasm-pack. -if [ -d ./pkg/ ]; then - rm -r $NODE_DIR - rm -r $BROWSER_DIR -fi - -TARGET=wasm32-unknown-unknown -WASM_BINARY=$CARGO_TARGET_DIR/$TARGET/release/${pname}.wasm - -NODE_WASM=${NODE_DIR}/${pname}_bg.wasm -BROWSER_WASM=${BROWSER_DIR}/${pname}_bg.wasm - -# Build the new wasm package -run_or_fail cargo build --lib --release --target $TARGET --package ${pname} -run_or_fail wasm-bindgen $WASM_BINARY --out-dir $NODE_DIR --typescript --target nodejs -run_or_fail wasm-bindgen $WASM_BINARY --out-dir $BROWSER_DIR --typescript --target web -run_if_available wasm-opt $NODE_WASM -o $NODE_WASM -O -run_if_available wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O diff --git a/noir/noir-repo/acvm-repo/acvm_js/installPhase.sh b/noir/noir-repo/acvm-repo/acvm_js/installPhase.sh deleted file mode 100755 index 34ddb8155e1..00000000000 --- a/noir/noir-repo/acvm-repo/acvm_js/installPhase.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -export self_path=$(dirname "$(readlink -f "$0")") - -export out_path=$out/acvm_js - -mkdir -p $out_path -cp $self_path/README.md $out_path/ -cp $self_path/package.json $out_path/ -cp -r $self_path/nodejs $out_path/ -cp -r $self_path/web $out_path/ diff --git a/noir/noir-repo/acvm-repo/acvm_js/package.json b/noir/noir-repo/acvm-repo/acvm_js/package.json index 55345a2ddf6..44d99f13c31 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/package.json +++ b/noir/noir-repo/acvm-repo/acvm_js/package.json @@ -34,9 +34,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0", "publish": "echo 📡 publishing `$npm_package_name` && yarn npm publish", "nightly:version": "jq --arg new_version \"-$(git rev-parse --short HEAD)$1\" '.version = .version + $new_version' package.json > package-tmp.json && mv package-tmp.json package.json", - "clean": "chmod u+w web nodejs || true && rm -rf web nodejs", - "build:nix": "nix build -L .#acvm_js", - "install:from:nix": "yarn clean && yarn build:nix && cp -rL ./result/acvm_js/nodejs ./ && cp -rL ./result/acvm_js/web ./" + "clean": "chmod u+w web nodejs || true && rm -rf web nodejs" }, "devDependencies": { "@esm-bundle/chai": "^4.3.4-fix.0", diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs b/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs index fc0e3b28ebf..188e5334ed5 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/black_box_solvers.rs @@ -59,7 +59,6 @@ pub fn ecdsa_secp256k1_verify( signature, ) .unwrap() - .into() } /// Verifies a ECDSA signature over the secp256r1 curve. @@ -81,5 +80,4 @@ pub fn ecdsa_secp256r1_verify( signature, ) .unwrap() - .into() } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/compression.rs b/noir/noir-repo/acvm-repo/acvm_js/src/compression.rs index 18e9216297e..8114e0d57d2 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/compression.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/compression.rs @@ -2,12 +2,12 @@ use acvm::acir::native_types::{WitnessMap, WitnessStack}; use js_sys::JsString; use wasm_bindgen::prelude::wasm_bindgen; -use crate::JsWitnessMap; +use crate::{JsWitnessMap, JsWitnessStack}; /// Compresses a `WitnessMap` into the binary format outputted by Nargo. /// -/// @param {Uint8Array} compressed_witness - A witness map. -/// @returns {WitnessMap} A compressed witness map +/// @param {WitnessMap} witness_map - A witness map. +/// @returns {Uint8Array} A compressed witness map #[wasm_bindgen(js_name = compressWitness, skip_jsdoc)] pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> { console_error_panic_hook::set_once(); @@ -21,6 +21,7 @@ pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> } /// Decompresses a compressed witness as outputted by Nargo into a `WitnessMap`. +/// This should be used to only fetch the witness map for the main function. /// /// @param {Uint8Array} compressed_witness - A compressed witness. /// @returns {WitnessMap} The decompressed witness map. @@ -28,8 +29,39 @@ pub fn compress_witness(witness_map: JsWitnessMap) -> Result, JsString> pub fn decompress_witness(compressed_witness: Vec) -> Result { console_error_panic_hook::set_once(); + let mut witness_stack = + WitnessStack::try_from(compressed_witness.as_slice()).map_err(|err| err.to_string())?; + + let witness = + witness_stack.pop().expect("Should have at least one witness on the stack").witness; + Ok(witness.into()) +} + +/// Compresses a `WitnessStack` into the binary format outputted by Nargo. +/// +/// @param {WitnessStack} witness_stack - A witness stack. +/// @returns {Uint8Array} A compressed witness stack +#[wasm_bindgen(js_name = compressWitnessStack, skip_jsdoc)] +pub fn compress_witness_stack(witness_stack: JsWitnessStack) -> Result, JsString> { + console_error_panic_hook::set_once(); + + let witness_stack = WitnessStack::from(witness_stack); + let compressed_witness_stack: Vec = + Vec::::try_from(witness_stack).map_err(|err| err.to_string())?; + + Ok(compressed_witness_stack) +} + +/// Decompresses a compressed witness stack as outputted by Nargo into a `WitnessStack`. +/// +/// @param {Uint8Array} compressed_witness - A compressed witness. +/// @returns {WitnessStack} The decompressed witness stack. +#[wasm_bindgen(js_name = decompressWitnessStack, skip_jsdoc)] +pub fn decompress_witness_stack(compressed_witness: Vec) -> Result { + console_error_panic_hook::set_once(); + let witness_stack = WitnessStack::try_from(compressed_witness.as_slice()).map_err(|err| err.to_string())?; - Ok(witness_stack.stack[0].witness.clone().into()) + Ok(witness_stack.into()) } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs index 60d27a489e2..0e58ccf039c 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/execute.rs @@ -1,5 +1,9 @@ +use std::{future::Future, pin::Pin}; + +use acvm::BlackBoxFunctionSolver; use acvm::{ - acir::circuit::Program, + acir::circuit::{Circuit, Program}, + acir::native_types::{WitnessMap, WitnessStack}, pwg::{ACVMStatus, ErrorLocation, OpcodeResolutionError, ACVM}, }; use bn254_blackbox_solver::Bn254BlackBoxSolver; @@ -9,7 +13,7 @@ use wasm_bindgen::prelude::wasm_bindgen; use crate::{ foreign_call::{resolve_brillig, ForeignCallHandler}, - JsExecutionError, JsWitnessMap, + JsExecutionError, JsWitnessMap, JsWitnessStack, }; #[wasm_bindgen] @@ -42,8 +46,16 @@ pub async fn execute_circuit( let solver = WasmBlackBoxFunctionSolver::initialize().await; - execute_circuit_with_black_box_solver(&solver, program, initial_witness, foreign_call_handler) - .await + let mut witness_stack = execute_program_with_native_type_return( + &solver, + program, + initial_witness, + &foreign_call_handler, + ) + .await?; + let witness_map = + witness_stack.pop().expect("Should have at least one witness on the stack").witness; + Ok(witness_map.into()) } /// Executes an ACIR circuit to generate the solved witness from the initial witness. @@ -56,69 +68,175 @@ pub async fn execute_circuit( #[wasm_bindgen(js_name = executeCircuitWithBlackBoxSolver, skip_jsdoc)] pub async fn execute_circuit_with_black_box_solver( solver: &WasmBlackBoxFunctionSolver, - // TODO(https://github.com/noir-lang/noir/issues/4428): These need to be updated to match the same interfaces - // as the native ACVM executor. Right now native execution still only handles one circuit so I do not feel the need - // to break the JS interface just yet. program: Vec, initial_witness: JsWitnessMap, foreign_call_handler: ForeignCallHandler, ) -> Result { console_error_panic_hook::set_once(); + + let mut witness_stack = execute_program_with_native_type_return( + solver, + program, + initial_witness, + &foreign_call_handler, + ) + .await?; + let witness_map = + witness_stack.pop().expect("Should have at least one witness on the stack").witness; + Ok(witness_map.into()) +} + +#[wasm_bindgen(js_name = executeProgram, skip_jsdoc)] +pub async fn execute_program( + program: Vec, + initial_witness: JsWitnessMap, + foreign_call_handler: ForeignCallHandler, +) -> Result { + console_error_panic_hook::set_once(); + + let solver = WasmBlackBoxFunctionSolver::initialize().await; + + execute_program_with_black_box_solver(&solver, program, initial_witness, &foreign_call_handler) + .await +} + +#[wasm_bindgen(js_name = executeProgramWithBlackBoxSolver, skip_jsdoc)] +pub async fn execute_program_with_black_box_solver( + solver: &WasmBlackBoxFunctionSolver, + program: Vec, + initial_witness: JsWitnessMap, + foreign_call_executor: &ForeignCallHandler, +) -> Result { + let witness_stack = execute_program_with_native_type_return( + solver, + program, + initial_witness, + foreign_call_executor, + ) + .await?; + + Ok(witness_stack.into()) +} + +async fn execute_program_with_native_type_return( + solver: &WasmBlackBoxFunctionSolver, + program: Vec, + initial_witness: JsWitnessMap, + foreign_call_executor: &ForeignCallHandler, +) -> Result { let program: Program = Program::deserialize_program(&program) - .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; - let circuit = match program.functions.len() { - 0 => return Ok(initial_witness), - 1 => &program.functions[0], - _ => return Err(JsExecutionError::new("Program contains multiple circuits however ACVM currently only supports programs containing a single circuit".to_string(), None).into()) - }; - - let mut acvm = ACVM::new(&solver.0, &circuit.opcodes, initial_witness.into()); - - loop { - let solver_status = acvm.solve(); - - match solver_status { - ACVMStatus::Solved => break, - ACVMStatus::InProgress => { - unreachable!("Execution should not stop while in `InProgress` state.") - } - ACVMStatus::Failure(error) => { - let (assert_message, call_stack) = match &error { - OpcodeResolutionError::UnsatisfiedConstrain { - opcode_location: ErrorLocation::Resolved(opcode_location), - } - | OpcodeResolutionError::IndexOutOfBounds { - opcode_location: ErrorLocation::Resolved(opcode_location), - .. - } => { - (circuit.get_assert_message(*opcode_location), Some(vec![*opcode_location])) + .map_err(|_| JsExecutionError::new("Failed to deserialize circuit. This is likely due to differing serialization formats between ACVM_JS and your compiler".to_string(), None))?; + + let executor = ProgramExecutor::new(&program.functions, &solver.0, foreign_call_executor); + let witness_stack = executor.execute(initial_witness.into()).await?; + + Ok(witness_stack) +} + +struct ProgramExecutor<'a, B: BlackBoxFunctionSolver> { + functions: &'a [Circuit], + + blackbox_solver: &'a B, + + foreign_call_handler: &'a ForeignCallHandler, +} + +impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { + fn new( + functions: &'a [Circuit], + blackbox_solver: &'a B, + foreign_call_handler: &'a ForeignCallHandler, + ) -> Self { + ProgramExecutor { functions, blackbox_solver, foreign_call_handler } + } + + async fn execute(&self, initial_witness: WitnessMap) -> Result { + let main = &self.functions[0]; + + let mut witness_stack = WitnessStack::default(); + let main_witness = self.execute_circuit(main, initial_witness, &mut witness_stack).await?; + witness_stack.push(0, main_witness); + Ok(witness_stack) + } + + fn execute_circuit( + &'a self, + circuit: &'a Circuit, + initial_witness: WitnessMap, + witness_stack: &'a mut WitnessStack, + ) -> Pin> + 'a>> { + Box::pin(async { + let mut acvm = ACVM::new(self.blackbox_solver, &circuit.opcodes, initial_witness); + + loop { + let solver_status = acvm.solve(); + + match solver_status { + ACVMStatus::Solved => break, + ACVMStatus::InProgress => { + unreachable!("Execution should not stop while in `InProgress` state.") } - OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { - let failing_opcode = - call_stack.last().expect("Brillig error call stacks cannot be empty"); - (circuit.get_assert_message(*failing_opcode), Some(call_stack.clone())) + ACVMStatus::Failure(error) => { + let (assert_message, call_stack) = match &error { + OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: ErrorLocation::Resolved(opcode_location), + } + | OpcodeResolutionError::IndexOutOfBounds { + opcode_location: ErrorLocation::Resolved(opcode_location), + .. + } => ( + circuit.get_assert_message(*opcode_location), + Some(vec![*opcode_location]), + ), + OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { + let failing_opcode = call_stack + .last() + .expect("Brillig error call stacks cannot be empty"); + ( + circuit.get_assert_message(*failing_opcode), + Some(call_stack.clone()), + ) + } + _ => (None, None), + }; + + let error_string = match &assert_message { + Some(assert_message) => format!("Assertion failed: {}", assert_message), + None => error.to_string(), + }; + + return Err(JsExecutionError::new(error_string, call_stack).into()); } - _ => (None, None), - }; - - let error_string = match &assert_message { - Some(assert_message) => format!("Assertion failed: {}", assert_message), - None => error.to_string(), - }; + ACVMStatus::RequiresForeignCall(foreign_call) => { + let result = + resolve_brillig(self.foreign_call_handler, &foreign_call).await?; - return Err(JsExecutionError::new(error_string.into(), call_stack).into()); + acvm.resolve_pending_foreign_call(result); + } + ACVMStatus::RequiresAcirCall(call_info) => { + let acir_to_call = &self.functions[call_info.id as usize]; + let initial_witness = call_info.initial_witness; + let call_solved_witness = self + .execute_circuit(acir_to_call, initial_witness, witness_stack) + .await?; + let mut call_resolved_outputs = Vec::new(); + for return_witness_index in acir_to_call.return_values.indices() { + if let Some(return_value) = + call_solved_witness.get_index(return_witness_index) + { + call_resolved_outputs.push(*return_value); + } else { + // TODO: look at changing this call stack from None + return Err(JsExecutionError::new(format!("Failed to read from solved witness of ACIR call at witness {}", return_witness_index), None).into()); + } + } + acvm.resolve_pending_acir_call(call_resolved_outputs); + witness_stack.push(call_info.id, call_solved_witness.clone()); + } + } } - ACVMStatus::RequiresForeignCall(foreign_call) => { - let result = resolve_brillig(&foreign_call_handler, &foreign_call).await?; - acvm.resolve_pending_foreign_call(result); - } - ACVMStatus::RequiresAcirCall(_) => { - todo!("Handle acir calls in acvm JS"); - } - } + Ok(acvm.finalize()) + }) } - - let witness_map = acvm.finalize(); - Ok(witness_map.into()) } diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/js_witness_stack.rs b/noir/noir-repo/acvm-repo/acvm_js/src/js_witness_stack.rs new file mode 100644 index 00000000000..59f2dbc051e --- /dev/null +++ b/noir/noir-repo/acvm-repo/acvm_js/src/js_witness_stack.rs @@ -0,0 +1,71 @@ +use acvm::acir::native_types::WitnessStack; +use js_sys::{Array, Map, Object}; +use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; + +use crate::JsWitnessMap; + +#[wasm_bindgen(typescript_custom_section)] +const WITNESS_STACK: &'static str = r#" +export type StackItem = { + index: number; + witness: WitnessMap; +} + +export type WitnessStack = Array; +"#; + +// WitnessStack +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(extends = Array, js_name = "WitnessStack", typescript_type = "WitnessStack")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsWitnessStack; + + #[wasm_bindgen(constructor, js_class = "Array")] + pub fn new() -> JsWitnessStack; + + #[wasm_bindgen(extends = Object, js_name = "StackItem", typescript_type = "StackItem")] + #[derive(Clone, Debug, PartialEq, Eq)] + pub type JsStackItem; + + #[wasm_bindgen(constructor, js_class = "Object")] + pub fn new() -> JsStackItem; +} + +impl Default for JsWitnessStack { + fn default() -> Self { + Self::new() + } +} + +impl From for JsWitnessStack { + fn from(mut witness_stack: WitnessStack) -> Self { + let js_witness_stack = JsWitnessStack::new(); + while let Some(stack_item) = witness_stack.pop() { + let js_map = JsWitnessMap::from(stack_item.witness); + let js_index = JsValue::from_f64(stack_item.index.into()); + + let entry_map = Map::new(); + entry_map.set(&JsValue::from_str("index"), &js_index); + entry_map.set(&JsValue::from_str("witness"), &js_map); + let stack_item = Object::from_entries(&entry_map).unwrap(); + + js_witness_stack.push(&stack_item); + } + // `reverse()` returns an `Array` so we have to wrap it + JsWitnessStack { obj: js_witness_stack.reverse() } + } +} + +impl From for WitnessStack { + fn from(js_witness_stack: JsWitnessStack) -> Self { + let mut witness_stack = WitnessStack::default(); + js_witness_stack.for_each(&mut |stack_item, _, _| { + let values_array = Object::values(&Object::from(stack_item)); + let index = values_array.get(0).as_f64().unwrap() as u32; + let js_witness_map: JsWitnessMap = values_array.get(1).into(); + witness_stack.push(index, js_witness_map.into()); + }); + witness_stack + } +} diff --git a/noir/noir-repo/acvm-repo/acvm_js/src/lib.rs b/noir/noir-repo/acvm-repo/acvm_js/src/lib.rs index 88afd1767c9..d7ecc0ae192 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/src/lib.rs +++ b/noir/noir-repo/acvm-repo/acvm_js/src/lib.rs @@ -1,31 +1,31 @@ -#![forbid(unsafe_code)] #![warn(unreachable_pub)] #![warn(clippy::semicolon_if_nothing_returned)] #![cfg_attr(not(test), warn(unused_crate_dependencies, unused_extern_crates))] -// TODO: Absence of per package targets -// https://doc.rust-lang.org/cargo/reference/unstable.html#per-package-target -// otherwise could be reorganized to make this file more pretty. +mod black_box_solvers; +mod build_info; +mod compression; +mod execute; +mod foreign_call; +mod js_execution_error; +mod js_witness_map; +mod js_witness_stack; +mod logging; +mod public_witness; -cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { - mod build_info; - mod compression; - mod execute; - mod foreign_call; - mod js_witness_map; - mod logging; - mod public_witness; - mod js_execution_error; - mod black_box_solvers; - - pub use black_box_solvers::{and, xor, sha256, blake2s256, keccak256, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify}; - pub use build_info::build_info; - pub use compression::{compress_witness, decompress_witness}; - pub use execute::{execute_circuit, execute_circuit_with_black_box_solver, create_black_box_solver}; - pub use js_witness_map::JsWitnessMap; - pub use logging::init_log_level; - pub use public_witness::{get_public_parameters_witness, get_public_witness, get_return_witness}; - pub use js_execution_error::JsExecutionError; - } -} +pub use black_box_solvers::{ + and, blake2s256, ecdsa_secp256k1_verify, ecdsa_secp256r1_verify, keccak256, sha256, xor, +}; +pub use build_info::build_info; +pub use compression::{ + compress_witness, compress_witness_stack, decompress_witness, decompress_witness_stack, +}; +pub use execute::{ + create_black_box_solver, execute_circuit, execute_circuit_with_black_box_solver, + execute_program, execute_program_with_black_box_solver, +}; +pub use js_execution_error::JsExecutionError; +pub use js_witness_map::JsWitnessMap; +pub use js_witness_stack::JsWitnessStack; +pub use logging::init_log_level; +pub use public_witness::{get_public_parameters_witness, get_public_witness, get_return_witness}; diff --git a/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts b/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts index adee3c15312..32487f8bbba 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts +++ b/noir/noir-repo/acvm-repo/acvm_js/test/node/execute_circuit.test.ts @@ -6,6 +6,9 @@ import { WasmBlackBoxFunctionSolver, WitnessMap, ForeignCallHandler, + executeProgram, + WitnessStack, + StackItem, } from '@noir-lang/acvm_js'; it('successfully executes circuit and extracts return value', async () => { @@ -157,3 +160,37 @@ it('successfully executes 500 circuits with same backend', async function () { expect(solvedWitness).to.be.deep.eq(expectedWitnessMap); } }); + +/** + * Below are all the same tests as above but using `executeProgram` + * TODO: also add a couple tests for executing multiple circuits + */ +it('executeProgram: successfully executes program and extracts return value', async () => { + const { bytecode, initialWitnessMap, resultWitness, expectedResult } = await import('../shared/addition'); + + const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, () => { + throw Error('unexpected oracle'); + }); + + const solvedStackItem: StackItem = witnessStack[0]; + expect(solvedStackItem.index).to.be.eq(0); + const solvedWitnessMap: WitnessMap = solvedStackItem.witness; + + // Witness stack should be consistent with initial witness + initialWitnessMap.forEach((value, key) => { + expect(solvedWitnessMap.get(key) as string).to.be.eq(value); + }); + + // Solved witness should contain expected return value + expect(solvedWitnessMap.get(resultWitness)).to.be.eq(expectedResult); +}); + +it('executeProgram: successfully process a program of acir functions with a nested call', async () => { + const { bytecode, initialWitnessMap, expectedWitnessStack } = await import('../shared/nested_acir_call'); + + const witnessStack: WitnessStack = await executeProgram(bytecode, initialWitnessMap, () => { + throw Error('unexpected oracle'); + }); + + expect(witnessStack).to.be.deep.eq(expectedWitnessStack); +}); diff --git a/noir/noir-repo/acvm-repo/acvm_js/test/node/witness_conversion.test.ts b/noir/noir-repo/acvm-repo/acvm_js/test/node/witness_conversion.test.ts index 41291c894ea..c6dccb4c83d 100644 --- a/noir/noir-repo/acvm-repo/acvm_js/test/node/witness_conversion.test.ts +++ b/noir/noir-repo/acvm-repo/acvm_js/test/node/witness_conversion.test.ts @@ -1,6 +1,7 @@ import { expect } from 'chai'; -import { compressWitness, decompressWitness } from '@noir-lang/acvm_js'; +import { compressWitness, decompressWitness, compressWitnessStack, decompressWitnessStack } from '@noir-lang/acvm_js'; import { expectedCompressedWitnessMap, expectedWitnessMap } from '../shared/witness_compression'; +import { expectedCompressedWitnessStack, expectedWitnessStack } from '../shared/nested_acir_call'; it('successfully compresses the witness', () => { const compressedWitnessMap = compressWitness(expectedWitnessMap); @@ -13,3 +14,15 @@ it('successfully decompresses the witness', () => { expect(witnessMap).to.be.deep.eq(expectedWitnessMap); }); + +it('successfully compresses the witness stack', () => { + const compressedWitnessStack = compressWitnessStack(expectedWitnessStack); + + expect(compressedWitnessStack).to.be.deep.eq(expectedCompressedWitnessStack); +}); + +it('successfully decompresses the witness stack', () => { + const witnessStack = decompressWitnessStack(expectedCompressedWitnessStack); + + expect(witnessStack).to.be.deep.eq(expectedWitnessStack); +}); diff --git a/noir/noir-repo/acvm-repo/acvm_js/test/shared/nested_acir_call.ts b/noir/noir-repo/acvm-repo/acvm_js/test/shared/nested_acir_call.ts new file mode 100644 index 00000000000..ce91282a681 --- /dev/null +++ b/noir/noir-repo/acvm-repo/acvm_js/test/shared/nested_acir_call.ts @@ -0,0 +1,59 @@ +import { WitnessMap, StackItem, WitnessStack } from '@noir-lang/acvm_js'; + +// See `nested_acir_call_circuit` integration test in `acir/tests/test_program_serialization.rs`. +export const bytecode = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, 24, 109, 227, 191, 93, 101, + 50, 123, 255, 35, 172, 99, 25, 83, 17, 250, 99, 14, 250, 224, 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, 57, + 108, 14, 91, 248, 202, 168, 65, 255, 207, 122, 28, 180, 250, 244, 221, 244, 197, 223, 68, 182, 154, 197, 184, 134, 80, + 54, 95, 136, 233, 142, 62, 101, 137, 24, 98, 94, 133, 132, 162, 196, 135, 23, 230, 34, 65, 182, 148, 211, 134, 137, 2, + 23, 218, 99, 226, 93, 135, 185, 121, 123, 33, 84, 12, 234, 218, 192, 64, 174, 3, 248, 47, 88, 48, 17, 150, 157, 183, + 151, 95, 244, 86, 91, 221, 61, 10, 81, 31, 178, 190, 110, 194, 102, 96, 76, 251, 202, 80, 13, 204, 77, 224, 25, 176, + 70, 79, 197, 128, 18, 64, 3, 4, 0, 0, +]); + +export const initialWitnessMap: WitnessMap = new Map([ + [0, '0x0000000000000000000000000000000000000000000000000000000000000008'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], +]); + +const inner_call_witness: StackItem = { + index: 2, + witness: new Map([ + [0, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], + ]), +}; + +const nested_call_witness: StackItem = { + index: 1, + witness: new Map([ + [0, '0x0000000000000000000000000000000000000000000000000000000000000008'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [2, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [3, '0x000000000000000000000000000000000000000000000000000000000000000a'], + ]), +}; + +const main_witness: StackItem = { + index: 0, + witness: new Map([ + [0, '0x0000000000000000000000000000000000000000000000000000000000000008'], + [1, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [2, '0x000000000000000000000000000000000000000000000000000000000000000a'], + [3, '0x000000000000000000000000000000000000000000000000000000000000000a'], + ]), +}; + +export const expectedWitnessStack: WitnessStack = [ + inner_call_witness, + nested_call_witness, + inner_call_witness, + nested_call_witness, + main_witness, +]; + +export const expectedCompressedWitnessStack = Uint8Array.from([ + 31, 139, 8, 0, 0, 0, 0, 0, 2, 255, 237, 145, 177, 13, 0, 32, 8, 4, 17, 117, 31, 75, 75, 87, 113, 255, 37, 44, 196, 5, + 228, 42, 194, 39, 132, 238, 114, 249, 239, 114, 163, 118, 47, 203, 254, 240, 101, 23, 152, 213, 120, 199, 73, 58, 42, + 200, 170, 176, 87, 238, 27, 119, 95, 201, 238, 190, 89, 7, 37, 195, 196, 176, 4, 5, 0, 0, +]); diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml index 396e4aa0146..1ad5103d2cb 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml @@ -17,16 +17,10 @@ acir.workspace = true acvm_blackbox_solver.workspace = true thiserror.workspace = true num-traits.workspace = true +cfg-if = "1.0.0" -rust-embed = { version = "6.6.0", features = [ - "debug-embed", - "interpolate-folder-path", - "include-exclude", -] } - -grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = [ - "std", -] } # BN254 fixed base scalar multiplication solver +# BN254 fixed base scalar multiplication solver +grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } ark-ec = { version = "^0.4.0", default-features = false } ark-ff = { version = "^0.4.0", default-features = false } num-bigint.workspace = true @@ -44,15 +38,6 @@ js-sys.workspace = true getrandom.workspace = true wasmer = "4.2.6" -[build-dependencies] -pkg-config = "0.3" -tar = "~0.4.15" -flate2 = "~1.0.1" -reqwest = { version = "0.11.20", default-features = false, features = [ - "rustls-tls", - "blocking", -] } - [features] default = ["bn254"] bn254 = ["acir/bn254"] diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/build.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/build.rs deleted file mode 100644 index 4269c86aba0..00000000000 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/build.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::path::PathBuf; - -const BARRETENBERG_BIN_DIR: &str = "BARRETENBERG_BIN_DIR"; - -fn main() -> Result<(), String> { - let out_dir = std::env::var("OUT_DIR").unwrap(); - - let dest_path = PathBuf::from(out_dir.clone()).join("acvm_backend.wasm"); - - println!("cargo:rustc-env={BARRETENBERG_BIN_DIR}={out_dir}"); - std::fs::copy("./src/acvm_backend.wasm", dest_path).unwrap(); - - Ok(()) -} diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs index be0e60ada96..231594170e3 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -20,10 +20,17 @@ pub struct Bn254BlackBoxSolver { } impl Bn254BlackBoxSolver { - #[cfg(target_arch = "wasm32")] pub async fn initialize() -> Bn254BlackBoxSolver { - let blackbox_vendor = Barretenberg::initialize().await; - Bn254BlackBoxSolver { blackbox_vendor } + // We fallback to the sync initialization of barretenberg on non-wasm targets. + // This ensures that wasm packages consuming this still build on the default target (useful for linting, etc.) + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + let blackbox_vendor = Barretenberg::initialize().await; + Bn254BlackBoxSolver { blackbox_vendor } + } else { + Bn254BlackBoxSolver::new() + } + } } #[cfg(not(target_arch = "wasm32"))] diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/acvm_backend.wasm b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/wasm/acvm_backend.wasm similarity index 100% rename from noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/acvm_backend.wasm rename to noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/wasm/acvm_backend.wasm diff --git a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/wasm/mod.rs b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/wasm/mod.rs index 10b1ab22a8d..f4f6f56aa99 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/wasm/mod.rs +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/src/wasm/mod.rs @@ -45,7 +45,7 @@ pub(crate) struct BackendError(#[from] Error); impl From for BackendError { fn from(value: FeatureError) -> Self { - value.into() + BackendError(Error::FromFeature(value)) } } @@ -76,10 +76,7 @@ use wasmer::{ pub(super) const WASM_SCRATCH_BYTES: usize = 1024; /// Embed the Barretenberg WASM file -#[derive(rust_embed::RustEmbed)] -#[folder = "$BARRETENBERG_BIN_DIR"] -#[include = "acvm_backend.wasm"] -struct Wasm; +const WASM_BIN: &[u8] = include_bytes!("./acvm_backend.wasm"); impl Barretenberg { #[cfg(not(target_arch = "wasm32"))] @@ -287,7 +284,7 @@ fn instance_load() -> (Instance, Memory, Store) { let (memory, mut store, custom_imports) = init_memory_and_state(); - let module = Module::new(&store, Wasm::get("acvm_backend.wasm").unwrap().data).unwrap(); + let module = Module::new(&store, WASM_BIN).unwrap(); (Instance::new(&mut store, &module, &custom_imports).unwrap(), memory, store) } @@ -299,9 +296,7 @@ async fn instance_load() -> (Instance, Memory, Store) { let (memory, mut store, custom_imports) = init_memory_and_state(); - let wasm_binary = Wasm::get("acvm_backend.wasm").unwrap().data; - - let js_bytes = unsafe { js_sys::Uint8Array::view(&wasm_binary) }; + let js_bytes = unsafe { js_sys::Uint8Array::view(&WASM_BIN) }; let js_module_promise = WebAssembly::compile(&js_bytes); let js_module: js_sys::WebAssembly::Module = wasm_bindgen_futures::JsFuture::from(js_module_promise).await.unwrap().into(); @@ -309,7 +304,7 @@ async fn instance_load() -> (Instance, Memory, Store) { let js_instance_promise = WebAssembly::instantiate_module(&js_module, &custom_imports.as_jsvalue(&store).into()); let js_instance = wasm_bindgen_futures::JsFuture::from(js_instance_promise).await.unwrap(); - let module: wasmer::Module = (js_module, wasm_binary).into(); + let module = wasmer::Module::from((js_module, WASM_BIN)); let instance: wasmer::Instance = Instance::from_jsvalue(&mut store, &module, &js_instance) .map_err(|_| "Error while creating BlackBox Functions vendor instance") .unwrap(); diff --git a/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs b/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs index ab4358739e9..bd33b5ee8fc 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs +++ b/noir/noir-repo/acvm-repo/brillig_vm/src/black_box.rs @@ -256,10 +256,11 @@ fn black_box_function_from_op(op: &BlackBoxOp) -> BlackBoxFunc { #[cfg(test)] mod test { use acir::brillig::{BlackBoxOp, MemoryAddress}; + use acvm_blackbox_solver::StubbedBlackBoxSolver; use crate::{ black_box::{evaluate_black_box, to_u8_vec, to_value_vec}, - DummyBlackBoxSolver, HeapArray, HeapVector, Memory, + HeapArray, HeapVector, Memory, }; #[test] @@ -280,7 +281,7 @@ mod test { output: HeapArray { pointer: 2.into(), size: 32 }, }; - evaluate_black_box(&op, &DummyBlackBoxSolver, &mut memory).unwrap(); + evaluate_black_box(&op, &StubbedBlackBoxSolver, &mut memory).unwrap(); let result = memory.read_slice(MemoryAddress(result_pointer), 32); diff --git a/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs b/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs index 0f430b0d5b2..65654e24720 100644 --- a/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs +++ b/noir/noir-repo/acvm-repo/brillig_vm/src/lib.rs @@ -16,7 +16,7 @@ use acir::brillig::{ HeapVector, MemoryAddress, Opcode, ValueOrArray, }; use acir::FieldElement; -use acvm_blackbox_solver::{BlackBoxFunctionSolver, BlackBoxResolutionError}; +use acvm_blackbox_solver::BlackBoxFunctionSolver; use arithmetic::{evaluate_binary_field_op, evaluate_binary_int_op, BrilligArithmeticError}; use black_box::evaluate_black_box; use num_bigint::BigUint; @@ -593,59 +593,10 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { } } -pub(crate) struct DummyBlackBoxSolver; - -impl BlackBoxFunctionSolver for DummyBlackBoxSolver { - fn schnorr_verify( - &self, - _public_key_x: &FieldElement, - _public_key_y: &FieldElement, - _signature: &[u8], - _message: &[u8], - ) -> Result { - Ok(true) - } - fn pedersen_commitment( - &self, - _inputs: &[FieldElement], - _domain_separator: u32, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - Ok((2_u128.into(), 3_u128.into())) - } - fn pedersen_hash( - &self, - _inputs: &[FieldElement], - _domain_separator: u32, - ) -> Result { - Ok(6_u128.into()) - } - fn fixed_base_scalar_mul( - &self, - _low: &FieldElement, - _high: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - Ok((4_u128.into(), 5_u128.into())) - } - fn ec_add( - &self, - _input1_x: &FieldElement, - _input1_y: &FieldElement, - _input2_x: &FieldElement, - _input2_y: &FieldElement, - ) -> Result<(FieldElement, FieldElement), BlackBoxResolutionError> { - Ok((5_u128.into(), 6_u128.into())) - } - fn poseidon2_permutation( - &self, - _input: &[FieldElement], - len: u32, - ) -> Result, BlackBoxResolutionError> { - Ok(vec![0_u128.into(); len as usize]) - } -} - #[cfg(test)] mod tests { + use acvm_blackbox_solver::StubbedBlackBoxSolver; + use super::*; #[test] @@ -662,7 +613,7 @@ mod tests { // Start VM let opcodes = [calldata_copy]; - let mut vm = VM::new(calldata, &opcodes, vec![], &DummyBlackBoxSolver); + let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); // Process a single VM opcode // @@ -706,7 +657,7 @@ mod tests { opcodes.push(Opcode::Jump { location: 3 }); opcodes.push(Opcode::JumpIf { condition: destination, location: 4 }); - let mut vm = VM::new(calldata, &opcodes, vec![], &DummyBlackBoxSolver); + let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -763,7 +714,7 @@ mod tests { jump_if_not_opcode, add_opcode, ]; - let mut vm = VM::new(calldata, &opcodes, vec![], &DummyBlackBoxSolver); + let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -811,7 +762,7 @@ mod tests { }, Opcode::Stop { return_data_offset: 1, return_data_size: 1 }, ]; - let mut vm = VM::new(calldata, opcodes, vec![], &DummyBlackBoxSolver); + let mut vm = VM::new(calldata, opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -842,7 +793,7 @@ mod tests { Opcode::Mov { destination: MemoryAddress::from(2), source: MemoryAddress::from(0) }; let opcodes = &[calldata_copy, mov_opcode]; - let mut vm = VM::new(calldata, opcodes, vec![], &DummyBlackBoxSolver); + let mut vm = VM::new(calldata, opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -898,7 +849,7 @@ mod tests { condition: MemoryAddress(1), }, ]; - let mut vm = VM::new(calldata, opcodes, vec![], &DummyBlackBoxSolver); + let mut vm = VM::new(calldata, opcodes, vec![], &StubbedBlackBoxSolver); let status = vm.process_opcode(); assert_eq!(status, VMStatus::InProgress); @@ -981,7 +932,7 @@ mod tests { .chain(cast_opcodes) .chain([equal_opcode, not_equal_opcode, less_than_opcode, less_than_equal_opcode]) .collect(); - let mut vm = VM::new(calldata, &opcodes, vec![], &DummyBlackBoxSolver); + let mut vm = VM::new(calldata, &opcodes, vec![], &StubbedBlackBoxSolver); // Calldata copy let status = vm.process_opcode(); @@ -1276,14 +1227,14 @@ mod tests { fn brillig_execute_and_get_vm( calldata: Vec, opcodes: &[Opcode], - ) -> VM<'_, DummyBlackBoxSolver> { - let mut vm = VM::new(calldata, opcodes, vec![], &DummyBlackBoxSolver); + ) -> VM<'_, StubbedBlackBoxSolver> { + let mut vm = VM::new(calldata, opcodes, vec![], &StubbedBlackBoxSolver); brillig_execute(&mut vm); assert_eq!(vm.call_stack, vec![]); vm } - fn brillig_execute(vm: &mut VM) { + fn brillig_execute(vm: &mut VM) { loop { let status = vm.process_opcode(); if matches!(status, VMStatus::Finished { .. } | VMStatus::ForeignCallWait { .. }) { diff --git a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs index fd538dc578b..1f5681ed470 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/compute_note_hash_and_nullifier.rs @@ -127,7 +127,7 @@ pub fn inject_compute_note_hash_and_nullifier( Ok(()) } -fn generate_compute_note_hash_and_nullifier(note_types: &Vec) -> NoirFunction { +fn generate_compute_note_hash_and_nullifier(note_types: &[String]) -> NoirFunction { let function_source = generate_compute_note_hash_and_nullifier_source(note_types); let (function_ast, errors) = parse_program(&function_source); @@ -140,7 +140,7 @@ fn generate_compute_note_hash_and_nullifier(note_types: &Vec) -> NoirFun function_ast.functions.remove(0) } -fn generate_compute_note_hash_and_nullifier_source(note_types: &Vec) -> String { +fn generate_compute_note_hash_and_nullifier_source(note_types: &[String]) -> String { // TODO(#4649): The serialized_note parameter is a fixed-size array, but we don't know what length it should have. // For now we hardcode it to 20, which is the same as MAX_NOTE_FIELDS_LENGTH. diff --git a/noir/noir-repo/compiler/noirc_driver/build.rs b/noir/noir-repo/compiler/noirc_driver/build.rs index 73a56142075..2ed109398a4 100644 --- a/noir/noir-repo/compiler/noirc_driver/build.rs +++ b/noir/noir-repo/compiler/noirc_driver/build.rs @@ -2,8 +2,7 @@ const GIT_COMMIT: &&str = &"GIT_COMMIT"; use std::path::Path; fn main() { - // Only use build_data if the environment variable isn't set - // The environment variable is always set when working via Nix + // Only use build_data if the environment variable isn't set. if std::env::var(GIT_COMMIT).is_err() { build_data::set_GIT_COMMIT(); build_data::set_GIT_DIRTY(); diff --git a/noir/noir-repo/compiler/noirc_driver/src/contract.rs b/noir/noir-repo/compiler/noirc_driver/src/contract.rs index 9a0e25a321b..bd78cb23cdc 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/contract.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/contract.rs @@ -51,4 +51,7 @@ pub struct ContractFunction { pub bytecode: Program, pub debug: DebugInfo, + + /// Names of the functions in the program. These are used for more informative debugging and benchmarking. + pub names: Vec, } diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index e52f702ad7c..8fa2d9680c8 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -11,12 +11,15 @@ use noirc_abi::{AbiParameter, AbiType, ContractEvent}; use noirc_errors::{CustomDiagnostic, FileDiagnostic}; use noirc_evaluator::create_program; use noirc_evaluator::errors::RuntimeError; +use noirc_evaluator::ssa::SsaProgramArtifact; use noirc_frontend::debug::build_debug_crate_file; use noirc_frontend::graph::{CrateId, CrateName}; use noirc_frontend::hir::def_map::{Contract, CrateDefMap}; use noirc_frontend::hir::Context; use noirc_frontend::macros_api::MacroProcessor; -use noirc_frontend::monomorphization::{monomorphize, monomorphize_debug, MonomorphizationError}; +use noirc_frontend::monomorphization::{ + errors::MonomorphizationError, monomorphize, monomorphize_debug, +}; use noirc_frontend::node_interner::FuncId; use noirc_frontend::token::SecondaryAttribute; use std::path::Path; @@ -68,6 +71,10 @@ pub struct CompileOptions { #[arg(long)] pub print_acir: bool, + /// Pretty print benchmark times of each code generation pass + #[arg(long, hide = true)] + pub benchmark_codegen: bool, + /// Treat all warnings as errors #[arg(long, conflicts_with = "silence_warnings")] pub deny_warnings: bool, @@ -417,6 +424,7 @@ fn compile_contract_inner( bytecode: function.program, debug: function.debug, is_unconstrained: modifiers.is_unconstrained, + names: function.names, }); } @@ -479,11 +487,28 @@ pub fn compile_no_check( } let visibility = program.return_visibility; - let (program, debug, warnings, input_witnesses, return_witnesses) = - create_program(program, options.show_ssa, options.show_brillig, options.force_brillig)?; - - let abi = - abi_gen::gen_abi(context, &main_function, input_witnesses, return_witnesses, visibility); + let SsaProgramArtifact { + program, + debug, + warnings, + main_input_witnesses, + main_return_witnesses, + names, + } = create_program( + program, + options.show_ssa, + options.show_brillig, + options.force_brillig, + options.benchmark_codegen, + )?; + + let abi = abi_gen::gen_abi( + context, + &main_function, + main_input_witnesses, + main_return_witnesses, + visibility, + ); let file_map = filter_relevant_files(&debug, &context.file_manager); Ok(CompiledProgram { @@ -499,5 +524,6 @@ pub fn compile_no_check( file_map, noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(), warnings, + names, }) } diff --git a/noir/noir-repo/compiler/noirc_driver/src/program.rs b/noir/noir-repo/compiler/noirc_driver/src/program.rs index 6f527297dcb..9ffd2d70dda 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/program.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/program.rs @@ -27,4 +27,6 @@ pub struct CompiledProgram { pub debug: DebugInfo, pub file_map: BTreeMap, pub warnings: Vec, + /// Names of the functions in the program. These are used for more informative debugging and benchmarking. + pub names: Vec, } diff --git a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml index a8f0e8d83a9..fad7c3c309e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml @@ -17,4 +17,5 @@ thiserror.workspace = true num-bigint = "0.4" im = { version = "15.1", features = ["serde"] } serde.workspace = true -tracing.workspace = true \ No newline at end of file +tracing.workspace = true +chrono = "0.4.37" \ No newline at end of file diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs index 92027026ce8..617e400b92f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs @@ -70,7 +70,7 @@ impl FunctionContext { function_id.to_string() } - fn ssa_type_to_parameter(typ: &Type) -> BrilligParameter { + pub(crate) fn ssa_type_to_parameter(typ: &Type) -> BrilligParameter { match typ { Type::Numeric(_) | Type::Reference(_) => { BrilligParameter::SingleAddr(get_bit_size_from_ssa_type(typ)) @@ -81,26 +81,13 @@ impl FunctionContext { }), *size, ), - Type::Slice(item_type) => { - BrilligParameter::Slice(vecmap(item_type.iter(), |item_typ| { - FunctionContext::ssa_type_to_parameter(item_typ) - })) + Type::Slice(_) => { + panic!("ICE: Slice parameters cannot be derived from type information") } _ => unimplemented!("Unsupported function parameter/return type {typ:?}"), } } - /// Collects the parameters of a given function - pub(crate) fn parameters(func: &Function) -> Vec { - func.parameters() - .iter() - .map(|&value_id| { - let typ = func.dfg.type_of_value(value_id); - FunctionContext::ssa_type_to_parameter(&typ) - }) - .collect() - } - /// Collects the return values of a given function pub(crate) fn return_values(func: &Function) -> Vec { func.returns() 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 d10dcf13d9f..8ce15ba4e73 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 @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, HashMap}; use crate::ssa::ir::dfg::CallStack; -/// Represents a parameter or a return value of a function. +/// Represents a parameter or a return value of an entry point function. #[derive(Debug, Clone)] pub(crate) enum BrilligParameter { /// A single address parameter or return value. Holds the bit size of the parameter. @@ -11,7 +11,8 @@ pub(crate) enum BrilligParameter { /// An array parameter or return value. Holds the type of an array item and its size. Array(Vec, usize), /// A slice parameter or return value. Holds the type of a slice item. - Slice(Vec), + /// Only known-length slices can be passed to brillig entry points, so the size is available as well. + Slice(Vec, usize), } /// The result of compiling and linking brillig artifacts. @@ -108,7 +109,7 @@ impl BrilligArtifact { self.byte_code.append(&mut byte_code); // Remove all resolved external calls and transform them to jumps - let is_resolved = |label: &Label| self.labels.get(label).is_some(); + let is_resolved = |label: &Label| self.labels.contains_key(label); let resolved_external_calls = self .unresolved_external_call_labels diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs index 1d823ded718..db872487fcc 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/entry_point.rs @@ -1,6 +1,6 @@ use super::{ artifact::{BrilligArtifact, BrilligParameter}, - brillig_variable::{BrilligArray, BrilligVariable, SingleAddrVariable}, + brillig_variable::{BrilligArray, BrilligVariable, BrilligVector, SingleAddrVariable}, debug_show::DebugShow, registers::BrilligRegistersContext, BrilligBinaryOp, BrilligContext, ReservedRegisters, @@ -83,24 +83,56 @@ impl BrilligContext { current_calldata_pointer += flattened_size; var } - BrilligParameter::Slice(_) => unimplemented!("Unsupported slices as parameter"), + BrilligParameter::Slice(_, _) => { + let pointer_to_the_array_in_calldata = + self.make_usize_constant_instruction(current_calldata_pointer.into()); + + let flattened_size = BrilligContext::flattened_size(argument); + let size_register = self.make_usize_constant_instruction(flattened_size.into()); + let rc_register = self.make_usize_constant_instruction(1_usize.into()); + + let var = BrilligVariable::BrilligVector(BrilligVector { + pointer: pointer_to_the_array_in_calldata.address, + size: size_register.address, + rc: rc_register.address, + }); + + current_calldata_pointer += flattened_size; + var + } }) .collect(); // Deflatten arrays for (argument_variable, argument) in argument_variables.iter_mut().zip(arguments) { - if let ( - BrilligVariable::BrilligArray(array), - BrilligParameter::Array(item_type, item_count), - ) = (argument_variable, argument) - { - if BrilligContext::has_nested_arrays(item_type) { + match (argument_variable, argument) { + ( + BrilligVariable::BrilligArray(array), + BrilligParameter::Array(item_type, item_count), + ) => { let deflattened_address = self.deflatten_array(item_type, array.size, array.pointer); self.mov_instruction(array.pointer, deflattened_address); array.size = item_type.len() * item_count; self.deallocate_register(deflattened_address); } + ( + BrilligVariable::BrilligVector(vector), + BrilligParameter::Slice(item_type, item_count), + ) => { + let flattened_size = BrilligContext::flattened_size(argument); + + let deflattened_address = + self.deflatten_array(item_type, flattened_size, vector.pointer); + self.mov_instruction(vector.pointer, deflattened_address); + self.usize_const_instruction( + vector.size, + (item_type.len() * item_count).into(), + ); + + self.deallocate_register(deflattened_address); + } + _ => {} } } } @@ -112,10 +144,10 @@ impl BrilligContext { fn flat_bit_sizes(param: &BrilligParameter) -> Box + '_> { match param { BrilligParameter::SingleAddr(bit_size) => Box::new(std::iter::once(*bit_size)), - BrilligParameter::Array(item_types, item_count) => Box::new( + BrilligParameter::Array(item_types, item_count) + | BrilligParameter::Slice(item_types, item_count) => Box::new( (0..*item_count).flat_map(move |_| item_types.iter().flat_map(flat_bit_sizes)), ), - BrilligParameter::Slice(..) => unimplemented!("Unsupported slices as parameter"), } } @@ -134,13 +166,11 @@ impl BrilligContext { fn flattened_size(param: &BrilligParameter) -> usize { match param { BrilligParameter::SingleAddr(_) => 1, - BrilligParameter::Array(item_types, item_count) => { + BrilligParameter::Array(item_types, item_count) + | BrilligParameter::Slice(item_types, item_count) => { let item_size: usize = item_types.iter().map(BrilligContext::flattened_size).sum(); item_count * item_size } - BrilligParameter::Slice(_) => { - unreachable!("ICE: Slices cannot be passed as entry point arguments") - } } } @@ -457,8 +487,8 @@ mod tests { use acvm::FieldElement; use crate::brillig::brillig_ir::{ - artifact::BrilligParameter, brillig_variable::BrilligArray, + entry_point::BrilligParameter, tests::{create_and_run_vm, create_context, create_entry_point_bytecode}, }; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index f05a0932f23..fac7a7c0829 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -38,20 +38,22 @@ pub mod ssa_gen; /// convert the final SSA into ACIR and return it. pub(crate) fn optimize_into_acir( program: Program, - print_ssa_passes: bool, + print_passes: bool, print_brillig_trace: bool, force_brillig_output: bool, + print_timings: bool, ) -> Result, RuntimeError> { let abi_distinctness = program.return_distinctness; let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); let ssa_gen_span_guard = ssa_gen_span.enter(); - let ssa = SsaBuilder::new(program, print_ssa_passes, force_brillig_output)? + let ssa = SsaBuilder::new(program, print_passes, force_brillig_output, print_timings)? .run_pass(Ssa::defunctionalize, "After Defunctionalization:") .run_pass(Ssa::remove_paired_rc, "After Removing Paired rc_inc & rc_decs:") .run_pass(Ssa::inline_functions, "After Inlining:") // Run mem2reg with the CFG separated into blocks .run_pass(Ssa::mem2reg, "After Mem2Reg:") + .run_pass(Ssa::as_slice_optimization, "After `as_slice` optimization") .try_run_pass(Ssa::evaluate_assert_constant, "After Assert Constant:")? .try_run_pass(Ssa::unroll_loops, "After Unrolling:")? .run_pass(Ssa::simplify_cfg, "After Simplifying:") @@ -60,17 +62,53 @@ pub(crate) fn optimize_into_acir( // Run mem2reg once more with the flattened CFG to catch any remaining loads/stores .run_pass(Ssa::mem2reg, "After Mem2Reg:") .run_pass(Ssa::fold_constants, "After Constant Folding:") + .run_pass(Ssa::remove_enable_side_effects, "After EnableSideEffects removal:") .run_pass(Ssa::fold_constants_using_constraints, "After Constraint Folding:") .run_pass(Ssa::dead_instruction_elimination, "After Dead Instruction Elimination:") + .run_pass(Ssa::array_set_optimization, "After Array Set Optimizations:") .finish(); - let brillig = ssa.to_brillig(print_brillig_trace); + let brillig = time("SSA to Brillig", print_timings, || ssa.to_brillig(print_brillig_trace)); drop(ssa_gen_span_guard); - let last_array_uses = ssa.find_last_array_uses(); + time("SSA to ACIR", print_timings, || ssa.into_acir(&brillig, abi_distinctness)) +} + +// Helper to time SSA passes +fn time(name: &str, print_timings: bool, f: impl FnOnce() -> T) -> T { + let start_time = chrono::Utc::now().time(); + let result = f(); + + if print_timings { + let end_time = chrono::Utc::now().time(); + println!("{name}: {} ms", (end_time - start_time).num_milliseconds()); + } + + result +} - ssa.into_acir(&brillig, abi_distinctness, &last_array_uses) +#[derive(Default)] +pub struct SsaProgramArtifact { + pub program: AcirProgram, + pub debug: Vec, + pub warnings: Vec, + pub main_input_witnesses: Vec, + pub main_return_witnesses: Vec, + pub names: Vec, +} + +impl SsaProgramArtifact { + fn add_circuit(&mut self, mut circuit_artifact: SsaCircuitArtifact, is_main: bool) { + self.program.functions.push(circuit_artifact.circuit); + self.debug.push(circuit_artifact.debug_info); + self.warnings.append(&mut circuit_artifact.warnings); + if is_main { + self.main_input_witnesses = circuit_artifact.input_witnesses; + self.main_return_witnesses = circuit_artifact.return_witnesses; + } + self.names.push(circuit_artifact.name); + } } /// Compiles the [`Program`] into [`ACIR``][acvm::acir::circuit::Program]. @@ -83,8 +121,8 @@ pub fn create_program( enable_ssa_logging: bool, enable_brillig_logging: bool, force_brillig_output: bool, -) -> Result<(AcirProgram, Vec, Vec, Vec, Vec), RuntimeError> -{ + print_codegen_timings: bool, +) -> Result { let debug_variables = program.debug_variables.clone(); let debug_types = program.debug_types.clone(); let debug_functions = program.debug_functions.clone(); @@ -97,6 +135,7 @@ pub fn create_program( enable_ssa_logging, enable_brillig_logging, force_brillig_output, + print_codegen_timings, )?; assert_eq!( generated_acirs.len(), @@ -104,38 +143,33 @@ pub fn create_program( "The generated ACIRs should match the supplied function signatures" ); - let mut functions = vec![]; - let mut debug_infos = vec![]; - let mut warning_infos = vec![]; - let mut main_input_witnesses = Vec::new(); - let mut main_return_witnesses = Vec::new(); + let mut program_artifact = SsaProgramArtifact::default(); // For setting up the ABI we need separately specify main's input and return witnesses let mut is_main = true; for (acir, func_sig) in generated_acirs.into_iter().zip(func_sigs) { - let (circuit, debug_info, warnings, input_witnesses, return_witnesses) = - convert_generated_acir_into_circuit( - acir, - func_sig, - recursive, - // TODO: get rid of these clones - debug_variables.clone(), - debug_functions.clone(), - debug_types.clone(), - ); - functions.push(circuit); - debug_infos.push(debug_info); - warning_infos.extend(warnings); - if is_main { - // main_input_witness = circuit.re - main_input_witnesses = input_witnesses; - main_return_witnesses = return_witnesses; - } + let circuit_artifact = convert_generated_acir_into_circuit( + acir, + func_sig, + recursive, + // TODO: get rid of these clones + debug_variables.clone(), + debug_functions.clone(), + debug_types.clone(), + ); + program_artifact.add_circuit(circuit_artifact, is_main); is_main = false; } - let program = AcirProgram { functions }; + Ok(program_artifact) +} - Ok((program, debug_infos, warning_infos, main_input_witnesses, main_return_witnesses)) +pub struct SsaCircuitArtifact { + name: String, + circuit: Circuit, + debug_info: DebugInfo, + warnings: Vec, + input_witnesses: Vec, + return_witnesses: Vec, } fn convert_generated_acir_into_circuit( @@ -145,7 +179,7 @@ fn convert_generated_acir_into_circuit( debug_variables: DebugVariables, debug_functions: DebugFunctions, debug_types: DebugTypes, -) -> (Circuit, DebugInfo, Vec, Vec, Vec) { +) -> SsaCircuitArtifact { let opcodes = generated_acir.take_opcodes(); let current_witness_index = generated_acir.current_witness_index().0; let GeneratedAcir { @@ -154,11 +188,10 @@ fn convert_generated_acir_into_circuit( input_witnesses, assert_messages, warnings, + name, .. } = generated_acir; - let locations = locations.clone(); - let (public_parameter_witnesses, private_parameters) = split_public_and_private_inputs(&func_sig, &input_witnesses); @@ -172,7 +205,7 @@ fn convert_generated_acir_into_circuit( private_parameters, public_parameters, return_values, - assert_messages: assert_messages.clone().into_iter().collect(), + assert_messages: assert_messages.into_iter().collect(), recursive, }; @@ -188,7 +221,14 @@ fn convert_generated_acir_into_circuit( let (optimized_circuit, transformation_map) = acvm::compiler::optimize(circuit); debug_info.update_acir(transformation_map); - (optimized_circuit, debug_info, warnings, input_witnesses, return_witnesses) + SsaCircuitArtifact { + name, + circuit: optimized_circuit, + debug_info, + warnings, + input_witnesses, + return_witnesses, + } } // Takes each function argument and partitions the circuit's inputs witnesses according to its visibility. @@ -229,6 +269,7 @@ fn split_public_and_private_inputs( struct SsaBuilder { ssa: Ssa, print_ssa_passes: bool, + print_codegen_timings: bool, } impl SsaBuilder { @@ -236,9 +277,10 @@ impl SsaBuilder { program: Program, print_ssa_passes: bool, force_brillig_runtime: bool, + print_codegen_timings: bool, ) -> Result { let ssa = ssa_gen::generate_ssa(program, force_brillig_runtime)?; - Ok(SsaBuilder { print_ssa_passes, ssa }.print("Initial SSA:")) + Ok(SsaBuilder { print_ssa_passes, print_codegen_timings, ssa }.print("Initial SSA:")) } fn finish(self) -> Ssa { @@ -247,7 +289,7 @@ impl SsaBuilder { /// Runs the given SSA pass and prints the SSA afterward if `print_ssa_passes` is true. fn run_pass(mut self, pass: fn(Ssa) -> Ssa, msg: &str) -> Self { - self.ssa = pass(self.ssa); + self.ssa = time(msg, self.print_codegen_timings, || pass(self.ssa)); self.print(msg) } @@ -257,7 +299,7 @@ impl SsaBuilder { pass: fn(Ssa) -> Result, msg: &str, ) -> Result { - self.ssa = pass(self.ssa)?; + self.ssa = time(msg, self.print_codegen_timings, || pass(self.ssa))?; Ok(self.print(msg)) } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index 1d05e998b13..b43110b2f5b 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -25,7 +25,7 @@ use iter_extended::vecmap; use num_bigint::BigUint; #[derive(Debug, Default)] -/// The output of the Acir-gen pass +/// The output of the Acir-gen pass, which should only be produced for entry point Acir functions pub(crate) struct GeneratedAcir { /// The next witness index that may be declared. /// If witness index is `None` then we have not yet created a witness @@ -58,6 +58,10 @@ pub(crate) struct GeneratedAcir { pub(crate) assert_messages: BTreeMap, pub(crate) warnings: Vec, + + /// Name for the corresponding entry point represented by this Acir-gen output. + /// Only used for debugging and benchmarking purposes + pub(crate) name: String, } impl GeneratedAcir { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 96f959612af..9e597dc8e9a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -21,7 +21,7 @@ use super::{ }, ssa_gen::Ssa, }; -use crate::brillig::brillig_ir::artifact::GeneratedBrillig; +use crate::brillig::brillig_ir::artifact::{BrilligParameter, GeneratedBrillig}; use crate::brillig::brillig_ir::BrilligContext; use crate::brillig::{brillig_gen::brillig_fn::FunctionContext as BrilligFunctionContext, Brillig}; use crate::errors::{InternalError, InternalWarning, RuntimeError, SsaReport}; @@ -181,15 +181,15 @@ impl Ssa { self, brillig: &Brillig, abi_distinctness: Distinctness, - last_array_uses: &HashMap, ) -> Result, RuntimeError> { let mut acirs = Vec::new(); // TODO: can we parallelise this? for function in self.functions.values() { let context = Context::new(); - if let Some(generated_acir) = - context.convert_ssa_function(&self, function, brillig, last_array_uses)? + if let Some(mut generated_acir) = + context.convert_ssa_function(&self, function, brillig)? { + generated_acir.name = function.name().to_owned(); acirs.push(generated_acir); } } @@ -245,7 +245,6 @@ impl Context { ssa: &Ssa, function: &Function, brillig: &Brillig, - last_array_uses: &HashMap, ) -> Result, RuntimeError> { match function.runtime() { RuntimeType::Acir(inline_type) => { @@ -257,8 +256,8 @@ impl Context { } } } - // We only want to convert entry point functions. This being `main` and those marked with `#[fold]` - Ok(Some(self.convert_acir_main(function, ssa, brillig, last_array_uses)?)) + // We only want to convert entry point functions. This being `main` and those marked with `InlineType::Fold` + Ok(Some(self.convert_acir_main(function, ssa, brillig)?)) } RuntimeType::Brillig => { if function.id() == ssa.main_id { @@ -275,7 +274,6 @@ impl Context { main_func: &Function, ssa: &Ssa, brillig: &Brillig, - last_array_uses: &HashMap, ) -> Result { let dfg = &main_func.dfg; let entry_block = &dfg[main_func.entry_block()]; @@ -284,13 +282,7 @@ impl Context { self.data_bus = dfg.data_bus.to_owned(); let mut warnings = Vec::new(); for instruction_id in entry_block.instructions() { - warnings.extend(self.convert_ssa_instruction( - *instruction_id, - dfg, - ssa, - brillig, - last_array_uses, - )?); + warnings.extend(self.convert_ssa_instruction(*instruction_id, dfg, ssa, brillig)?); } warnings.extend(self.convert_ssa_return(entry_block.unwrap_terminator(), dfg)?); @@ -308,12 +300,14 @@ impl Context { let typ = dfg.type_of_value(*param_id); self.create_value_from_type(&typ, &mut |this, _| Ok(this.acir_context.add_variable())) })?; + let arguments = self.gen_brillig_parameters(dfg[main_func.entry_block()].parameters(), dfg); + let witness_inputs = self.acir_context.extract_witness(&inputs); let outputs: Vec = vecmap(main_func.returns(), |result_id| dfg.type_of_value(*result_id).into()); - let code = self.gen_brillig_for(main_func, brillig)?; + let code = self.gen_brillig_for(main_func, arguments, brillig)?; // We specifically do not attempt execution of the brillig code being generated as this can result in it being // replaced with constraints on witnesses to the program outputs. @@ -463,7 +457,6 @@ impl Context { dfg: &DataFlowGraph, ssa: &Ssa, brillig: &Brillig, - last_array_uses: &HashMap, ) -> Result, RuntimeError> { let instruction = &dfg[instruction_id]; self.acir_context.set_call_stack(dfg.get_call_stack(instruction_id)); @@ -523,7 +516,7 @@ impl Context { self.current_side_effects_enabled_var = acir_var; } Instruction::ArrayGet { .. } | Instruction::ArraySet { .. } => { - self.handle_array_operation(instruction_id, dfg, last_array_uses)?; + self.handle_array_operation(instruction_id, dfg)?; } Instruction::Allocate => { unreachable!("Expected all allocate instructions to be removed before acir_gen") @@ -606,8 +599,9 @@ impl Context { } let inputs = vecmap(arguments, |arg| self.convert_value(*arg, dfg)); + let arguments = self.gen_brillig_parameters(arguments, dfg); - let code = self.gen_brillig_for(func, brillig)?; + let code = self.gen_brillig_for(func, arguments, brillig)?; let outputs: Vec = vecmap(result_ids, |result_id| { dfg.type_of_value(*result_id).into() @@ -685,14 +679,49 @@ impl Context { Ok(()) } + fn gen_brillig_parameters( + &self, + values: &[ValueId], + dfg: &DataFlowGraph, + ) -> Vec { + values + .iter() + .map(|&value_id| { + let typ = dfg.type_of_value(value_id); + if let Type::Slice(item_types) = typ { + let len = match self + .ssa_values + .get(&value_id) + .expect("ICE: Unknown slice input to brillig") + { + AcirValue::DynamicArray(AcirDynamicArray { len, .. }) => *len, + AcirValue::Array(array) => array.len(), + _ => unreachable!("ICE: Slice value is not an array"), + }; + + BrilligParameter::Slice( + item_types + .iter() + .map(BrilligFunctionContext::ssa_type_to_parameter) + .collect(), + len / item_types.len(), + ) + } else { + BrilligFunctionContext::ssa_type_to_parameter(&typ) + } + }) + .collect() + } + fn gen_brillig_for( &self, func: &Function, + arguments: Vec, brillig: &Brillig, ) -> Result { // Create the entry point artifact let mut entry_point = BrilligContext::new_entry_point_artifact( - BrilligFunctionContext::parameters(func), + arguments, BrilligFunctionContext::return_values(func), BrilligFunctionContext::function_id_to_function_label(func.id()), ); @@ -721,12 +750,16 @@ impl Context { &mut self, instruction: InstructionId, dfg: &DataFlowGraph, - last_array_uses: &HashMap, ) -> Result<(), RuntimeError> { + let mut mutable_array_set = false; + // Pass the instruction between array methods rather than the internal fields themselves let (array, index, store_value) = match dfg[instruction] { Instruction::ArrayGet { array, index } => (array, index, None), - Instruction::ArraySet { array, index, value, .. } => (array, index, Some(value)), + Instruction::ArraySet { array, index, value, mutable } => { + mutable_array_set = mutable; + (array, index, Some(value)) + } _ => { return Err(InternalError::Unexpected { expected: "Instruction should be an ArrayGet or ArraySet".to_owned(), @@ -744,11 +777,8 @@ impl Context { let (new_index, new_value) = self.convert_array_operation_inputs(array, dfg, index, store_value)?; - let resolved_array = dfg.resolve(array); - let map_array = last_array_uses.get(&resolved_array) == Some(&instruction); - if let Some(new_value) = new_value { - self.array_set(instruction, new_index, new_value, dfg, map_array)?; + self.array_set(instruction, new_index, new_value, dfg, mutable_array_set)?; } else { self.array_get(instruction, array, new_index, dfg)?; } @@ -1028,16 +1058,18 @@ impl Context { } } - /// Copy the array and generates a write opcode on the new array - /// - /// Note: Copying the array is inefficient and is not the way we want to do it in the end. + /// If `mutate_array` is: + /// - true: Mutate the array directly + /// - false: Copy the array and generates a write opcode on the new array. This is + /// generally very inefficient and should be avoided if possible. Currently + /// this is controlled by SSA's array set optimization pass. fn array_set( &mut self, instruction: InstructionId, mut var_index: AcirVar, store_value: AcirValue, dfg: &DataFlowGraph, - map_array: bool, + mutate_array: bool, ) -> Result<(), RuntimeError> { // Pass the instruction between array methods rather than the internal fields themselves let array = match dfg[instruction] { @@ -1075,7 +1107,7 @@ impl Context { .first() .expect("Array set does not have one result"); let result_block_id; - if map_array { + if mutate_array { self.memory_blocks.insert(*result_id, block_id); result_block_id = block_id; } else { @@ -2401,14 +2433,13 @@ mod test { ssa::{ function_builder::FunctionBuilder, ir::{ - function::{FunctionId, InlineType, RuntimeType}, + function::{FunctionId, InlineType}, instruction::BinaryOp, map::Id, types::Type, }, }, }; - use fxhash::FxHashMap as HashMap; fn build_basic_foo_with_return(builder: &mut FunctionBuilder, foo_id: FunctionId) { // acir(fold) fn foo f1 { @@ -2461,11 +2492,7 @@ mod test { let ssa = builder.finish(); let acir_functions = ssa - .into_acir( - &Brillig::default(), - noirc_frontend::Distinctness::Distinct, - &HashMap::default(), - ) + .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // Expected result: // main f0 @@ -2516,16 +2543,13 @@ mod test { check_call_opcode(&main_opcodes[0], 1, vec![Witness(0), Witness(1)], vec![Witness(2)]); check_call_opcode(&main_opcodes[1], 1, vec![Witness(0), Witness(1)], vec![Witness(3)]); - match &main_opcodes[2] { - Opcode::AssertZero(expr) => { - assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); - assert_eq!(expr.linear_combinations[0].1, Witness(2)); + if let Opcode::AssertZero(expr) = &main_opcodes[2] { + assert_eq!(expr.linear_combinations[0].0, FieldElement::from(1u128)); + assert_eq!(expr.linear_combinations[0].1, Witness(2)); - assert_eq!(expr.linear_combinations[1].0, FieldElement::from(-1i128)); - assert_eq!(expr.linear_combinations[1].1, Witness(3)); - assert_eq!(expr.q_c, FieldElement::from(0u128)); - } - _ => {} + assert_eq!(expr.linear_combinations[1].0, FieldElement::from(-1i128)); + assert_eq!(expr.linear_combinations[1].1, Witness(3)); + assert_eq!(expr.q_c, FieldElement::from(0u128)); } } @@ -2564,11 +2588,7 @@ mod test { let ssa = builder.finish(); let acir_functions = ssa - .into_acir( - &Brillig::default(), - noirc_frontend::Distinctness::Distinct, - &HashMap::default(), - ) + .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` // opcodes will be different. The changes can discerned from the checks below. @@ -2659,11 +2679,7 @@ mod test { let ssa = builder.finish(); let acir_functions = ssa - .into_acir( - &Brillig::default(), - noirc_frontend::Distinctness::Distinct, - &HashMap::default(), - ) + .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); assert_eq!(acir_functions.len(), 3, "Should have three ACIR functions"); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index e0e60b737ad..d3e5e506111 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -310,7 +310,8 @@ impl FunctionBuilder { index: ValueId, value: ValueId, ) -> ValueId { - self.insert_instruction(Instruction::ArraySet { array, index, value }, None).first() + self.insert_instruction(Instruction::ArraySet { array, index, value, mutable: false }, None) + .first() } /// Insert an instruction to increment an array's reference count. This only has an effect @@ -500,7 +501,6 @@ mod tests { use acvm::FieldElement; use crate::ssa::ir::{ - function::{InlineType, RuntimeType}, instruction::{Endian, Intrinsic}, map::Id, types::Type, 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 870b5e602f1..6b950c327cf 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 @@ -378,6 +378,30 @@ impl DataFlowGraph { value_id } + /// Replaces an instruction result with a fresh id. + pub(crate) fn replace_result( + &mut self, + instruction_id: InstructionId, + prev_value_id: ValueId, + ) -> ValueId { + let typ = self.type_of_value(prev_value_id); + let results = self.results.get_mut(&instruction_id).unwrap(); + let res_position = results + .iter() + .position(|&id| id == prev_value_id) + .expect("Result id not found while replacing"); + + let value_id = self.values.insert(Value::Instruction { + typ, + position: res_position, + instruction: instruction_id, + }); + + // Replace the value in list of results for this instruction + results[res_position] = value_id; + value_id + } + /// Returns the number of instructions /// inserted into functions. pub(crate) fn num_instructions(&self) -> usize { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dom.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dom.rs index bd1481a7474..15fa3bad38d 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dom.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dom.rs @@ -249,13 +249,8 @@ mod tests { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - basic_block::BasicBlockId, - dfg::CallStack, - dom::DominatorTree, - function::{Function, InlineType, RuntimeType}, - instruction::TerminatorInstruction, - map::Id, - types::Type, + basic_block::BasicBlockId, dfg::CallStack, dom::DominatorTree, function::Function, + instruction::TerminatorInstruction, map::Id, types::Type, }, }; 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 dd190c112f3..2b23cc1c1e8 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 @@ -189,8 +189,9 @@ pub(crate) enum Instruction { ArrayGet { array: ValueId, index: ValueId }, /// Creates a new array with the new value at the given index. All other elements are identical - /// to those in the given array. This will not modify the original array. - ArraySet { array: ValueId, index: ValueId, value: ValueId }, + /// to those in the given array. This will not modify the original array unless `mutable` is + /// set. This flag is off by default and only enabled when optimizations determine it is safe. + ArraySet { array: ValueId, index: ValueId, value: ValueId, mutable: bool }, /// An instruction to increment the reference count of a value. /// @@ -363,9 +364,12 @@ impl Instruction { Instruction::ArrayGet { array, index } => { Instruction::ArrayGet { array: f(*array), index: f(*index) } } - Instruction::ArraySet { array, index, value } => { - Instruction::ArraySet { array: f(*array), index: f(*index), value: f(*value) } - } + Instruction::ArraySet { array, index, value, mutable } => Instruction::ArraySet { + array: f(*array), + index: f(*index), + value: f(*value), + mutable: *mutable, + }, Instruction::IncrementRc { value } => Instruction::IncrementRc { value: f(*value) }, Instruction::DecrementRc { value } => Instruction::DecrementRc { value: f(*value) }, Instruction::RangeCheck { value, max_bit_size, assert_message } => { @@ -416,7 +420,7 @@ impl Instruction { f(*array); f(*index); } - Instruction::ArraySet { array, index, value } => { + Instruction::ArraySet { array, index, value, mutable: _ } => { f(*array); f(*index); f(*value); @@ -573,12 +577,12 @@ impl Instruction { Instruction::IncrementRc { .. } => None, Instruction::DecrementRc { .. } => None, Instruction::RangeCheck { value, max_bit_size, .. } => { - if let Some(numeric_constant) = dfg.get_numeric_constant(*value) { - if numeric_constant.num_bits() < *max_bit_size { - return Remove; - } + let max_potential_bits = dfg.get_value_max_num_bits(*value); + if max_potential_bits < *max_bit_size { + Remove + } else { + None } - None } } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs index 36f3ae8620b..9099268ace9 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs @@ -130,6 +130,12 @@ impl Binary { let zero = dfg.make_constant(FieldElement::zero(), operand_type); return SimplifyResult::SimplifiedTo(zero); } + if dfg.resolve(self.lhs) == dfg.resolve(self.rhs) + && dfg.get_value_max_num_bits(self.lhs) == 1 + { + // Squaring a boolean value is a noop. + return SimplifyResult::SimplifiedTo(self.lhs); + } } BinaryOp::Div => { if rhs_is_one { @@ -164,6 +170,7 @@ impl Binary { let one = dfg.make_constant(FieldElement::one(), Type::bool()); return SimplifyResult::SimplifiedTo(one); } + if operand_type == Type::bool() { // Simplify forms of `(boolean == true)` into `boolean` if lhs_is_one { 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 5b268de239d..1187ea8cb07 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 @@ -240,11 +240,16 @@ pub(super) fn simplify_call( let max_bit_size = dfg.get_numeric_constant(arguments[1]); if let Some(max_bit_size) = max_bit_size { let max_bit_size = max_bit_size.to_u128() as u32; - SimplifyResult::SimplifiedToInstruction(Instruction::RangeCheck { - value, - max_bit_size, - assert_message: Some("call to assert_max_bit_size".to_owned()), - }) + let max_potential_bits = dfg.get_value_max_num_bits(value); + if max_potential_bits < max_bit_size { + SimplifyResult::Remove + } else { + SimplifyResult::SimplifiedToInstruction(Instruction::RangeCheck { + value, + max_bit_size, + assert_message: Some("call to assert_max_bit_size".to_owned()), + }) + } } else { SimplifyResult::None } @@ -334,8 +339,13 @@ fn simplify_slice_push_back( let element_size = element_type.element_size(); let new_slice = dfg.make_array(slice, element_type); - let set_last_slice_value_instr = - Instruction::ArraySet { array: new_slice, index: arguments[0], value: arguments[2] }; + let set_last_slice_value_instr = Instruction::ArraySet { + array: new_slice, + index: arguments[0], + value: arguments[2], + mutable: false, + }; + let set_last_slice_value = dfg .insert_instruction_and_results(set_last_slice_value_instr, block, None, call_stack) .first(); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/post_order.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/post_order.rs index 5d743e953a5..d95ec451779 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/post_order.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/post_order.rs @@ -73,12 +73,7 @@ impl PostOrder { mod tests { use crate::ssa::{ function_builder::FunctionBuilder, - ir::{ - function::{Function, InlineType, RuntimeType}, - map::Id, - post_order::PostOrder, - types::Type, - }, + ir::{function::Function, map::Id, post_order::PostOrder, types::Type}, }; #[test] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 6ef618fba6f..fc13ab7307a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -176,14 +176,12 @@ fn display_instruction_inner( Instruction::ArrayGet { array, index } => { writeln!(f, "array_get {}, index {}", show(*array), show(*index)) } - Instruction::ArraySet { array, index, value } => { - writeln!( - f, - "array_set {}, index {}, value {}", - show(*array), - show(*index), - show(*value) - ) + Instruction::ArraySet { array, index, value, mutable } => { + let array = show(*array); + let index = show(*index); + let value = show(*value); + let mutable = if *mutable { " mut" } else { "" }; + writeln!(f, "array_set{mutable} {array}, index {index}, value {value}",) } Instruction::IncrementRc { value } => { writeln!(f, "inc_rc {}", show(*value)) diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs new file mode 100644 index 00000000000..cf61d7fd73f --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs @@ -0,0 +1,104 @@ +use crate::ssa::{ + ir::{ + basic_block::BasicBlockId, + dfg::DataFlowGraph, + instruction::{Instruction, InstructionId}, + types::Type::{Array, Slice}, + }, + ssa_gen::Ssa, +}; +use fxhash::{FxHashMap as HashMap, FxHashSet}; + +impl Ssa { + /// Map arrays with the last instruction that uses it + /// For this we simply process all the instructions in execution order + /// and update the map whenever there is a match + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn array_set_optimization(mut self) -> Self { + for func in self.functions.values_mut() { + if !func.runtime().is_entry_point() { + let mut reachable_blocks = func.reachable_blocks(); + assert_eq!(reachable_blocks.len(), 1, "Expected there to be 1 block remaining in Acir function for array_set optimization"); + + let block = reachable_blocks.pop_first().unwrap(); + let instructions_to_update = analyze_last_uses(&func.dfg, block); + make_mutable(&mut func.dfg, block, instructions_to_update); + } + } + self + } +} + +/// Returns the set of ArraySet instructions that can be made mutable +/// because their input value is unused elsewhere afterward. +fn analyze_last_uses(dfg: &DataFlowGraph, block_id: BasicBlockId) -> FxHashSet { + let block = &dfg[block_id]; + let mut array_to_last_use = HashMap::default(); + let mut instructions_that_can_be_made_mutable = FxHashSet::default(); + + for instruction_id in block.instructions() { + match &dfg[*instruction_id] { + Instruction::ArrayGet { array, .. } => { + let array = dfg.resolve(*array); + + if let Some(existing) = array_to_last_use.insert(array, *instruction_id) { + instructions_that_can_be_made_mutable.remove(&existing); + } + } + Instruction::ArraySet { array, .. } => { + let array = dfg.resolve(*array); + + if let Some(existing) = array_to_last_use.insert(array, *instruction_id) { + instructions_that_can_be_made_mutable.remove(&existing); + } + instructions_that_can_be_made_mutable.insert(*instruction_id); + } + Instruction::Call { arguments, .. } => { + for argument in arguments { + if matches!(dfg.type_of_value(*argument), Array { .. } | Slice { .. }) { + let argument = dfg.resolve(*argument); + + if let Some(existing) = array_to_last_use.insert(argument, *instruction_id) + { + instructions_that_can_be_made_mutable.remove(&existing); + } + } + } + } + _ => (), + } + } + + instructions_that_can_be_made_mutable +} + +/// Make each ArraySet instruction in `instructions_to_update` mutable. +fn make_mutable( + dfg: &mut DataFlowGraph, + block_id: BasicBlockId, + instructions_to_update: FxHashSet, +) { + if instructions_to_update.is_empty() { + return; + } + + // Take the instructions temporarily so we can mutate the DFG while we iterate through them + let block = &mut dfg[block_id]; + let instructions = block.take_instructions(); + + for instruction in &instructions { + if instructions_to_update.contains(instruction) { + let instruction = &mut dfg[*instruction]; + + if let Instruction::ArraySet { mutable, .. } = instruction { + *mutable = true; + } else { + unreachable!( + "Non-ArraySet instruction in instructions_to_update!\n{instruction:?}" + ); + } + } + } + + *dfg[block_id].instructions_mut() = instructions; +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_use.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_use.rs deleted file mode 100644 index 0bb8b0112b6..00000000000 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_use.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::ssa::{ - ir::{ - basic_block::BasicBlockId, - dfg::DataFlowGraph, - instruction::{Instruction, InstructionId}, - post_order::PostOrder, - value::{Value, ValueId}, - }, - ssa_gen::Ssa, -}; -use fxhash::FxHashMap as HashMap; - -impl Ssa { - /// Map arrays with the last instruction that uses it - /// For this we simply process all the instructions in execution order - /// and update the map whenever there is a match - #[tracing::instrument(level = "trace", skip(self))] - pub(crate) fn find_last_array_uses(&self) -> HashMap { - let mut array_use = HashMap::default(); - for func in self.functions.values() { - let mut reverse_post_order = PostOrder::with_function(func).into_vec(); - reverse_post_order.reverse(); - for block in reverse_post_order { - last_use(block, &func.dfg, &mut array_use); - } - } - array_use - } -} - -/// Updates the array_def map when an instructions is using an array -fn last_use( - block_id: BasicBlockId, - dfg: &DataFlowGraph, - array_def: &mut HashMap, -) { - let block = &dfg[block_id]; - for instruction_id in block.instructions() { - match &dfg[*instruction_id] { - Instruction::ArrayGet { array, .. } | Instruction::ArraySet { array, .. } => { - let array = dfg.resolve(*array); - array_def.insert(array, *instruction_id); - } - Instruction::Call { arguments, .. } => { - for argument in arguments { - let resolved_arg = dfg.resolve(*argument); - if matches!(dfg[resolved_arg], Value::Array { .. }) { - array_def.insert(resolved_arg, *instruction_id); - } - } - } - _ => { - // Nothing to do - } - } - } -} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs new file mode 100644 index 00000000000..69eab1da0ed --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs @@ -0,0 +1,71 @@ +use crate::ssa::{ + ir::{ + function::Function, + instruction::{Instruction, InstructionId, Intrinsic}, + types::Type, + value::Value, + }, + ssa_gen::Ssa, +}; +use fxhash::FxHashMap as HashMap; + +impl Ssa { + /// A simple SSA pass to find any calls to `Intrinsic::AsSlice` and replacing any references to the length of the + /// resulting slice with the length of the array from which it was generated. + /// + /// This allows the length of a slice generated from an array to be used in locations where a constant value is + /// necessary when the value of the array is unknown. + /// + /// Note that this pass must be placed before loop unrolling to be useful. + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn as_slice_optimization(mut self) -> Self { + for func in self.functions.values_mut() { + let known_slice_lengths = known_slice_lengths(func); + replace_known_slice_lengths(func, known_slice_lengths); + } + self + } +} + +fn known_slice_lengths(func: &Function) -> HashMap { + let mut known_slice_lengths = HashMap::default(); + for block_id in func.reachable_blocks() { + let block = &func.dfg[block_id]; + for instruction_id in block.instructions() { + let (target_func, arguments) = match &func.dfg[*instruction_id] { + Instruction::Call { func, arguments } => (func, arguments), + _ => continue, + }; + + match &func.dfg[*target_func] { + Value::Intrinsic(Intrinsic::AsSlice) => { + let array_typ = func.dfg.type_of_value(arguments[0]); + if let Type::Array(_, length) = array_typ { + known_slice_lengths.insert(*instruction_id, length); + } else { + unreachable!("AsSlice called with non-array {}", array_typ); + } + } + _ => continue, + }; + } + } + known_slice_lengths +} + +fn replace_known_slice_lengths( + func: &mut Function, + known_slice_lengths: HashMap, +) { + known_slice_lengths.into_iter().for_each(|(instruction_id, known_length)| { + let call_returns = func.dfg.instruction_results(instruction_id); + let original_slice_length = call_returns[0]; + + // We won't use the new id for the original unknown length. + // This isn't strictly necessary as a new result will be defined the next time for which the instruction + // is reinserted but this avoids leaving the program in an invalid state. + func.dfg.replace_result(instruction_id, original_slice_length); + let known_length = func.dfg.make_constant(known_length.into(), Type::length_type()); + func.dfg.set_value_from_id(original_slice_length, known_length); + }); +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs index 6d556e86cb5..0409f0e6a49 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/bubble_up_constrains.rs @@ -77,7 +77,6 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::{InlineType, RuntimeType}, instruction::{Binary, BinaryOp, Instruction}, map::Id, types::Type, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index 88948331960..6cac8c91bc3 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -283,7 +283,6 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::{InlineType, RuntimeType}, instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, map::Id, types::Type, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs index df89dcb716d..d1b3e1e83f5 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -169,7 +169,6 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::{InlineType, RuntimeType}, instruction::{BinaryOp, Intrinsic}, map::Id, types::Type, 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 e731a7952a6..07771397ce8 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 @@ -841,7 +841,7 @@ mod test { function_builder::FunctionBuilder, ir::{ dfg::DataFlowGraph, - function::{Function, InlineType, RuntimeType}, + function::Function, instruction::{BinaryOp, Instruction, Intrinsic, TerminatorInstruction}, map::Id, types::Type, 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 adb6d2871e5..ce54bb533f7 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 @@ -114,12 +114,7 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, - ir::{ - cfg::ControlFlowGraph, - function::{InlineType, RuntimeType}, - map::Id, - types::Type, - }, + ir::{cfg::ControlFlowGraph, map::Id, types::Type}, opt::flatten_cfg::branch_analysis::find_branch_ends, }; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs index 6b923a2e42d..0a351148fa3 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/value_merger.rs @@ -234,9 +234,9 @@ impl<'a> ValueMerger<'a> { /// such as with dynamic indexing of non-homogenous slices. fn make_slice_dummy_data(&mut self, typ: &Type) -> ValueId { match typ { - Type::Numeric(_) => { + Type::Numeric(numeric_type) => { let zero = FieldElement::zero(); - self.dfg.make_constant(zero, Type::field()) + self.dfg.make_constant(zero, Type::Numeric(*numeric_type)) } Type::Array(element_types, len) => { let mut array = im::Vector::new(); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs index 7c7698d236d..ead3cac071c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/inlining.rs @@ -11,7 +11,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, dfg::{CallStack, InsertInstructionResult}, - function::{Function, FunctionId, InlineType, RuntimeType}, + function::{Function, FunctionId}, instruction::{Instruction, InstructionId, TerminatorInstruction}, value::{Value, ValueId}, }, @@ -351,14 +351,13 @@ impl<'function> PerFunctionContext<'function> { for id in block.instructions() { match &self.source_function.dfg[*id] { Instruction::Call { func, arguments } => match self.get_function(*func) { - Some(function) => match ssa.functions[&function].runtime() { - RuntimeType::Acir(InlineType::Inline) => { - self.inline_function(ssa, *id, function, arguments); - } - RuntimeType::Acir(InlineType::Fold) | RuntimeType::Brillig => { + Some(function) => { + if ssa.functions[&function].runtime().is_entry_point() { self.push_instruction(*id); + } else { + self.inline_function(ssa, *id, function, arguments); } - }, + } None => self.push_instruction(*id), }, _ => self.push_instruction(*id), @@ -523,7 +522,7 @@ mod test { function_builder::FunctionBuilder, ir::{ basic_block::BasicBlockId, - function::{InlineType, RuntimeType}, + function::InlineType, instruction::{BinaryOp, Intrinsic, TerminatorInstruction}, map::Id, types::Type, 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 f1a38585bd6..7b87142d824 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 @@ -414,7 +414,6 @@ mod tests { ir::{ basic_block::BasicBlockId, dfg::DataFlowGraph, - function::{InlineType, RuntimeType}, instruction::{BinaryOp, Instruction, Intrinsic, TerminatorInstruction}, map::Id, types::Type, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs index 8f98b3fb17f..4452840a28c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -3,7 +3,8 @@ //! Each pass is generally expected to mutate the SSA IR into a gradually //! simpler form until the IR only has a single function remaining with 1 block within it. //! Generally, these passes are also expected to minimize the final amount of instructions. -mod array_use; +mod array_set; +mod as_slice_length; mod assert_constant; mod bubble_up_constrains; mod constant_folding; @@ -14,5 +15,6 @@ mod inlining; mod mem2reg; mod rc; mod remove_bit_shifts; +mod remove_enable_side_effects; mod simplify_cfg; mod unrolling; diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs index 7b5196f2004..1561547e32e 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/rc.rs @@ -166,12 +166,8 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - basic_block::BasicBlockId, - dfg::DataFlowGraph, - function::{InlineType, RuntimeType}, - instruction::Instruction, - map::Id, - types::Type, + basic_block::BasicBlockId, dfg::DataFlowGraph, function::RuntimeType, + instruction::Instruction, map::Id, types::Type, }, }; 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 new file mode 100644 index 00000000000..8535dc2661f --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -0,0 +1,167 @@ +//! The goal of the "remove enable side effects" optimization pass is to delay any [Instruction::EnableSideEffects] +//! instructions such that they cover the minimum number of instructions possible. +//! +//! The pass works as follows: +//! - Insert instructions until an [Instruction::EnableSideEffects] is encountered, save this [InstructionId]. +//! - Continue inserting instructions until either +//! - Another [Instruction::EnableSideEffects] is encountered, if so then drop the previous [InstructionId] in favour +//! of this one. +//! - An [Instruction] with side-effects is encountered, if so then insert the currently saved [Instruction::EnableSideEffects] +//! before the [Instruction]. Continue inserting instructions until the next [Instruction::EnableSideEffects] is encountered. +use std::collections::HashSet; + +use acvm::FieldElement; + +use crate::ssa::{ + ir::{ + basic_block::BasicBlockId, + dfg::DataFlowGraph, + function::Function, + instruction::{BinaryOp, Instruction, Intrinsic}, + value::Value, + }, + ssa_gen::Ssa, +}; + +impl Ssa { + /// See [`remove_enable_side_effects`][self] module for more information. + #[tracing::instrument(level = "trace", skip(self))] + pub(crate) fn remove_enable_side_effects(mut self) -> Ssa { + for function in self.functions.values_mut() { + remove_enable_side_effects(function); + } + self + } +} + +fn remove_enable_side_effects(function: &mut Function) { + let mut context = Context::default(); + context.block_queue.push(function.entry_block()); + + while let Some(block) = context.block_queue.pop() { + if context.visited_blocks.contains(&block) { + continue; + } + + context.visited_blocks.insert(block); + context.remove_enable_side_effects_in_block(function, block); + } +} + +#[derive(Default)] +struct Context { + visited_blocks: HashSet, + block_queue: Vec, +} + +impl Context { + fn remove_enable_side_effects_in_block( + &mut self, + function: &mut Function, + block: BasicBlockId, + ) { + let instructions = function.dfg[block].take_instructions(); + + let mut last_side_effects_enabled_instruction = None; + + let mut new_instructions = Vec::with_capacity(instructions.len()); + for instruction_id in instructions { + let instruction = &function.dfg[instruction_id]; + + // If we run into another `Instruction::EnableSideEffects` before encountering any + // instructions with side effects then we can drop the instruction we're holding and + // continue with the new `Instruction::EnableSideEffects`. + if let Instruction::EnableSideEffects { condition } = instruction { + // If we're seeing an `enable_side_effects u1 1` then we want to insert it immediately. + // This is because we want to maximize the effect it will have. + if function + .dfg + .get_numeric_constant(*condition) + .map_or(false, |condition| condition.is_one()) + { + new_instructions.push(instruction_id); + last_side_effects_enabled_instruction = None; + continue; + } + + last_side_effects_enabled_instruction = Some(instruction_id); + continue; + } + + // If we hit an instruction which is affected by the side effects var then we must insert the + // `Instruction::EnableSideEffects` before we insert this new instruction. + if Self::responds_to_side_effects_var(&function.dfg, instruction) { + if let Some(enable_side_effects_instruction_id) = + last_side_effects_enabled_instruction.take() + { + new_instructions.push(enable_side_effects_instruction_id); + } + } + new_instructions.push(instruction_id); + } + + *function.dfg[block].instructions_mut() = new_instructions; + + self.block_queue.extend(function.dfg[block].successors()); + } + + fn responds_to_side_effects_var(dfg: &DataFlowGraph, instruction: &Instruction) -> bool { + use Instruction::*; + match instruction { + Binary(binary) => { + if matches!(binary.operator, BinaryOp::Div | BinaryOp::Mod) { + if let Some(rhs) = dfg.get_numeric_constant(binary.rhs) { + rhs == FieldElement::zero() + } else { + true + } + } else { + false + } + } + + Cast(_, _) + | Not(_) + | Truncate { .. } + | Constrain(..) + | RangeCheck { .. } + | IncrementRc { .. } + | DecrementRc { .. } => false, + + EnableSideEffects { .. } + | ArrayGet { .. } + | ArraySet { .. } + | Allocate + | Store { .. } + | Load { .. } => true, + + // Some `Intrinsic`s have side effects so we must check what kind of `Call` this is. + Call { func, .. } => match dfg[*func] { + Value::Intrinsic(intrinsic) => match intrinsic { + Intrinsic::SlicePushBack + | Intrinsic::SlicePushFront + | Intrinsic::SlicePopBack + | Intrinsic::SlicePopFront + | Intrinsic::SliceInsert + | Intrinsic::SliceRemove => true, + + Intrinsic::ArrayLen + | Intrinsic::AssertConstant + | Intrinsic::ApplyRangeConstraint + | Intrinsic::StrAsBytes + | Intrinsic::ToBits(_) + | Intrinsic::ToRadix(_) + | Intrinsic::BlackBox(_) + | Intrinsic::FromField + | Intrinsic::AsField + | Intrinsic::AsSlice => false, + }, + + // We must assume that functions contain a side effect as we cannot inspect more deeply. + Value::Function(_) => true, + + _ => false, + }, + } + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs index b9675d99c90..f524b10f1f2 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs @@ -154,7 +154,6 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, ir::{ - function::{InlineType, RuntimeType}, instruction::{BinaryOp, TerminatorInstruction}, map::Id, types::Type, 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 92ec1e8f1bb..8110e3469f1 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 @@ -464,12 +464,7 @@ impl<'f> LoopIteration<'f> { mod tests { use crate::ssa::{ function_builder::FunctionBuilder, - ir::{ - function::{InlineType, RuntimeType}, - instruction::BinaryOp, - map::Id, - types::Type, - }, + ir::{instruction::BinaryOp, map::Id, types::Type}, }; #[test] diff --git a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml index a3a8d460572..03b92e15032 100644 --- a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml @@ -26,6 +26,7 @@ tracing.workspace = true petgraph = "0.6" [dev-dependencies] +base64.workspace = true strum = "0.24" strum_macros = "0.24" tempfile.workspace = true 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 b14ead8ad42..dea9fc0f3d3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -120,7 +120,7 @@ impl StatementKind { // Desugar `a = b` to `a = a b`. This relies on the evaluation of `a` having no side effects, // which is currently enforced by the restricted syntax of LValues. if operator != Token::Assign { - let lvalue_expr = lvalue.as_expression(span); + let lvalue_expr = lvalue.as_expression(); let error_msg = "Token passed to Statement::assign is not a binary operator"; let infix = crate::InfixExpression { @@ -425,9 +425,9 @@ pub struct AssignStatement { #[derive(Debug, PartialEq, Eq, Clone)] pub enum LValue { Ident(Ident), - MemberAccess { object: Box, field_name: Ident }, - Index { array: Box, index: Expression }, - Dereference(Box), + MemberAccess { object: Box, field_name: Ident, span: Span }, + Index { array: Box, index: Expression, span: Span }, + Dereference(Box, Span), } #[derive(Debug, PartialEq, Eq, Clone)] @@ -484,28 +484,40 @@ impl Recoverable for Pattern { } impl LValue { - fn as_expression(&self, span: Span) -> Expression { + fn as_expression(&self) -> Expression { let kind = match self { LValue::Ident(ident) => ExpressionKind::Variable(Path::from_ident(ident.clone())), - LValue::MemberAccess { object, field_name } => { + LValue::MemberAccess { object, field_name, span: _ } => { ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { - lhs: object.as_expression(span), + lhs: object.as_expression(), rhs: field_name.clone(), })) } - LValue::Index { array, index } => ExpressionKind::Index(Box::new(IndexExpression { - collection: array.as_expression(span), - index: index.clone(), - })), - LValue::Dereference(lvalue) => { + LValue::Index { array, index, span: _ } => { + ExpressionKind::Index(Box::new(IndexExpression { + collection: array.as_expression(), + index: index.clone(), + })) + } + LValue::Dereference(lvalue, _span) => { ExpressionKind::Prefix(Box::new(crate::PrefixExpression { operator: crate::UnaryOp::Dereference { implicitly_added: false }, - rhs: lvalue.as_expression(span), + rhs: lvalue.as_expression(), })) } }; + let span = self.span(); Expression::new(kind, span) } + + pub fn span(&self) -> Span { + match self { + LValue::Ident(ident) => ident.span(), + LValue::MemberAccess { span, .. } + | LValue::Index { span, .. } + | LValue::Dereference(_, span) => *span, + } + } } #[derive(Debug, PartialEq, Eq, Clone)] @@ -675,9 +687,11 @@ impl Display for LValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { LValue::Ident(ident) => ident.fmt(f), - LValue::MemberAccess { object, field_name } => write!(f, "{object}.{field_name}"), - LValue::Index { array, index } => write!(f, "{array}[{index}]"), - LValue::Dereference(lvalue) => write!(f, "*{lvalue}"), + LValue::MemberAccess { object, field_name, span: _ } => { + write!(f, "{object}.{field_name}") + } + LValue::Index { array, index, span: _ } => write!(f, "{array}[{index}]"), + LValue::Dereference(lvalue, _span) => write!(f, "*{lvalue}"), } } } 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 8e5c174d270..71e0d44b478 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -282,7 +282,7 @@ impl DebugInstrumenter { .unwrap_or_else(|| panic!("var lookup failed for var_name={}", &id.0.contents)); build_assign_var_stmt(var_id, id_expr(&ident("__debug_expr", id.span()))) } - ast::LValue::Dereference(_lv) => { + ast::LValue::Dereference(_lv, span) => { // TODO: this is a dummy statement for now, but we should // somehow track the derefence and update the pointed to // variable @@ -303,16 +303,16 @@ impl DebugInstrumenter { }); break; } - ast::LValue::MemberAccess { object, field_name } => { + ast::LValue::MemberAccess { object, field_name, span } => { cursor = object; let field_name_id = self.insert_field_name(&field_name.0.contents); - indexes.push(sint_expr(-(field_name_id.0 as i128), expression_span)); + indexes.push(sint_expr(-(field_name_id.0 as i128), *span)); } - ast::LValue::Index { index, array } => { + ast::LValue::Index { index, array, span: _ } => { cursor = array; indexes.push(index.clone()); } - ast::LValue::Dereference(_ref) => { + ast::LValue::Dereference(_ref, _span) => { unimplemented![] } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs index 0c53bff4a54..90aa4baee7c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_crate.rs @@ -4,7 +4,7 @@ use crate::graph::CrateId; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; use crate::hir::resolution::errors::ResolverError; -use crate::hir::resolution::import::{resolve_import, ImportDirective}; +use crate::hir::resolution::import::{resolve_import, ImportDirective, PathResolution}; use crate::hir::resolution::{ collect_impls, collect_trait_impls, path_resolver, resolve_free_functions, resolve_globals, resolve_impls, resolve_structs, resolve_trait_by_path, resolve_trait_impls, resolve_traits, @@ -56,8 +56,11 @@ impl UnresolvedFunctions { for bound in &mut func.def.where_clause { match resolve_trait_by_path(def_maps, module, bound.trait_bound.trait_path.clone()) { - Ok(trait_id) => { + Ok((trait_id, warning)) => { bound.trait_bound.trait_id = Some(trait_id); + if let Some(warning) = warning { + errors.push(DefCollectorErrorKind::PathResolutionError(warning)); + } } Err(err) => { errors.push(err); @@ -281,6 +284,13 @@ impl DefCollector { for collected_import in def_collector.collected_imports { match resolve_import(crate_id, &collected_import, &context.def_maps) { Ok(resolved_import) => { + if let Some(error) = resolved_import.error { + errors.push(( + DefCollectorErrorKind::PathResolutionError(error).into(), + root_file_id, + )); + } + // Populate module namespaces according to the imports used let current_def_map = context.def_maps.get_mut(&crate_id).unwrap(); @@ -299,9 +309,9 @@ impl DefCollector { } } } - Err((error, module_id)) => { + Err(error) => { let current_def_map = context.def_maps.get(&crate_id).unwrap(); - let file_id = current_def_map.file_id(module_id); + let file_id = current_def_map.file_id(collected_import.module_id); let error = DefCollectorErrorKind::PathResolutionError(error); errors.push((error.into(), file_id)); } @@ -409,12 +419,13 @@ fn inject_prelude( Path { segments: segments.clone(), kind: crate::PathKind::Dep, span: Span::default() }; if !crate_id.is_stdlib() { - if let Ok(module_def) = path_resolver::resolve_path( + if let Ok(PathResolution { module_def_id, error }) = path_resolver::resolve_path( &context.def_maps, ModuleId { krate: crate_id, local_id: crate_root }, path, ) { - let module_id = module_def.as_module().expect("std::prelude should be a module"); + assert!(error.is_none(), "Tried to add private item to prelude"); + let module_id = module_def_id.as_module().expect("std::prelude should be a module"); let prelude = context.module(module_id).scope().names(); for path in prelude { 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 3c6c0582292..d5b0c612f90 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 @@ -25,7 +25,7 @@ pub enum ResolverError { #[error("path is not an identifier")] PathIsNotIdent { span: Span }, #[error("could not resolve path")] - PathResolutionError(PathResolutionError), + PathResolutionError(#[from] PathResolutionError), #[error("Expected")] Expected { span: Span, expected: String, got: String }, #[error("Duplicate field in constructor")] @@ -72,10 +72,6 @@ pub enum ResolverError { NumericConstantInFormatString { name: String, span: Span }, #[error("Closure environment must be a tuple or unit type")] InvalidClosureEnvironment { typ: Type, span: Span }, - #[error("{name} is private and not visible from the current module")] - PrivateFunctionCalled { name: String, span: Span }, - #[error("{name} is not visible from the current crate")] - NonCrateFunctionCalled { name: String, span: Span }, #[error("Nested slices are not supported")] NestedSlices { span: Span }, #[error("#[recursive] attribute is only allowed on entry points to a program")] @@ -290,13 +286,6 @@ impl From for Diagnostic { ResolverError::InvalidClosureEnvironment { span, typ } => Diagnostic::simple_error( format!("{typ} is not a valid closure environment type"), "Closure environment must be a tuple or unit type".to_string(), span), - // This will be upgraded to an error in future versions - ResolverError::PrivateFunctionCalled { span, name } => Diagnostic::simple_warning( - format!("{name} is private and not visible from the current module"), - format!("{name} is private"), span), - ResolverError::NonCrateFunctionCalled { span, name } => Diagnostic::simple_warning( - format!("{name} is not visible from the current crate"), - format!("{name} is only visible within its crate"), span), ResolverError::NestedSlices { span } => Diagnostic::simple_error( "Nested slices are not supported".into(), "Try to use a constant sized array instead".into(), diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs index 9c8418daf80..ade97e2cf42 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/import.rs @@ -1,10 +1,11 @@ use noirc_errors::{CustomDiagnostic, Span}; +use thiserror::Error; use crate::graph::CrateId; use std::collections::BTreeMap; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId, PerNs}; -use crate::{Ident, Path, PathKind}; +use crate::{Ident, ItemVisibility, Path, PathKind}; #[derive(Debug, Clone)] pub struct ImportDirective { @@ -14,12 +15,30 @@ pub struct ImportDirective { pub is_prelude: bool, } -pub type PathResolution = Result; +struct NamespaceResolution { + module_id: ModuleId, + namespace: PerNs, + error: Option, +} + +type NamespaceResolutionResult = Result; + +pub struct PathResolution { + pub module_def_id: ModuleDefId, + + pub error: Option, +} + +pub(crate) type PathResolutionResult = Result; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum PathResolutionError { + #[error("Could not resolve '{0}' in path")] Unresolved(Ident), + #[error("Contract variable '{0}' referenced from outside the contract")] ExternalContractUsed(Ident), + #[error("{0} is private and not visible from the current module")] + Private(Ident), } #[derive(Debug)] @@ -31,21 +50,26 @@ pub struct ResolvedImport { // The module which we must add the resolved namespace to pub module_scope: LocalModuleId, pub is_prelude: bool, + pub error: Option, } impl From for CustomDiagnostic { fn from(error: PathResolutionError) -> Self { - match error { - PathResolutionError::Unresolved(ident) => CustomDiagnostic::simple_error( - format!("Could not resolve '{ident}' in path"), - String::new(), - ident.span(), - ), + match &error { + PathResolutionError::Unresolved(ident) => { + CustomDiagnostic::simple_error(error.to_string(), String::new(), ident.span()) + } PathResolutionError::ExternalContractUsed(ident) => CustomDiagnostic::simple_error( - format!("Contract variable '{ident}' referenced from outside the contract"), + error.to_string(), "Contracts may only be referenced from within a contract".to_string(), ident.span(), ), + // This will be upgraded to an error in future versions + PathResolutionError::Private(ident) => CustomDiagnostic::simple_warning( + error.to_string(), + format!("{ident} is private"), + ident.span(), + ), } } } @@ -54,27 +78,49 @@ pub fn resolve_import( crate_id: CrateId, import_directive: &ImportDirective, def_maps: &BTreeMap, -) -> Result { - let def_map = &def_maps[&crate_id]; - +) -> Result { let allow_contracts = allow_referencing_contracts(def_maps, crate_id, import_directive.module_id); let module_scope = import_directive.module_id; - let resolved_namespace = - resolve_path_to_ns(import_directive, def_map, def_maps, allow_contracts) - .map_err(|error| (error, module_scope))?; + let NamespaceResolution { + module_id: resolved_module, + namespace: resolved_namespace, + mut error, + } = resolve_path_to_ns(import_directive, crate_id, crate_id, def_maps, allow_contracts)?; let name = resolve_path_name(import_directive); + + let visibility = resolved_namespace + .values + .or(resolved_namespace.types) + .map(|(_, visibility, _)| visibility) + .expect("Found empty namespace"); + + error = error.or_else(|| { + if can_reference_module_id( + def_maps, + crate_id, + import_directive.module_id, + resolved_module, + visibility, + ) { + None + } else { + Some(PathResolutionError::Private(name.clone())) + } + }); + Ok(ResolvedImport { name, resolved_namespace, module_scope, is_prelude: import_directive.is_prelude, + error, }) } -pub(super) fn allow_referencing_contracts( +fn allow_referencing_contracts( def_maps: &BTreeMap, krate: CrateId, local_id: LocalModuleId, @@ -82,27 +128,40 @@ pub(super) fn allow_referencing_contracts( ModuleId { krate, local_id }.module(def_maps).is_contract } -pub fn resolve_path_to_ns( +fn resolve_path_to_ns( import_directive: &ImportDirective, - def_map: &CrateDefMap, + crate_id: CrateId, + importing_crate: CrateId, def_maps: &BTreeMap, allow_contracts: bool, -) -> PathResolution { +) -> NamespaceResolutionResult { let import_path = &import_directive.path.segments; + let def_map = &def_maps[&crate_id]; match import_directive.path.kind { crate::ast::PathKind::Crate => { // Resolve from the root of the crate - resolve_path_from_crate_root(def_map, import_path, def_maps, allow_contracts) - } - crate::ast::PathKind::Dep => { - resolve_external_dep(def_map, import_directive, def_maps, allow_contracts) + resolve_path_from_crate_root( + crate_id, + importing_crate, + import_path, + def_maps, + allow_contracts, + ) } + crate::ast::PathKind::Dep => resolve_external_dep( + def_map, + import_directive, + def_maps, + allow_contracts, + importing_crate, + ), crate::ast::PathKind::Plain => { // Plain paths are only used to import children modules. It's possible to allow import of external deps, but maybe this distinction is better? // In Rust they can also point to external Dependencies, if no children can be found with the specified name resolve_name_in_module( - def_map, + crate_id, + importing_crate, import_path, import_directive.module_id, def_maps, @@ -113,45 +172,60 @@ pub fn resolve_path_to_ns( } fn resolve_path_from_crate_root( - def_map: &CrateDefMap, + crate_id: CrateId, + importing_crate: CrateId, + import_path: &[Ident], def_maps: &BTreeMap, allow_contracts: bool, -) -> PathResolution { - resolve_name_in_module(def_map, import_path, def_map.root, def_maps, allow_contracts) +) -> NamespaceResolutionResult { + resolve_name_in_module( + crate_id, + importing_crate, + import_path, + def_maps[&crate_id].root, + def_maps, + allow_contracts, + ) } fn resolve_name_in_module( - def_map: &CrateDefMap, + krate: CrateId, + importing_crate: CrateId, import_path: &[Ident], starting_mod: LocalModuleId, def_maps: &BTreeMap, allow_contracts: bool, -) -> PathResolution { - let mut current_mod = &def_map.modules[starting_mod.0]; +) -> NamespaceResolutionResult { + let def_map = &def_maps[&krate]; + let mut current_mod_id = ModuleId { krate, local_id: starting_mod }; + let mut current_mod = &def_map.modules[current_mod_id.local_id.0]; // There is a possibility that the import path is empty // In that case, early return if import_path.is_empty() { - let mod_id = ModuleId { krate: def_map.krate, local_id: starting_mod }; - return Ok(PerNs::types(mod_id.into())); + return Ok(NamespaceResolution { + module_id: current_mod_id, + namespace: PerNs::types(current_mod_id.into()), + error: None, + }); } - let mut import_path = import_path.iter(); - let first_segment = import_path.next().expect("ice: could not fetch first segment"); + let first_segment = import_path.first().expect("ice: could not fetch first segment"); let mut current_ns = current_mod.find_name(first_segment); if current_ns.is_none() { return Err(PathResolutionError::Unresolved(first_segment.clone())); } - for segment in import_path { - let typ = match current_ns.take_types() { - None => return Err(PathResolutionError::Unresolved(segment.clone())), - Some(typ) => typ, + let mut warning: Option = None; + for (last_segment, current_segment) in import_path.iter().zip(import_path.iter().skip(1)) { + let (typ, visibility) = match current_ns.types { + None => return Err(PathResolutionError::Unresolved(last_segment.clone())), + Some((typ, visibility, _)) => (typ, visibility), }; // In the type namespace, only Mod can be used in a path. - let new_module_id = match typ { + current_mod_id = match typ { ModuleDefId::ModuleId(id) => id, ModuleDefId::FunctionId(_) => panic!("functions cannot be in the type namespace"), // TODO: If impls are ever implemented, types can be used in a path @@ -161,22 +235,37 @@ fn resolve_name_in_module( ModuleDefId::GlobalId(_) => panic!("globals cannot be in the type namespace"), }; - current_mod = &def_maps[&new_module_id.krate].modules[new_module_id.local_id.0]; + warning = warning.or_else(|| { + if can_reference_module_id( + def_maps, + importing_crate, + starting_mod, + current_mod_id, + visibility, + ) { + None + } else { + Some(PathResolutionError::Private(last_segment.clone())) + } + }); + + current_mod = &def_maps[¤t_mod_id.krate].modules[current_mod_id.local_id.0]; // Check if namespace - let found_ns = current_mod.find_name(segment); + let found_ns = current_mod.find_name(current_segment); if found_ns.is_none() { - return Err(PathResolutionError::Unresolved(segment.clone())); + return Err(PathResolutionError::Unresolved(current_segment.clone())); } + // Check if it is a contract and we're calling from a non-contract context if current_mod.is_contract && !allow_contracts { - return Err(PathResolutionError::ExternalContractUsed(segment.clone())); + return Err(PathResolutionError::ExternalContractUsed(current_segment.clone())); } current_ns = found_ns; } - Ok(current_ns) + Ok(NamespaceResolution { module_id: current_mod_id, namespace: current_ns, error: warning }) } fn resolve_path_name(import_directive: &ImportDirective) -> Ident { @@ -191,11 +280,11 @@ fn resolve_external_dep( directive: &ImportDirective, def_maps: &BTreeMap, allow_contracts: bool, -) -> PathResolution { + importing_crate: CrateId, +) -> NamespaceResolutionResult { // Use extern_prelude to get the dep - // let path = &directive.path.segments; - // + // Fetch the root module from the prelude let crate_name = path.first().unwrap(); let dep_module = current_def_map @@ -218,7 +307,49 @@ fn resolve_external_dep( is_prelude: false, }; - let dep_def_map = def_maps.get(&dep_module.krate).unwrap(); + resolve_path_to_ns(&dep_directive, dep_module.krate, importing_crate, def_maps, allow_contracts) +} + +// Issue an error if the given private function is being called from a non-child module, or +// if the given pub(crate) function is being called from another crate +fn can_reference_module_id( + def_maps: &BTreeMap, + importing_crate: CrateId, + current_module: LocalModuleId, + target_module: ModuleId, + visibility: ItemVisibility, +) -> bool { + // Note that if the target module is in a different crate from the current module then we will either + // return true as the target module is public or return false as it is private without looking at the `CrateDefMap` in either case. + let same_crate = target_module.krate == importing_crate; + let target_crate_def_map = &def_maps[&target_module.krate]; + + match visibility { + ItemVisibility::Public => true, + ItemVisibility::PublicCrate => same_crate, + ItemVisibility::Private => { + same_crate + && module_descendent_of_target( + target_crate_def_map, + target_module.local_id, + current_module, + ) + } + } +} + +// Returns true if `current` is a (potentially nested) child module of `target`. +// This is also true if `current == target`. +fn module_descendent_of_target( + def_map: &CrateDefMap, + target: LocalModuleId, + current: LocalModuleId, +) -> bool { + if current == target { + return true; + } - resolve_path_to_ns(&dep_directive, dep_def_map, def_maps, allow_contracts) + def_map.modules[current.0] + .parent + .map_or(false, |parent| module_descendent_of_target(def_map, target, parent)) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs index 4c5fa3bceef..e19af3c732f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/path_resolver.rs @@ -1,11 +1,9 @@ -use super::import::{ - allow_referencing_contracts, resolve_path_to_ns, ImportDirective, PathResolutionError, -}; +use super::import::{resolve_import, ImportDirective, PathResolution, PathResolutionResult}; use crate::Path; use std::collections::BTreeMap; use crate::graph::CrateId; -use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleDefId, ModuleId}; +use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}; pub trait PathResolver { /// Resolve the given path returning the resolved ModuleDefId. @@ -13,7 +11,7 @@ pub trait PathResolver { &self, def_maps: &BTreeMap, path: Path, - ) -> Result; + ) -> PathResolutionResult; fn local_module_id(&self) -> LocalModuleId; @@ -36,7 +34,7 @@ impl PathResolver for StandardPathResolver { &self, def_maps: &BTreeMap, path: Path, - ) -> Result { + ) -> PathResolutionResult { resolve_path(def_maps, self.module_id, path) } @@ -55,17 +53,15 @@ pub fn resolve_path( def_maps: &BTreeMap, module_id: ModuleId, path: Path, -) -> Result { +) -> PathResolutionResult { // lets package up the path into an ImportDirective and resolve it using that let import = ImportDirective { module_id: module_id.local_id, path, alias: None, is_prelude: false }; - let allow_referencing_contracts = - allow_referencing_contracts(def_maps, module_id.krate, module_id.local_id); + let resolved_import = resolve_import(module_id.krate, &import, def_maps)?; - let def_map = &def_maps[&module_id.krate]; - let ns = resolve_path_to_ns(&import, def_map, def_maps, allow_referencing_contracts)?; + let namespace = resolved_import.resolved_namespace; + let id = + namespace.values.or(namespace.types).map(|(id, _, _)| id).expect("Found empty namespace"); - let function = ns.values.map(|(id, _, _)| id); - let id = function.or_else(|| ns.types.map(|(id, _, _)| id)); - Ok(id.expect("Found empty namespace")) + Ok(PathResolution { module_def_id: id, error: resolved_import.error }) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 624961ddaed..f2b8212db7a 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -25,7 +25,7 @@ use std::collections::{BTreeMap, HashSet}; use std::rc::Rc; use crate::graph::CrateId; -use crate::hir::def_map::{LocalModuleId, ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; +use crate::hir::def_map::{ModuleDefId, TryFromModuleDefId, MAIN_FUNCTION}; use crate::hir_def::stmt::{HirAssignStatement, HirForStatement, HirLValue, HirPattern}; use crate::node_interner::{ DefinitionId, DefinitionKind, DependencyId, ExprId, FuncId, GlobalId, NodeInterner, StmtId, @@ -56,6 +56,7 @@ use crate::hir_def::{ }; use super::errors::{PubPosition, ResolverError}; +use super::import::PathResolution; const SELF_TYPE_NAME: &str = "Self"; @@ -677,10 +678,14 @@ impl<'a> Resolver<'a> { // If we cannot find a local generic of the same name, try to look up a global match self.path_resolver.resolve(self.def_maps, path.clone()) { - Ok(ModuleDefId::GlobalId(id)) => { + Ok(PathResolution { module_def_id: ModuleDefId::GlobalId(id), error }) => { if let Some(current_item) = self.current_item { self.interner.add_global_dependency(current_item, id); } + + if let Some(error) = error { + self.push_err(error.into()); + } Some(Type::Constant(self.eval_global_as_array_length(id, path))) } _ => None, @@ -1306,75 +1311,27 @@ impl<'a> Resolver<'a> { HirLValue::Ident(ident.0, Type::Error) } - LValue::MemberAccess { object, field_name } => { - let object = Box::new(self.resolve_lvalue(*object)); - HirLValue::MemberAccess { object, field_name, field_index: None, typ: Type::Error } - } - LValue::Index { array, index } => { + LValue::MemberAccess { object, field_name, span } => HirLValue::MemberAccess { + object: Box::new(self.resolve_lvalue(*object)), + field_name, + location: Location::new(span, self.file), + field_index: None, + typ: Type::Error, + }, + LValue::Index { array, index, span } => { let array = Box::new(self.resolve_lvalue(*array)); let index = self.resolve_expression(index); - HirLValue::Index { array, index, typ: Type::Error } + let location = Location::new(span, self.file); + HirLValue::Index { array, index, location, typ: Type::Error } } - LValue::Dereference(lvalue) => { + LValue::Dereference(lvalue, span) => { let lvalue = Box::new(self.resolve_lvalue(*lvalue)); - HirLValue::Dereference { lvalue, element_type: Type::Error } - } - } - } - - // Issue an error if the given private function is being called from a non-child module, or - // if the given pub(crate) function is being called from another crate - fn check_can_reference_function( - &mut self, - func: FuncId, - span: Span, - visibility: ItemVisibility, - ) { - let function_module = self.interner.function_module(func); - let current_module = self.path_resolver.module_id(); - - let same_crate = function_module.krate == current_module.krate; - let krate = function_module.krate; - let current_module = current_module.local_id; - let name = self.interner.function_name(&func).to_string(); - match visibility { - ItemVisibility::Public => (), - ItemVisibility::Private => { - if !same_crate - || !self.module_descendent_of_target( - krate, - function_module.local_id, - current_module, - ) - { - self.errors.push(ResolverError::PrivateFunctionCalled { span, name }); - } - } - ItemVisibility::PublicCrate => { - if !same_crate { - self.errors.push(ResolverError::NonCrateFunctionCalled { span, name }); - } + let location = Location::new(span, self.file); + HirLValue::Dereference { lvalue, location, element_type: Type::Error } } } } - // Returns true if `current` is a (potentially nested) child module of `target`. - // This is also true if `current == target`. - fn module_descendent_of_target( - &self, - krate: CrateId, - target: LocalModuleId, - current: LocalModuleId, - ) -> bool { - if current == target { - return true; - } - - self.def_maps[&krate].modules[current.0] - .parent - .map_or(false, |parent| self.module_descendent_of_target(krate, target, parent)) - } - fn resolve_local_variable(&mut self, hir_ident: HirIdent, var_scope_index: usize) { let mut transitive_capture_index: Option = None; @@ -1468,15 +1425,6 @@ impl<'a> Resolver<'a> { if let Some(current_item) = self.current_item { self.interner.add_function_dependency(current_item, id); } - - if self.interner.function_visibility(id) != ItemVisibility::Public { - let span = hir_ident.location.span; - self.check_can_reference_function( - id, - span, - self.interner.function_visibility(id), - ); - } } DefinitionKind::Global(global_id) => { if let Some(current_item) = self.current_item { @@ -1915,7 +1863,7 @@ impl<'a> Resolver<'a> { } if let Ok(ModuleDefId::TraitId(trait_id)) = - self.path_resolver.resolve(self.def_maps, trait_bound.trait_path.clone()) + self.resolve_path(trait_bound.trait_path.clone()) { let the_trait = self.interner.get_trait(trait_id); if let Some(method) = @@ -1950,7 +1898,13 @@ impl<'a> Resolver<'a> { } fn resolve_path(&mut self, path: Path) -> Result { - self.path_resolver.resolve(self.def_maps, path).map_err(ResolverError::PathResolutionError) + let path_resolution = self.path_resolver.resolve(self.def_maps, path)?; + + if let Some(error) = path_resolution.error { + self.push_err(error.into()); + } + + Ok(path_resolution.module_def_id) } fn resolve_block(&mut self, block_expr: BlockExpression) -> HirExpression { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs index 04da558a642..a7669f57e33 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/resolution/traits.rs @@ -21,6 +21,7 @@ use crate::{ use super::{ functions, get_module_mut, get_struct_type, + import::{PathResolution, PathResolutionError}, path_resolver::{PathResolver, StandardPathResolver}, resolver::Resolver, take_errors, @@ -274,7 +275,15 @@ fn collect_trait_impl( let module = ModuleId { local_id: trait_impl.module_id, krate: crate_id }; trait_impl.trait_id = match resolve_trait_by_path(def_maps, module, trait_impl.trait_path.clone()) { - Ok(trait_id) => Some(trait_id), + Ok((trait_id, warning)) => { + if let Some(warning) = warning { + errors.push(( + DefCollectorErrorKind::PathResolutionError(warning).into(), + trait_impl.file_id, + )); + } + Some(trait_id) + } Err(error) => { errors.push((error.into(), trait_impl.file_id)); None @@ -362,11 +371,13 @@ pub(crate) fn resolve_trait_by_path( def_maps: &BTreeMap, module: ModuleId, path: Path, -) -> Result { +) -> Result<(TraitId, Option), DefCollectorErrorKind> { let path_resolver = StandardPathResolver::new(module); match path_resolver.resolve(def_maps, path.clone()) { - Ok(ModuleDefId::TraitId(trait_id)) => Ok(trait_id), + Ok(PathResolution { module_def_id: ModuleDefId::TraitId(trait_id), error }) => { + Ok((trait_id, error)) + } Ok(_) => Err(DefCollectorErrorKind::NotATrait { not_a_trait_name: path }), Err(_) => Err(DefCollectorErrorKind::TraitNotFound { trait_path: path }), } 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 c881efcfc45..6c28aabe0fb 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 @@ -65,8 +65,6 @@ pub enum TypeCheckError { VariableMustBeMutable { name: String, span: Span }, #[error("No method named '{method_name}' found for type '{object_type}'")] UnresolvedMethodCall { method_name: String, object_type: Type, span: Span }, - #[error("Comparisons are invalid on Field types. Try casting the operands to a sized integer type first")] - InvalidComparisonOnField { span: Span }, #[error("Integers must have the same signedness LHS is {sign_x:?}, RHS is {sign_y:?}")] IntegerSignedness { sign_x: Signedness, sign_y: Signedness, span: Span }, #[error("Integers must have the same bit width LHS is {bit_width_x}, RHS is {bit_width_y}")] @@ -76,7 +74,7 @@ pub enum TypeCheckError { #[error("{kind} cannot be used in a unary operation")] InvalidUnaryOp { kind: String, span: Span }, #[error("Bitwise operations are invalid on Field types. Try casting the operands to a sized integer type first.")] - InvalidBitwiseOperationOnField { span: Span }, + FieldBitwiseOp { span: Span }, #[error("Integer cannot be used with type {typ}")] IntegerTypeMismatch { typ: Type, span: Span }, #[error("Cannot use an integer and a Field in a binary operation, try converting the Field into an integer first")] @@ -224,12 +222,11 @@ impl From for Diagnostic { | TypeCheckError::TupleIndexOutOfBounds { span, .. } | TypeCheckError::VariableMustBeMutable { span, .. } | TypeCheckError::UnresolvedMethodCall { span, .. } - | TypeCheckError::InvalidComparisonOnField { span } | TypeCheckError::IntegerSignedness { span, .. } | TypeCheckError::IntegerBitWidth { span, .. } | TypeCheckError::InvalidInfixOp { span, .. } | TypeCheckError::InvalidUnaryOp { span, .. } - | TypeCheckError::InvalidBitwiseOperationOnField { span, .. } + | TypeCheckError::FieldBitwiseOp { span, .. } | TypeCheckError::IntegerTypeMismatch { span, .. } | TypeCheckError::FieldComparison { span, .. } | TypeCheckError::AmbiguousBitWidth { span, .. } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs index 10476b6caef..b56e2dce2a9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -126,7 +126,7 @@ impl<'interner> TypeChecker<'interner> { } } HirLiteral::Bool(_) => Type::Bool, - HirLiteral::Integer(_, _) => Type::polymorphic_integer_or_field(self.interner), + HirLiteral::Integer(_, _) => self.polymorphic_integer_or_field(), HirLiteral::Str(string) => { let len = Type::Constant(string.len() as u64); Type::String(Box::new(len)) @@ -152,14 +152,16 @@ impl<'interner> TypeChecker<'interner> { Ok((typ, use_impl)) => { if use_impl { let id = infix_expr.trait_method_id; - // Assume operators have no trait generics - self.verify_trait_constraint( - &lhs_type, - id.trait_id, - &[], - *expr_id, - span, - ); + + // Delay checking the trait constraint until the end of the function. + // Checking it now could bind an unbound type variable to any type + // that implements the trait. + let constraint = crate::hir_def::traits::TraitConstraint { + typ: lhs_type.clone(), + trait_id: id.trait_id, + trait_generics: Vec::new(), + }; + self.trait_constraints.push((constraint, *expr_id)); self.typecheck_operator_method(*expr_id, id, &lhs_type, span); } typ @@ -416,23 +418,28 @@ impl<'interner> TypeChecker<'interner> { self.interner.select_impl_for_expression(function_ident_id, impl_kind); } Err(erroring_constraints) => { - // Don't show any errors where try_get_trait returns None. - // This can happen if a trait is used that was never declared. - let constraints = erroring_constraints - .into_iter() - .map(|constraint| { - let r#trait = self.interner.try_get_trait(constraint.trait_id)?; - let mut name = r#trait.name.to_string(); - if !constraint.trait_generics.is_empty() { - let generics = vecmap(&constraint.trait_generics, ToString::to_string); - name += &format!("<{}>", generics.join(", ")); - } - Some((constraint.typ, name)) - }) - .collect::>>(); + if erroring_constraints.is_empty() { + self.errors.push(TypeCheckError::TypeAnnotationsNeeded { span }); + } else { + // Don't show any errors where try_get_trait returns None. + // This can happen if a trait is used that was never declared. + let constraints = erroring_constraints + .into_iter() + .map(|constraint| { + let r#trait = self.interner.try_get_trait(constraint.trait_id)?; + let mut name = r#trait.name.to_string(); + if !constraint.trait_generics.is_empty() { + let generics = + vecmap(&constraint.trait_generics, ToString::to_string); + name += &format!("<{}>", generics.join(", ")); + } + Some((constraint.typ, name)) + }) + .collect::>>(); - if let Some(constraints) = constraints { - self.errors.push(TypeCheckError::NoMatchingImplFound { constraints, span }); + if let Some(constraints) = constraints { + self.errors.push(TypeCheckError::NoMatchingImplFound { constraints, span }); + } } } } @@ -558,15 +565,13 @@ impl<'interner> TypeChecker<'interner> { let index_type = self.check_expression(&index_expr.index); let span = self.interner.expr_span(&index_expr.index); - index_type.unify( - &Type::polymorphic_integer_or_field(self.interner), - &mut self.errors, - || TypeCheckError::TypeMismatch { + index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || { + TypeCheckError::TypeMismatch { expected_typ: "an integer".to_owned(), expr_typ: index_type.to_string(), expr_span: span, - }, - ); + } + }); // When writing `a[i]`, if `a : &mut ...` then automatically dereference `a` as many // times as needed to get the underlying array. @@ -836,6 +841,10 @@ impl<'interner> TypeChecker<'interner> { match (lhs_type, rhs_type) { // Avoid reporting errors multiple times (Error, _) | (_, Error) => Ok((Bool, false)), + (Alias(alias, args), other) | (other, Alias(alias, args)) => { + let alias = alias.borrow().get_type(args); + self.comparator_operand_type_rules(&alias, other, op, span) + } // Matches on TypeVariable must be first to follow any type // bindings. @@ -844,12 +853,8 @@ impl<'interner> TypeChecker<'interner> { return self.comparator_operand_type_rules(other, binding, op, span); } - self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); - Ok((Bool, false)) - } - (Alias(alias, args), other) | (other, Alias(alias, args)) => { - let alias = alias.borrow().get_type(args); - self.comparator_operand_type_rules(&alias, other, op, span) + let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); + Ok((Bool, use_impl)) } (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { if sign_x != sign_y { @@ -1079,13 +1084,16 @@ impl<'interner> TypeChecker<'interner> { } } + /// Handles the TypeVariable case for checking binary operators. + /// Returns true if we should use the impl for the operator instead of the primitive + /// version of it. fn bind_type_variables_for_infix( &mut self, lhs_type: &Type, op: &HirBinaryOp, rhs_type: &Type, span: Span, - ) { + ) -> bool { self.unify(lhs_type, rhs_type, || TypeCheckError::TypeMismatchWithSource { expected: lhs_type.clone(), actual: rhs_type.clone(), @@ -1093,22 +1101,26 @@ impl<'interner> TypeChecker<'interner> { span, }); - // In addition to unifying both types, we also have to bind either - // the lhs or rhs to an integer type variable. This ensures if both lhs - // and rhs are type variables, that they will have the correct integer - // type variable kind instead of TypeVariableKind::Normal. - let target = if op.kind.is_valid_for_field_type() { - Type::polymorphic_integer_or_field(self.interner) - } else { - Type::polymorphic_integer(self.interner) - }; + let use_impl = !lhs_type.is_numeric(); + + // If this operator isn't valid for fields we have to possibly narrow + // TypeVariableKind::IntegerOrField to TypeVariableKind::Integer. + // Doing so also ensures a type error if Field is used. + // The is_numeric check is to allow impls for custom types to bypass this. + if !op.kind.is_valid_for_field_type() && lhs_type.is_numeric() { + let target = Type::polymorphic_integer(self.interner); + + use BinaryOpKind::*; + use TypeCheckError::*; + self.unify(lhs_type, &target, || match op.kind { + Less | LessEqual | Greater | GreaterEqual => FieldComparison { span }, + And | Or | Xor | ShiftRight | ShiftLeft => FieldBitwiseOp { span }, + Modulo => FieldModulo { span }, + other => unreachable!("Operator {other:?} should be valid for Field"), + }); + } - self.unify(lhs_type, &target, || TypeCheckError::TypeMismatchWithSource { - expected: lhs_type.clone(), - actual: rhs_type.clone(), - source: Source::Binary, - span, - }); + use_impl } // Given a binary operator and another type. This method will produce the output type @@ -1130,6 +1142,10 @@ impl<'interner> TypeChecker<'interner> { match (lhs_type, rhs_type) { // An error type on either side will always return an error (Error, _) | (_, Error) => Ok((Error, false)), + (Alias(alias, args), other) | (other, Alias(alias, args)) => { + let alias = alias.borrow().get_type(args); + self.infix_operand_type_rules(&alias, op, other, span) + } // Matches on TypeVariable must be first so that we follow any type // bindings. @@ -1138,14 +1154,8 @@ impl<'interner> TypeChecker<'interner> { return self.infix_operand_type_rules(binding, op, other, span); } - self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); - - // Both types are unified so the choice of which to return is arbitrary - Ok((other.clone(), false)) - } - (Alias(alias, args), other) | (other, Alias(alias, args)) => { - let alias = alias.borrow().get_type(args); - self.infix_operand_type_rules(&alias, op, other, span) + let use_impl = self.bind_type_variables_for_infix(lhs_type, op, rhs_type, span); + Ok((other.clone(), use_impl)) } (Integer(sign_x, bit_width_x), Integer(sign_y, bit_width_y)) => { if sign_x != sign_y { @@ -1170,7 +1180,7 @@ impl<'interner> TypeChecker<'interner> { if op.kind == BinaryOpKind::Modulo { return Err(TypeCheckError::FieldModulo { span }); } else { - return Err(TypeCheckError::InvalidBitwiseOperationOnField { span }); + return Err(TypeCheckError::FieldBitwiseOp { span }); } } Ok((FieldElement, false)) @@ -1211,7 +1221,7 @@ impl<'interner> TypeChecker<'interner> { self.errors .push(TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span }); } - let expected = Type::polymorphic_integer_or_field(self.interner); + let expected = self.polymorphic_integer_or_field(); rhs_type.unify(&expected, &mut self.errors, || TypeCheckError::InvalidUnaryOp { kind: rhs_type.to_string(), span, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs index c6e9b7f804a..926dac30bcd 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/mod.rs @@ -37,6 +37,11 @@ pub struct TypeChecker<'interner> { /// on each variable, but it is only until function calls when the types /// needed for the trait constraint may become known. trait_constraints: Vec<(TraitConstraint, ExprId)>, + + /// All type variables created in the current function. + /// This map is used to default any integer type variables at the end of + /// a function (before checking trait constraints) if a type wasn't already chosen. + type_variables: Vec, } /// Type checks a function and assigns the @@ -86,31 +91,13 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec Vec Vec Vec TypeChecker<'interner> { fn new(interner: &'interner mut NodeInterner) -> Self { - Self { interner, errors: Vec::new(), trait_constraints: Vec::new(), current_function: None } + Self { + interner, + errors: Vec::new(), + trait_constraints: Vec::new(), + type_variables: Vec::new(), + current_function: None, + } } fn check_function_body(&mut self, body: &ExprId) -> Type { @@ -350,6 +369,7 @@ impl<'interner> TypeChecker<'interner> { interner, errors: Vec::new(), trait_constraints: Vec::new(), + type_variables: Vec::new(), current_function: None, }; let statement = this.interner.get_global(id).let_statement; @@ -383,6 +403,22 @@ impl<'interner> TypeChecker<'interner> { make_error, ); } + + /// Return a fresh integer or field type variable and log it + /// in self.type_variables to default it later. + fn polymorphic_integer_or_field(&mut self) -> Type { + let typ = Type::polymorphic_integer_or_field(self.interner); + self.type_variables.push(typ.clone()); + typ + } + + /// Return a fresh integer type variable and log it + /// in self.type_variables to default it later. + fn polymorphic_integer(&mut self) -> Type { + let typ = Type::polymorphic_integer(self.interner); + self.type_variables.push(typ.clone()); + typ + } } // XXX: These tests are all manual currently. @@ -398,7 +434,9 @@ mod test { use crate::graph::CrateId; use crate::hir::def_map::{ModuleData, ModuleId}; - use crate::hir::resolution::import::PathResolutionError; + use crate::hir::resolution::import::{ + PathResolution, PathResolutionError, PathResolutionResult, + }; use crate::hir_def::expr::HirIdent; use crate::hir_def::stmt::HirLetStatement; use crate::hir_def::stmt::HirPattern::Identifier; @@ -608,12 +646,13 @@ mod test { &self, _def_maps: &BTreeMap, path: Path, - ) -> Result { + ) -> PathResolutionResult { // Not here that foo::bar and hello::foo::bar would fetch the same thing let name = path.segments.last().unwrap(); self.0 .get(&name.0.contents) .cloned() + .map(|module_def_id| PathResolution { module_def_id, error: None }) .ok_or_else(move || PathResolutionError::Unresolved(name.clone())) } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs index 69363d5f00a..fb57aa75f89 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/type_check/stmt.rs @@ -1,5 +1,5 @@ use iter_extended::vecmap; -use noirc_errors::{Location, Span}; +use noirc_errors::Span; use crate::hir_def::expr::{HirExpression, HirIdent, HirLiteral}; use crate::hir_def::stmt::{ @@ -71,7 +71,7 @@ impl<'interner> TypeChecker<'interner> { expr_span: range_span, }); - let expected_type = Type::polymorphic_integer(self.interner); + let expected_type = self.polymorphic_integer(); self.unify(&start_range_type, &expected_type, || TypeCheckError::TypeCannotBeUsed { typ: start_range_type.clone(), @@ -195,51 +195,51 @@ impl<'interner> TypeChecker<'interner> { (typ.clone(), HirLValue::Ident(ident.clone(), typ), mutable) } - HirLValue::MemberAccess { object, field_name, .. } => { + HirLValue::MemberAccess { object, field_name, location, .. } => { let (lhs_type, object, mut mutable) = self.check_lvalue(object, assign_span); let mut object = Box::new(object); - let span = field_name.span(); let field_name = field_name.clone(); let object_ref = &mut object; let mutable_ref = &mut mutable; + let location = *location; let dereference_lhs = move |_: &mut Self, _, element_type| { // We must create a temporary value first to move out of object_ref before // we eventually reassign to it. let id = DefinitionId::dummy_id(); - let location = Location::new(span, fm::FileId::dummy()); let ident = HirIdent::non_trait_method(id, location); let tmp_value = HirLValue::Ident(ident, Type::Error); let lvalue = std::mem::replace(object_ref, Box::new(tmp_value)); - *object_ref = Box::new(HirLValue::Dereference { lvalue, element_type }); + *object_ref = + Box::new(HirLValue::Dereference { lvalue, element_type, location }); *mutable_ref = true; }; let name = &field_name.0.contents; let (object_type, field_index) = self - .check_field_access(&lhs_type, name, span, Some(dereference_lhs)) + .check_field_access(&lhs_type, name, field_name.span(), Some(dereference_lhs)) .unwrap_or((Type::Error, 0)); let field_index = Some(field_index); let typ = object_type.clone(); - let lvalue = HirLValue::MemberAccess { object, field_name, field_index, typ }; + let lvalue = + HirLValue::MemberAccess { object, field_name, field_index, typ, location }; (object_type, lvalue, mutable) } - HirLValue::Index { array, index, .. } => { + HirLValue::Index { array, index, location, .. } => { let index_type = self.check_expression(index); let expr_span = self.interner.expr_span(index); + let location = *location; - index_type.unify( - &Type::polymorphic_integer_or_field(self.interner), - &mut self.errors, - || TypeCheckError::TypeMismatch { + index_type.unify(&self.polymorphic_integer_or_field(), &mut self.errors, || { + TypeCheckError::TypeMismatch { expected_typ: "an integer".to_owned(), expr_typ: index_type.to_string(), expr_span, - }, - ); + } + }); let (mut lvalue_type, mut lvalue, mut mutable) = self.check_lvalue(array, assign_span); @@ -248,7 +248,8 @@ impl<'interner> TypeChecker<'interner> { // as needed to unwrap any &mut wrappers. while let Type::MutableReference(element) = lvalue_type.follow_bindings() { let element_type = element.as_ref().clone(); - lvalue = HirLValue::Dereference { lvalue: Box::new(lvalue), element_type }; + lvalue = + HirLValue::Dereference { lvalue: Box::new(lvalue), element_type, location }; lvalue_type = *element; // We know this value to be mutable now since we found an `&mut` mutable = true; @@ -275,11 +276,12 @@ impl<'interner> TypeChecker<'interner> { }; let array = Box::new(lvalue); - (typ.clone(), HirLValue::Index { array, index: *index, typ }, mutable) + (typ.clone(), HirLValue::Index { array, index: *index, typ, location }, mutable) } - HirLValue::Dereference { lvalue, element_type: _ } => { + HirLValue::Dereference { lvalue, element_type: _, location } => { let (reference_type, lvalue, _) = self.check_lvalue(lvalue, assign_span); let lvalue = Box::new(lvalue); + let location = *location; let element_type = Type::type_variable(self.interner.next_type_variable_id()); let expected_type = Type::MutableReference(Box::new(element_type.clone())); @@ -291,7 +293,11 @@ impl<'interner> TypeChecker<'interner> { }); // Dereferences are always mutable since we already type checked against a &mut T - (element_type.clone(), HirLValue::Dereference { lvalue, element_type }, true) + ( + element_type.clone(), + HirLValue::Dereference { lvalue, element_type, location }, + true, + ) } } } 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 4e5f718cf47..c5e287b393c 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 @@ -99,6 +99,15 @@ impl HirPattern { | HirPattern::Struct(_, _, location) => location.span, } } + + pub(crate) fn location(&self) -> Location { + match self { + HirPattern::Identifier(ident) => ident.location, + HirPattern::Mutable(_, location) + | HirPattern::Tuple(_, location) + | HirPattern::Struct(_, _, location) => *location, + } + } } /// Represents an Ast form that can be assigned to. These @@ -111,14 +120,17 @@ pub enum HirLValue { field_name: Ident, field_index: Option, typ: Type, + location: Location, }, Index { array: Box, index: ExprId, typ: Type, + location: Location, }, Dereference { lvalue: Box, element_type: Type, + location: Location, }, } 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 65592407d7d..ec8b54c33b8 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 @@ -543,11 +543,11 @@ impl TypeBinding { pub struct TypeVariableId(pub usize); impl Type { - pub fn default_int_type() -> Type { + pub fn default_int_or_field_type() -> Type { Type::FieldElement } - pub fn default_range_loop_type() -> Type { + pub fn default_int_type() -> Type { Type::Integer(Signedness::Unsigned, IntegerBitSize::SixtyFour) } @@ -589,6 +589,7 @@ impl Type { TypeBinding::Bound(binding) => binding.is_bindable(), TypeBinding::Unbound(_) => true, }, + Type::Alias(alias, args) => alias.borrow().get_type(args).is_bindable(), _ => false, } } @@ -605,6 +606,15 @@ impl Type { matches!(self.follow_bindings(), Type::Integer(Signedness::Unsigned, _)) } + pub fn is_numeric(&self) -> bool { + use Type::*; + use TypeVariableKind as K; + matches!( + self.follow_bindings(), + FieldElement | Integer(..) | Bool | TypeVariable(_, K::Integer | K::IntegerOrField) + ) + } + fn contains_numeric_typevar(&self, target_id: TypeVariableId) -> bool { // True if the given type is a NamedGeneric with the target_id let named_generic_id_matches_target = |typ: &Type| { @@ -777,7 +787,7 @@ impl std::fmt::Display for Type { Type::TypeVariable(var, TypeVariableKind::Normal) => write!(f, "{}", var.borrow()), Type::TypeVariable(binding, TypeVariableKind::Integer) => { if let TypeBinding::Unbound(_) = &*binding.borrow() { - write!(f, "{}", TypeVariableKind::Integer.default_type()) + write!(f, "{}", Type::default_int_type()) } else { write!(f, "{}", binding.borrow()) } @@ -1696,11 +1706,12 @@ impl BinaryTypeOperator { impl TypeVariableKind { /// Returns the default type this type variable should be bound to if it is still unbound /// during monomorphization. - pub(crate) fn default_type(&self) -> Type { + pub(crate) fn default_type(&self) -> Option { match self { - TypeVariableKind::IntegerOrField | TypeVariableKind::Normal => Type::default_int_type(), - TypeVariableKind::Integer => Type::default_range_loop_type(), - TypeVariableKind::Constant(length) => Type::Constant(*length), + TypeVariableKind::IntegerOrField => Some(Type::default_int_or_field_type()), + TypeVariableKind::Integer => Some(Type::default_int_type()), + TypeVariableKind::Constant(length) => Some(Type::Constant(*length)), + TypeVariableKind::Normal => None, } } } @@ -1734,12 +1745,12 @@ impl From<&Type> for PrintableType { }, Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { TypeBinding::Bound(typ) => typ.into(), - TypeBinding::Unbound(_) => Type::default_range_loop_type().into(), + TypeBinding::Unbound(_) => Type::default_int_type().into(), }, Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) => { match &*binding.borrow() { TypeBinding::Bound(typ) => typ.into(), - TypeBinding::Unbound(_) => Type::default_int_type().into(), + TypeBinding::Unbound(_) => Type::default_int_or_field_type().into(), } } Type::Bool => PrintableType::Boolean, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/LICENSE b/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/LICENSE new file mode 100644 index 00000000000..4ab5bbd793b --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2020 Max Woolf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/README.md b/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/README.md new file mode 100644 index 00000000000..201ed68aef0 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/README.md @@ -0,0 +1,9 @@ +# BLNS + +[Big List of Naughty Strings](https://github.com/minimaxir/big-list-of-naughty-strings) + +## Updating + +```bash +wget -O blns.base64.json "https://raw.githubusercontent.com/minimaxir/big-list-of-naughty-strings/master/blns.base64.json" +``` diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/blns.base64.json b/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/blns.base64.json new file mode 100644 index 00000000000..18e61c0d2c4 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/blns/blns.base64.json @@ -0,0 +1,679 @@ +[ + "", + "dW5kZWZpbmVk", + "dW5kZWY=", + "bnVsbA==", + "TlVMTA==", + "KG51bGwp", + "bmls", + "TklM", + "dHJ1ZQ==", + "ZmFsc2U=", + "VHJ1ZQ==", + "RmFsc2U=", + "VFJVRQ==", + "RkFMU0U=", + "Tm9uZQ==", + "aGFzT3duUHJvcGVydHk=", + "XA==", + "MA==", + "MQ==", + "MS4wMA==", + "JDEuMDA=", + "MS8y", + "MUUy", + "MUUwMg==", + "MUUrMDI=", + "LTE=", + "LTEuMDA=", + "LSQxLjAw", + "LTEvMg==", + "LTFFMg==", + "LTFFMDI=", + "LTFFKzAy", + "MS8w", + "MC8w", + "LTIxNDc0ODM2NDgvLTE=", + "LTkyMjMzNzIwMzY4NTQ3NzU4MDgvLTE=", + "LTA=", + "LTAuMA==", + "KzA=", + "KzAuMA==", + "MC4wMA==", + "MC4uMA==", + "Lg==", + "MC4wLjA=", + "MCwwMA==", + "MCwsMA==", + "LA==", + "MCwwLDA=", + "MC4wLzA=", + "MS4wLzAuMA==", + "MC4wLzAuMA==", + "MSwwLzAsMA==", + "MCwwLzAsMA==", + "LS0x", + "LQ==", + "LS4=", + "LSw=", + "OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5", + "OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5OTk5", + "TmFO", + "SW5maW5pdHk=", + "LUluZmluaXR5", + "SU5G", + "MSNJTkY=", + "LTEjSU5E", + "MSNRTkFO", + "MSNTTkFO", + "MSNJTkQ=", + "MHgw", + "MHhmZmZmZmZmZg==", + "MHhmZmZmZmZmZmZmZmZmZmZm", + "MHhhYmFkMWRlYQ==", + "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5", + "MSwwMDAuMDA=", + "MSAwMDAuMDA=", + "MScwMDAuMDA=", + "MSwwMDAsMDAwLjAw", + "MSAwMDAgMDAwLjAw", + "MScwMDAnMDAwLjAw", + "MS4wMDAsMDA=", + "MSAwMDAsMDA=", + "MScwMDAsMDA=", + "MS4wMDAuMDAwLDAw", + "MSAwMDAgMDAwLDAw", + "MScwMDAnMDAwLDAw", + "MDEwMDA=", + "MDg=", + "MDk=", + "Mi4yMjUwNzM4NTg1MDcyMDExZS0zMDg=", + "LC4vOydbXS09", + "PD4/OiJ7fXxfKw==", + "IUAjJCVeJiooKWB+", + "AQIDBAUGBwgODxAREhMUFRYXGBkaGxwdHh9/", + "woDCgcKCwoPChMKGwofCiMKJworCi8KMwo3CjsKPwpDCkcKSwpPClMKVwpbCl8KYwpnCmsKbwpzC", + "ncKewp8=", + "CwwgwoXCoOGagOKAgOKAgeKAguKAg+KAhOKAheKAhuKAh+KAiOKAieKAiuKAi+KAqOKAqeKAr+KB", + "n+OAgA==", + "wq3YgNiB2ILYg9iE2IXYnNud3I/hoI7igIvigIzigI3igI7igI/igKrigKvigKzigK3igK7igaDi", + "gaHigaLigaPigaTigabigafigajiganigarigavigaziga3iga7iga/vu7/vv7nvv7rvv7vwkYK9", + "8JuyoPCbsqHwm7Ki8Juyo/CdhbPwnYW08J2FtfCdhbbwnYW38J2FuPCdhbnwnYW686CAgfOggKDz", + "oICh86CAovOggKPzoICk86CApfOggKbzoICn86CAqPOggKnzoICq86CAq/OggKzzoICt86CArvOg", + "gK/zoICw86CAsfOggLLzoICz86CAtPOggLXzoIC286CAt/OggLjzoIC586CAuvOggLvzoIC886CA", + "vfOggL7zoIC/86CBgPOggYHzoIGC86CBg/OggYTzoIGF86CBhvOggYfzoIGI86CBifOggYrzoIGL", + "86CBjPOggY3zoIGO86CBj/OggZDzoIGR86CBkvOggZPzoIGU86CBlfOggZbzoIGX86CBmPOggZnz", + "oIGa86CBm/OggZzzoIGd86CBnvOggZ/zoIGg86CBofOggaLzoIGj86CBpPOggaXzoIGm86CBp/Og", + "gajzoIGp86CBqvOggavzoIGs86CBrfOgga7zoIGv86CBsPOggbHzoIGy86CBs/OggbTzoIG186CB", + "tvOggbfzoIG486CBufOggbrzoIG786CBvPOggb3zoIG+86CBvw==", + "77u/", + "77++", + "zqniiYjDp+KImuKIq8ucwrXiiaTiiaXDtw==", + "w6XDn+KIgsaSwqnLmeKIhsuawqzigKbDpg==", + "xZPiiJHCtMKu4oCgwqXCqMuGw7jPgOKAnOKAmA==", + "wqHihKLCo8Ki4oiewqfCtuKAosKqwrrigJPiiaA=", + "wrjLm8OH4peKxLHLnMOCwq/LmMK/", + "w4XDjcOOw4/LncOTw5Tvo7/DksOaw4bimIM=", + "xZLigJ7CtOKAsMuHw4HCqMuGw5jiiI/igJ3igJk=", + "YOKBhOKCrOKAueKAuu+sge+sguKAocKwwrfigJrigJTCsQ==", + "4oWb4oWc4oWd4oWe", + "0IHQgtCD0ITQhdCG0IfQiNCJ0IrQi9CM0I3QjtCP0JDQkdCS0JPQlNCV0JbQl9CY0JnQmtCb0JzQ", + "ndCe0J/QoNCh0KLQo9Ck0KXQptCn0KjQqdCq0KvQrNCt0K7Qr9Cw0LHQstCz0LTQtdC20LfQuNC5", + "0LrQu9C80L3QvtC/0YDRgdGC0YPRhNGF0YbRh9GI0YnRitGL0YzRjdGO0Y8=", + "2aDZodmi2aPZpNml2abZp9mo2ak=", + "4oGw4oG04oG1", + "4oKA4oKB4oKC", + "4oGw4oG04oG14oKA4oKB4oKC", + "4LiU4LmJ4LmJ4LmJ4LmJ4LmJ4LmH4LmH4LmH4LmH4LmH4LmJ4LmJ4LmJ4LmJ4LmJ4LmH4LmH4LmH", + "4LmH4LmH4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmH4LmH4LmH4LmH4LmH4LmJ4LmJ4LmJ4LmJ", + "4LmJ4LmH4LmH4LmH4LmH4LmH4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmH4LmH4LmH4LmH4LmH", + "4LmJ4LmJ4LmJ4LmJ4LmJ4LmH4LmH4LmH4LmH4LmH4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmJ4LmH", + "4LmH4LmH4LmH4LmH4LmJ4LmJ4LmJ4LmJ4LmJ4LmH4LmH4LmH4LmHIOC4lOC5ieC5ieC5ieC5ieC5", + "ieC5h+C5h+C5h+C5h+C5h+C5ieC5ieC5ieC5ieC5ieC5h+C5h+C5h+C5h+C5h+C5ieC5ieC5ieC5", + "ieC5ieC5ieC5ieC5ieC5h+C5h+C5h+C5h+C5h+C5ieC5ieC5ieC5ieC5ieC5h+C5h+C5h+C5h+C5", + "h+C5ieC5ieC5ieC5ieC5ieC5ieC5ieC5ieC5h+C5h+C5h+C5h+C5h+C5ieC5ieC5ieC5ieC5ieC5", + "h+C5h+C5h+C5h+C5h+C5ieC5ieC5ieC5ieC5ieC5ieC5ieC5ieC5h+C5h+C5h+C5h+C5h+C5ieC5", + "ieC5ieC5ieC5ieC5h+C5h+C5h+C5hyDguJTguYnguYnguYnguYnguYnguYfguYfguYfguYfguYfg", + "uYnguYnguYnguYnguYnguYfguYfguYfguYfguYfguYnguYnguYnguYnguYnguYnguYnguYnguYfg", + "uYfguYfguYfguYfguYnguYnguYnguYnguYnguYfguYfguYfguYfguYfguYnguYnguYnguYnguYng", + "uYnguYnguYnguYfguYfguYfguYfguYfguYnguYnguYnguYnguYnguYfguYfguYfguYfguYfguYng", + "uYnguYnguYnguYnguYnguYnguYnguYfguYfguYfguYfguYfguYnguYnguYnguYnguYnguYfguYfg", + "uYfguYc=", + "Jw==", + "Ig==", + "Jyc=", + "IiI=", + "JyIn", + "IicnJyciJyI=", + "IiciJyInJycnIg==", + "PGZvbyB2YWw94oCcYmFy4oCdIC8+", + "PGZvbyB2YWw94oCcYmFy4oCdIC8+", + "PGZvbyB2YWw94oCdYmFy4oCcIC8+", + "PGZvbyB2YWw9YGJhcicgLz4=", + "55Sw5Lit44GV44KT44Gr44GC44GS44Gm5LiL44GV44GE", + "44OR44O844OG44Kj44O844G46KGM44GL44Gq44GE44GL", + "5ZKM6KO95ryi6Kqe", + "6YOo6JC95qC8", + "7IKs7ZqM6rO87ZWZ7JuQIOyWtO2VmeyXsOq1rOyGjA==", + "7LCm7LCo66W8IO2DgOqzoCDsmKgg7Y6y7Iuc66eo6rO8IOyRm+uLpOumrCDrmKDrsKnqsIHtlZg=", + "56S+5pyD56eR5a246Zmi6Kqe5a2456CU56m25omA", + "7Jq4656A67CU7Yag66W0", + "8KCcjvCgnLHwoJ258KCxk/CgsbjwoLKW8KCzjw==", + "6KGo44Od44GCQem3l8WSw6nvvKLpgI3DnMOfwqrEhcOx5LiC45CA8KCAgA==", + "44O94Ly84LqI2YTNnOC6iOC8ve++iSDjg73gvLzguojZhM2c4LqI4Ly9776J", + "KO+9oeKXlSDiiIAg4peV772hKQ==", + "772A772oKMK04oiA772A4oip", + "X1/vvpsoLF8sKik=", + "44O7KO+/o+KIgO+/oynjg7s6Kjo=", + "776f772l4py/44O+4pWyKO+9oeKXleKAv+KXle+9oSnilbHinL/vvaXvvp8=", + "LOOAguODuzoqOuODu+OCnOKAmSgg4pi7IM+JIOKYuyAp44CC44O7Oio644O744Kc4oCZ", + "KOKVr8Kw4pahwrDvvInila/vuLUg4pS74pSB4pS7KQ==", + "KO++ieCypeebiuCype+8ie++ie+7vyDilLvilIHilLs=", + "4pSs4pSA4pSs44OOKCDCuiBfIMK644OOKQ==", + "KCDNocKwIM2cypYgzaHCsCk=", + "8J+YjQ==", + "8J+RqfCfj70=", + "8J+RqOKAjfCfprAg8J+RqPCfj7/igI3wn6awIPCfkajigI3wn6axIPCfkajwn4+/4oCN8J+msSDwn6a58J+Pv+KAjeKZgu+4jw==", + "8J+RviDwn5mHIPCfkoEg8J+ZhSDwn5mGIPCfmYsg8J+ZjiDwn5mN", + "8J+QtSDwn5mIIPCfmYkg8J+Zig==", + "4p2k77iPIPCfkpQg8J+SjCDwn5KVIPCfkp4g8J+SkyDwn5KXIPCfkpYg8J+SmCDwn5KdIPCfkp8g", + "8J+SnCDwn5KbIPCfkpog8J+SmQ==", + "4pyL8J+PvyDwn5Kq8J+PvyDwn5GQ8J+PvyDwn5mM8J+PvyDwn5GP8J+PvyDwn5mP8J+Pvw==", + "8J+aviDwn4aSIPCfhpMg8J+GlSDwn4aWIPCfhpcg8J+GmSDwn4+n", + "MO+4j+KDoyAx77iP4oOjIDLvuI/ig6MgM++4j+KDoyA077iP4oOjIDXvuI/ig6MgNu+4j+KDoyA3", + "77iP4oOjIDjvuI/ig6MgOe+4j+KDoyDwn5Sf", + "8J+HuvCfh7jwn4e38J+HuvCfh7gg8J+HpvCfh6vwn4em8J+HsvCfh7g=", + "8J+HuvCfh7jwn4e38J+HuvCfh7jwn4em8J+Hq/Cfh6bwn4ey", + "8J+HuvCfh7jwn4e38J+HuvCfh7jwn4em", + "77yR77yS77yT", + "2aHZotmj", + "2KvZhSDZhtmB2LMg2LPZgti32Kog2YjYqNin2YTYqtit2K/Zitiv2IwsINis2LLZitix2KrZiiDY", + "qNin2LPYqtiu2K/Yp9mFINij2YYg2K/ZhtmILiDYpdiwINmH2YbYp9ifINin2YTYs9iq2KfYsSDZ", + "iNiq2YbYtdmK2Kgg2YPYp9mGLiDYo9mH2ZHZhCDYp9mK2LfYp9mE2YrYp9iMINio2LHZiti32KfZ", + "htmK2Kct2YHYsdmG2LPYpyDZgtivINij2K7YsC4g2LPZhNmK2YXYp9mG2Iwg2KXYqtmB2KfZgtmK", + "2Kkg2KjZitmGINmF2KcsINmK2LDZg9ixINin2YTYrdiv2YjYryDYo9mKINio2LnYrywg2YXYudin", + "2YXZhNipINio2YjZhNmG2K/Yp9iMINin2YTYpdi32YTYp9mCINi52YQg2KXZitmILg==", + "15HWsNa816jWtdeQ16nWtNeB15nXqiwg15HWuNa816jWuNeQINeQ1rHXnNa515TWtNeZ150sINeQ", + "1rXXqiDXlNa316nWuNa814HXnta315nWtNedLCDXldaw15DWtdeqINeU1rjXkNa416jWttel", + "15TWuNeZ1rDXqta415R0ZXN02KfZhNi12YHYrdin2Kog2KfZhNiq2ZHYrdmI2YQ=", + "77e9", + "77e6", + "2YXZj9mG2Y7Yp9mC2Y7YtNmO2KnZjyDYs9mP2KjZj9mE2ZAg2KfZkNiz2ZLYqtmQ2K7Zktiv2Y7Y", + "p9mF2ZAg2KfZhNmE2ZHZj9i62Y7YqdmQINmB2ZDZiiDYp9mE2YbZkdmP2LjZj9mF2ZAg2KfZhNmS", + "2YLZjtin2KbZkNmF2Y7YqdmQINmI2Y7ZgdmQ2YrZhSDZitmO2K7Zj9i12ZHZjiDYp9mE2KrZkdmO", + "2LfZktio2ZDZitmC2Y7Yp9iq2Y8g2KfZhNmS2K3Yp9iz2Y/ZiNio2ZDZitmR2Y7YqdmP2Iw=", + "4Zqb4ZqE4ZqT4ZqQ4ZqL4ZqS4ZqE4ZqA4ZqR4ZqE4ZqC4ZqR4ZqP4ZqF4Zqc", + "4Zqb4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqA4ZqcCg==", + "4oCq4oCqdGVzdOKAqg==", + "4oCrdGVzdOKAqw==", + "4oCpdGVzdOKAqQ==", + "dGVzdOKBoHRlc3TigKs=", + "4oGmdGVzdOKBpw==", + "4bmwzLrMusyVb82eIMy3acyyzKzNh8yqzZluzJ3Ml82VdsyfzJzMmMymzZ9vzLbMmcywzKBrw6jN", + "msyuzLrMqsy5zLHMpCDMlnTMnc2VzLPMo8y7zKrNnmjMvM2TzLLMpsyzzJjMsmXNh8yjzLDMpsys", + "zY4gzKLMvMy7zLHMmGjNms2OzZnMnMyjzLLNhWnMpsyyzKPMsMykdsy7zY1lzLrMrcyzzKrMsC1t", + "zKJpzYVuzJbMusyezLLMr8ywZMy1zLzMn82ZzKnMvMyYzLMgzJ7MpcyxzLPMrXLMm8yXzJhlzZlw", + "zaByzLzMnsy7zK3Ml2XMusygzKPNn3PMmM2HzLPNjcydzYllzYnMpcyvzJ7Mss2azKzNnMe5zKzN", + "js2OzJ/Mls2HzKR0zY3MrMykzZPMvMytzZjNhWnMqsyxbs2gZ8y0zYkgzY/Nic2FY8yszJ9ozaFh", + "zKvMu8yvzZhvzKvMn8yWzY3MmcydzYlzzJfMpsyyLsyozLnNiMyj", + "zKHNk8yezYVJzJfMmMymzZ1uzYfNh82ZdsyuzKtva8yyzKvMmc2IacyWzZnMrcy5zKDMnm7Mocy7", + "zK7Mo8y6Z8yyzYjNmcytzZnMrM2OIMywdM2UzKZozJ7MsmXMosykIM2NzKzMss2WZsy0zJjNlcyj", + "w6jNluG6ucylzKlszZbNlM2aac2TzZrMps2gbs2WzY3Ml82TzLPMrmfNjSDMqG/NmsyqzaFmzJjM", + "o8ysIMyWzJjNlsyfzZnMrmPSic2UzKvNls2TzYfNls2FaMy1zKTMo82azZTDocyXzLzNlc2Fb8y8", + "zKPMpXPMsc2IzLrMlsymzLvNoi7Mm8yWzJ7MoMyrzLA=", + "zJfMus2WzLnMr82T4bmuzKTNjcylzYfNiGjMssyBZc2PzZPMvMyXzJnMvMyjzZQgzYfMnMyxzKDN", + "k82NzYVOzZXNoGXMl8yxesyYzJ3MnMy6zZlwzKTMusy5zY3Mr82aZcygzLvMoM2ccsyozKTNjcy6", + "zJbNlMyWzJZkzKDMn8ytzKzMnc2facymzZbMqc2TzZTMpGHMoMyXzKzNicyZbs2azZwgzLvMnsyw", + "zZrNhWjMtc2JacyzzJ52zKLNh+G4mc2OzZ8t0onMrcypzLzNlG3MpMytzKtpzZXNh8ydzKZuzJfN", + "meG4jcyfIMyvzLLNlc2ex6vMn8yvzLDMss2ZzLvMnWYgzKrMsMywzJfMlsytzJjNmGPMps2NzLLM", + "ns2NzKnMmeG4pc2aYcyuzY7Mn8yZzZzGocypzLnNjnPMpC7MncydINKJWsyhzJbMnM2WzLDMo82J", + "zJxhzZbMsM2ZzKzNoWzMssyrzLPNjcypZ8yhzJ/MvMyxzZrMnsyszYVvzJfNnC7Mnw==", + "zKZIzKzMpMyXzKTNnWXNnCDMnMylzJ3Mu82NzJ/MgXfMlWjMlsyvzZNvzJ3NmcyWzY7MscyuINKJ", + "zLrMmcyezJ/NiFfMt8y8zK1hzLrMqs2NxK/NiM2VzK3NmcyvzJx0zLbMvMyuc8yYzZnNlsyVIMyg", + "zKvMoELMu82NzZnNicyzzYVlzLVozLXMrM2HzKvNmWnMuc2TzLPMs8yuzY7Mq8yVbs2fZMy0zKrM", + "nMyWIMywzYnMqc2HzZnMss2ezYVUzZbMvM2TzKrNomjNj82TzK7Mu2XMrMydzJ/NhSDMpMy5zJ1X", + "zZnMnsydzZTNh82dzYVhzY/Nk82UzLnMvMyjbMy0zZTMsMykzJ/NlOG4vcyrLs2V", + "WsyuzJ7MoM2ZzZTNheG4gMyXzJ7NiMy7zJfhuLbNmc2OzK/MucyezZNHzLtPzK3Ml8yu", + "y5nJkG5i4bSJbMmQIMmQdcaDyZDJryDHncm5b2xvcCDKh8edIMedyblvccmQbCDKh24gyod1bnDh", + "tIlw4bSJyZR14bSJIMm5b2TJr8edyocgcG/Jr3Nu4bSJx50gb3AgcMedcyAnyofhtIlsx50gxoN1", + "4bSJyZRz4bSJZOG0iXDJkCDJuW7Kh8edyofJlMedc3VvyZQgJ8qHx53Jr8mQIMqH4bSJcyDJuW9s", + "b3Agya9uc2ThtIkgya/Hncm5b8ul", + "MDDLmcaWJC0=", + "77y0772I772FIO+9ke+9le+9ie+9g++9iyDvvYLvvZLvvY/vvZfvvY4g772G772P772YIO+9iu+9", + "le+9je+9kO+9kyDvvY/vvZbvvYXvvZIg772U772I772FIO+9jO+9ge+9mu+9mSDvvYTvvY/vvYc=", + "8J2Qk/CdkKHwnZCeIPCdkKrwnZCu8J2QovCdkJzwnZCkIPCdkJvwnZCr8J2QqPCdkLDwnZCnIPCd", + "kJ/wnZCo8J2QsSDwnZCj8J2QrvCdkKbwnZCp8J2QrCDwnZCo8J2Qr/CdkJ7wnZCrIPCdkK3wnZCh", + "8J2QniDwnZCl8J2QmvCdkLPwnZCyIPCdkJ3wnZCo8J2QoA==", + "8J2Vv/Cdlo3wnZaKIPCdlpbwnZaa8J2WjvCdlojwnZaQIPCdlofwnZaX8J2WlPCdlpzwnZaTIPCd", + "lovwnZaU8J2WnSDwnZaP8J2WmvCdlpLwnZaV8J2WmCDwnZaU8J2Wm/CdlorwnZaXIPCdlpnwnZaN", + "8J2WiiDwnZaR8J2WhvCdlp/wnZaeIPCdlonwnZaU8J2WjA==", + "8J2Ru/CdkonwnZKGIPCdkpLwnZKW8J2SivCdkoTwnZKMIPCdkoPwnZKT8J2SkPCdkpjwnZKPIPCd", + "kofwnZKQ8J2SmSDwnZKL8J2SlvCdko7wnZKR8J2SlCDwnZKQ8J2Sl/CdkobwnZKTIPCdkpXwnZKJ", + "8J2ShiDwnZKN8J2SgvCdkpvwnZKaIPCdkoXwnZKQ8J2SiA==", + "8J2To/Cdk7HwnZOuIPCdk7rwnZO+8J2TsvCdk6zwnZO0IPCdk6vwnZO78J2TuPCdlIDwnZO3IPCd", + "k6/wnZO48J2UgSDwnZOz8J2TvvCdk7bwnZO58J2TvCDwnZO48J2Tv/Cdk67wnZO7IPCdk73wnZOx", + "8J2TriDwnZO18J2TqvCdlIPwnZSCIPCdk63wnZO48J2TsA==", + "8J2Vi/CdlZnwnZWWIPCdlaLwnZWm8J2VmvCdlZTwnZWcIPCdlZPwnZWj8J2VoPCdlajwnZWfIPCd", + "lZfwnZWg8J2VqSDwnZWb8J2VpvCdlZ7wnZWh8J2VpCDwnZWg8J2Vp/CdlZbwnZWjIPCdlaXwnZWZ", + "8J2VliDwnZWd8J2VkvCdlavwnZWqIPCdlZXwnZWg8J2VmA==", + "8J2ag/CdmpHwnZqOIPCdmprwnZqe8J2akvCdmozwnZqUIPCdmovwnZqb8J2amPCdmqDwnZqXIPCd", + "mo/wnZqY8J2aoSDwnZqT8J2anvCdmpbwnZqZ8J2anCDwnZqY8J2an/Cdmo7wnZqbIPCdmp3wnZqR", + "8J2ajiDwnZqV8J2aivCdmqPwnZqiIPCdmo3wnZqY8J2akA==", + "4pKv4pKj4pKgIOKSrOKSsOKSpOKSnuKSpiDikp3ikq3ikqrikrLikqkg4pKh4pKq4pKzIOKSpeKS", + "sOKSqOKSq+KSriDikqrikrHikqDikq0g4pKv4pKj4pKgIOKSp+KSnOKSteKStCDikp/ikqrikqI=", + "PHNjcmlwdD5hbGVydCgxMjMpPC9zY3JpcHQ+", + "Jmx0O3NjcmlwdCZndDthbGVydCgmIzM5OzEyMyYjMzk7KTsmbHQ7L3NjcmlwdCZndDs=", + "PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEyMykgLz4=", + "PHN2Zz48c2NyaXB0PjEyMzwxPmFsZXJ0KDEyMyk8L3NjcmlwdD4=", + "Ij48c2NyaXB0PmFsZXJ0KDEyMyk8L3NjcmlwdD4=", + "Jz48c2NyaXB0PmFsZXJ0KDEyMyk8L3NjcmlwdD4=", + "PjxzY3JpcHQ+YWxlcnQoMTIzKTwvc2NyaXB0Pg==", + "PC9zY3JpcHQ+PHNjcmlwdD5hbGVydCgxMjMpPC9zY3JpcHQ+", + "PCAvIHNjcmlwdCA+PCBzY3JpcHQgPmFsZXJ0KDEyMyk8IC8gc2NyaXB0ID4=", + "b25mb2N1cz1KYVZhU0NyaXB0OmFsZXJ0KDEyMykgYXV0b2ZvY3Vz", + "IiBvbmZvY3VzPUphVmFTQ3JpcHQ6YWxlcnQoMTIzKSBhdXRvZm9jdXM=", + "JyBvbmZvY3VzPUphVmFTQ3JpcHQ6YWxlcnQoMTIzKSBhdXRvZm9jdXM=", + "77ycc2NyaXB077yeYWxlcnQoMTIzKe+8nC9zY3JpcHTvvJ4=", + "PHNjPHNjcmlwdD5yaXB0PmFsZXJ0KDEyMyk8L3NjPC9zY3JpcHQ+cmlwdD4=", + "LS0+PHNjcmlwdD5hbGVydCgxMjMpPC9zY3JpcHQ+", + "IjthbGVydCgxMjMpO3Q9Ig==", + "JzthbGVydCgxMjMpO3Q9Jw==", + "SmF2YVNDcmlwdDphbGVydCgxMjMp", + "O2FsZXJ0KDEyMyk7", + "c3JjPUphVmFTQ3JpcHQ6cHJvbXB0KDEzMik=", + "Ij48c2NyaXB0PmFsZXJ0KDEyMyk7PC9zY3JpcHQgeD0i", + "Jz48c2NyaXB0PmFsZXJ0KDEyMyk7PC9zY3JpcHQgeD0n", + "PjxzY3JpcHQ+YWxlcnQoMTIzKTs8L3NjcmlwdCB4PQ==", + "IiBhdXRvZm9jdXMgb25rZXl1cD0iamF2YXNjcmlwdDphbGVydCgxMjMp", + "JyBhdXRvZm9jdXMgb25rZXl1cD0namF2YXNjcmlwdDphbGVydCgxMjMp", + "PHNjcmlwdHgyMHR5cGU9InRleHQvamF2YXNjcmlwdCI+amF2YXNjcmlwdDphbGVydCgxKTs8L3Nj", + "cmlwdD4=", + "PHNjcmlwdHgzRXR5cGU9InRleHQvamF2YXNjcmlwdCI+amF2YXNjcmlwdDphbGVydCgxKTs8L3Nj", + "cmlwdD4=", + "PHNjcmlwdHgwRHR5cGU9InRleHQvamF2YXNjcmlwdCI+amF2YXNjcmlwdDphbGVydCgxKTs8L3Nj", + "cmlwdD4=", + "PHNjcmlwdHgwOXR5cGU9InRleHQvamF2YXNjcmlwdCI+amF2YXNjcmlwdDphbGVydCgxKTs8L3Nj", + "cmlwdD4=", + "PHNjcmlwdHgwQ3R5cGU9InRleHQvamF2YXNjcmlwdCI+amF2YXNjcmlwdDphbGVydCgxKTs8L3Nj", + "cmlwdD4=", + "PHNjcmlwdHgyRnR5cGU9InRleHQvamF2YXNjcmlwdCI+amF2YXNjcmlwdDphbGVydCgxKTs8L3Nj", + "cmlwdD4=", + "PHNjcmlwdHgwQXR5cGU9InRleHQvamF2YXNjcmlwdCI+amF2YXNjcmlwdDphbGVydCgxKTs8L3Nj", + "cmlwdD4=", + "J2AiPjx4M0NzY3JpcHQ+amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "J2AiPjx4MDBzY3JpcHQ+amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "QUJDPGRpdiBzdHlsZT0ieHgzQWV4cHJlc3Npb24oamF2YXNjcmlwdDphbGVydCgxKSI+REVG", + "QUJDPGRpdiBzdHlsZT0ieDpleHByZXNzaW9ueDVDKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDpleHByZXNzaW9ueDAwKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDpleHB4MDByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDpleHB4NUNyZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4MEFleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4MDlleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTN4ODB4ODBleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODRleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4QzJ4QTBleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRF", + "Rg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODBleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4OEFleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4MERleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4MENleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODdleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RUZ4QkJ4QkZleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4MjBleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODhleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4MDBleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4OEJleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODZleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODVleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODJleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4MEJleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSkiPkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODFleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODNleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "QUJDPGRpdiBzdHlsZT0ieDp4RTJ4ODB4ODlleHByZXNzaW9uKGphdmFzY3JpcHQ6YWxlcnQoMSki", + "PkRFRg==", + "PGEgaHJlZj0ieDBCamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDBGamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEMyeEEwamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVs", + "ZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDA1amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUxeEEweDhFamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDE4amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDExamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDg4amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDg5amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDgwamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDE3amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDAzamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDBFamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDFBamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDAwamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDEwamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDgyamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDIwamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDEzamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDA5amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDhBamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDE0amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDE5amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweEFGamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDFGamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDgxamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDFEamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDg3amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDA3amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUxeDlBeDgwamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDgzamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDA0amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDAxamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDA4amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDg0amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDg2amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUzeDgweDgwamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDEyamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDBEamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDBBamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDBDamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDE1amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweEE4amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDE2amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDAyamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDFCamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDA2amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweEE5amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgweDg1amF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDFFamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieEUyeDgxeDlGamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6", + "emVsZW1lbnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0ieDFDamF2YXNjcmlwdDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0iamF2YXNjcmlwdHgwMDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0iamF2YXNjcmlwdHgzQTpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0iamF2YXNjcmlwdHgwOTpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0iamF2YXNjcmlwdHgwRDpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "PGEgaHJlZj0iamF2YXNjcmlwdHgwQTpqYXZhc2NyaXB0OmFsZXJ0KDEpIiBpZD0iZnV6emVsZW1l", + "bnQxIj50ZXN0PC9hPg==", + "YCInPjxpbWcgc3JjPXh4eDp4IHgwQW9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgyMm9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgwQm9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgwRG9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgyRm9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgwOW9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgwQ29uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgwMG9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgyN29uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "YCInPjxpbWcgc3JjPXh4eDp4IHgyMG9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT4=", + "ImAnPjxzY3JpcHQ+eDNCamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDBEamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEVGeEJCeEJGamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDgxamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDg0amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUzeDgweDgwamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDA5amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDg5amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDg1amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDg4amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDAwamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweEE4amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDhBamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUxeDlBeDgwamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDBDamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDJCamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEYweDkweDk2eDlBamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+LWphdmFzY3JpcHQ6YWxlcnQoMSk8L3NjcmlwdD4=", + "ImAnPjxzY3JpcHQ+eDBBamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweEFGamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDdFamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDg3amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgxeDlGamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweEE5amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEMyeDg1amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEVGeEJGeEFFamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDgzamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDhCamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEVGeEJGeEJFamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDgwamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDIxamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDgyamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUyeDgweDg2amF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEUxeEEweDhFamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDBCamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eDIwamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "ImAnPjxzY3JpcHQ+eEMyeEEwamF2YXNjcmlwdDphbGVydCgxKTwvc2NyaXB0Pg==", + "PGltZyB4MDBzcmM9eCBvbmVycm9yPSJhbGVydCgxKSI+", + "PGltZyB4NDdzcmM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyB4MTFzcmM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyB4MTJzcmM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZ3g0N3NyYz14IG9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZ3gxMHNyYz14IG9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZ3gxM3NyYz14IG9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZ3gzMnNyYz14IG9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZ3g0N3NyYz14IG9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZ3gxMXNyYz14IG9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZyB4NDdzcmM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyB4MzRzcmM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyB4MzlzcmM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyB4MDBzcmM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4MDk9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4MTA9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4MTM9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4MzI9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4MTI9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4MTE9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4MDA9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmN4NDc9eCBvbmVycm9yPSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmM9eHgwOW9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZyBzcmM9eHgxMG9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZyBzcmM9eHgxMW9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZyBzcmM9eHgxMm9uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZyBzcmM9eHgxM29uZXJyb3I9ImphdmFzY3JpcHQ6YWxlcnQoMSkiPg==", + "PGltZ1thXVtiXVtjXXNyY1tkXT14W2Vdb25lcnJvcj1bZl0iYWxlcnQoMSkiPg==", + "PGltZyBzcmM9eCBvbmVycm9yPXgwOSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmM9eCBvbmVycm9yPXgxMCJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmM9eCBvbmVycm9yPXgxMSJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmM9eCBvbmVycm9yPXgxMiJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmM9eCBvbmVycm9yPXgzMiJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGltZyBzcmM9eCBvbmVycm9yPXgwMCJqYXZhc2NyaXB0OmFsZXJ0KDEpIj4=", + "PGEgaHJlZj1qYXZhJiMxJiMyJiMzJiM0JiM1JiM2JiM3JiM4JiMxMSYjMTJzY3JpcHQ6amF2YXNj", + "cmlwdDphbGVydCgxKT5YWFg8L2E+", + "PGltZyBzcmM9InhgIGA8c2NyaXB0PmphdmFzY3JpcHQ6YWxlcnQoMSk8L3NjcmlwdD4iYCBgPg==", + "PGltZyBzcmMgb25lcnJvciAvIiAnIj0gYWx0PWphdmFzY3JpcHQ6YWxlcnQoMSkvLyI+", + "PHRpdGxlIG9ucHJvcGVydHljaGFuZ2U9amF2YXNjcmlwdDphbGVydCgxKT48L3RpdGxlPjx0aXRs", + "ZSB0aXRsZT0+", + "PGEgaHJlZj1odHRwOi8vZm9vLmJhci8jeD1geT48L2E+PGltZyBhbHQ9ImA+PGltZyBzcmM9eDp4", + "IG9uZXJyb3I9amF2YXNjcmlwdDphbGVydCgxKT48L2E+Ij4=", + "PCEtLVtpZl0+PHNjcmlwdD5qYXZhc2NyaXB0OmFsZXJ0KDEpPC9zY3JpcHQgLS0+", + "PCEtLVtpZjxpbWcgc3JjPXggb25lcnJvcj1qYXZhc2NyaXB0OmFsZXJ0KDEpLy9dPiAtLT4=", + "PHNjcmlwdCBzcmM9Ii8lKGpzY3JpcHQpcyI+PC9zY3JpcHQ+", + "PHNjcmlwdCBzcmM9IlwlKGpzY3JpcHQpcyI+PC9zY3JpcHQ+", + "PElNRyAiIiI+PFNDUklQVD5hbGVydCgiWFNTIik8L1NDUklQVD4iPg==", + "PElNRyBTUkM9amF2YXNjcmlwdDphbGVydChTdHJpbmcuZnJvbUNoYXJDb2RlKDg4LDgzLDgzKSk+", + "PElNRyBTUkM9IyBvbm1vdXNlb3Zlcj0iYWxlcnQoJ3h4cycpIj4=", + "PElNRyBTUkM9IG9ubW91c2VvdmVyPSJhbGVydCgneHhzJykiPg==", + "PElNRyBvbm1vdXNlb3Zlcj0iYWxlcnQoJ3h4cycpIj4=", + "PElNRyBTUkM9JiMxMDY7JiM5NzsmIzExODsmIzk3OyYjMTE1OyYjOTk7JiMxMTQ7JiMxMDU7JiMx", + "MTI7JiMxMTY7JiM1ODsmIzk3OyYjMTA4OyYjMTAxOyYjMTE0OyYjMTE2OyYjNDA7JiMzOTsmIzg4", + "OyYjODM7JiM4MzsmIzM5OyYjNDE7Pg==", + "PElNRyBTUkM9JiMwMDAwMTA2JiMwMDAwMDk3JiMwMDAwMTE4JiMwMDAwMDk3JiMwMDAwMTE1JiMw", + "MDAwMDk5JiMwMDAwMTE0JiMwMDAwMTA1JiMwMDAwMTEyJiMwMDAwMTE2JiMwMDAwMDU4JiMwMDAw", + "MDk3JiMwMDAwMTA4JiMwMDAwMTAxJiMwMDAwMTE0JiMwMDAwMTE2JiMwMDAwMDQwJiMwMDAwMDM5", + "JiMwMDAwMDg4JiMwMDAwMDgzJiMwMDAwMDgzJiMwMDAwMDM5JiMwMDAwMDQxPg==", + "PElNRyBTUkM9JiN4NkEmI3g2MSYjeDc2JiN4NjEmI3g3MyYjeDYzJiN4NzImI3g2OSYjeDcwJiN4", + "NzQmI3gzQSYjeDYxJiN4NkMmI3g2NSYjeDcyJiN4NzQmI3gyOCYjeDI3JiN4NTgmI3g1MyYjeDUz", + "JiN4MjcmI3gyOT4=", + "PElNRyBTUkM9ImphdiBhc2NyaXB0OmFsZXJ0KCdYU1MnKTsiPg==", + "PElNRyBTUkM9ImphdiYjeDA5O2FzY3JpcHQ6YWxlcnQoJ1hTUycpOyI+", + "PElNRyBTUkM9ImphdiYjeDBBO2FzY3JpcHQ6YWxlcnQoJ1hTUycpOyI+", + "PElNRyBTUkM9ImphdiYjeDBEO2FzY3JpcHQ6YWxlcnQoJ1hTUycpOyI+", + "cGVybCAtZSAncHJpbnQgIjxJTUcgU1JDPWphdmEwc2NyaXB0OmFsZXJ0KCJYU1MiKT4iOycgPiBv", + "dXQ=", + "PElNRyBTUkM9IiAmIzE0OyBqYXZhc2NyaXB0OmFsZXJ0KCdYU1MnKTsiPg==", + "PFNDUklQVC9YU1MgU1JDPSJodHRwOi8vaGEuY2tlcnMub3JnL3hzcy5qcyI+PC9TQ1JJUFQ+", + "PEJPRFkgb25sb2FkISMkJSYoKSp+Ky1fLiw6Oz9AWy98XV5gPWFsZXJ0KCJYU1MiKT4=", + "PFNDUklQVC9TUkM9Imh0dHA6Ly9oYS5ja2Vycy5vcmcveHNzLmpzIj48L1NDUklQVD4=", + "PDxTQ1JJUFQ+YWxlcnQoIlhTUyIpOy8vPDwvU0NSSVBUPg==", + "PFNDUklQVCBTUkM9aHR0cDovL2hhLmNrZXJzLm9yZy94c3MuanM/PCBCID4=", + "PFNDUklQVCBTUkM9Ly9oYS5ja2Vycy5vcmcvLmo+", + "PElNRyBTUkM9ImphdmFzY3JpcHQ6YWxlcnQoJ1hTUycpIg==", + "PGlmcmFtZSBzcmM9aHR0cDovL2hhLmNrZXJzLm9yZy9zY3JpcHRsZXQuaHRtbCA8", + "IjthbGVydCgnWFNTJyk7Ly8=", + "PHUgb25jb3B5PWFsZXJ0KCk+IENvcHkgbWU8L3U+", + "PGkgb253aGVlbD1hbGVydCgxKT4gU2Nyb2xsIG92ZXIgbWUgPC9pPg==", + "PHBsYWludGV4dD4=", + "aHR0cDovL2EvJSUzMCUzMA==", + "PC90ZXh0YXJlYT48c2NyaXB0PmFsZXJ0KDEyMyk8L3NjcmlwdD4=", + "MTtEUk9QIFRBQkxFIHVzZXJz", + "MSc7IERST1AgVEFCTEUgdXNlcnMtLSAx", + "JyBPUiAxPTEgLS0gMQ==", + "JyBPUiAnMSc9JzE=", + "JQ==", + "Xw==", + "LQ==", + "LS0=", + "LS12ZXJzaW9u", + "LS1oZWxw", + "JFVTRVI=", + "L2Rldi9udWxsOyB0b3VjaCAvdG1wL2JsbnMuZmFpbCA7IGVjaG8=", + "YHRvdWNoIC90bXAvYmxucy5mYWlsYA==", + "JCh0b3VjaCAvdG1wL2JsbnMuZmFpbCk=", + "QHtbc3lzdGVtICJ0b3VjaCAvdG1wL2JsbnMuZmFpbCJdfQ==", + "ZXZhbCgicHV0cyAnaGVsbG8gd29ybGQnIik=", + "U3lzdGVtKCJscyAtYWwgLyIp", + "YGxzIC1hbCAvYA==", + "S2VybmVsLmV4ZWMoImxzIC1hbCAvIik=", + "S2VybmVsLmV4aXQoMSk=", + "JXgoJ2xzIC1hbCAvJyk=", + "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iSVNPLTg4NTktMSI/PjwhRE9DVFlQRSBmb28g", + "WyA8IUVMRU1FTlQgZm9vIEFOWSA+PCFFTlRJVFkgeHhlIFNZU1RFTSAiZmlsZTovLy9ldGMvcGFz", + "c3dkIiA+XT48Zm9vPiZ4eGU7PC9mb28+", + "JEhPTUU=", + "JEVOVnsnSE9NRSd9", + "JWQ=", + "JXM=", + "ezB9", + "JSouKnM=", + "Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vZXRjL3Bhc3N3ZCUwMA==", + "Li4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vLi4vZXRjL2hvc3Rz", + "KCkgeyAwOyB9OyB0b3VjaCAvdG1wL2JsbnMuc2hlbGxzaG9jazEuZmFpbDs=", + "KCkgeyBfOyB9ID5fWyQoJCgpKV0geyB0b3VjaCAvdG1wL2JsbnMuc2hlbGxzaG9jazIuZmFpbDsg", + "fQ==", + "PDw8ICVzKHVuPSclcycpID0gJXU=", + "KysrQVRIMA==", + "Q09O", + "UFJO", + "QVVY", + "Q0xPQ0sk", + "TlVM", + "QTo=", + "Wlo6", + "Q09NMQ==", + "TFBUMQ==", + "TFBUMg==", + "TFBUMw==", + "Q09NMg==", + "Q09NMw==", + "Q09NNA==", + "RENDIFNFTkQgU1RBUlRLRVlMT0dHRVIgMCAwIDA=", + "U2N1bnRob3JwZSBHZW5lcmFsIEhvc3BpdGFs", + "UGVuaXN0b25lIENvbW11bml0eSBDaHVyY2g=", + "TGlnaHR3YXRlciBDb3VudHJ5IFBhcms=", + "SmltbXkgQ2xpdGhlcm9l", + "SG9ybmltYW4gTXVzZXVt", + "c2hpdGFrZSBtdXNocm9vbXM=", + "Um9tYW5zSW5TdXNzZXguY28udWs=", + "aHR0cDovL3d3dy5jdW0ucWMuY2Ev", + "Q3JhaWcgQ29ja2J1cm4sIFNvZnR3YXJlIFNwZWNpYWxpc3Q=", + "TGluZGEgQ2FsbGFoYW4=", + "RHIuIEhlcm1hbiBJLiBMaWJzaGl0eg==", + "bWFnbmEgY3VtIGxhdWRl", + "U3VwZXIgQm93bCBYWFg=", + "bWVkaWV2YWwgZXJlY3Rpb24gb2YgcGFyYXBldHM=", + "ZXZhbHVhdGU=", + "bW9jaGE=", + "ZXhwcmVzc2lvbg==", + "QXJzZW5hbCBjYW5hbA==", + "Y2xhc3NpYw==", + "VHlzb24gR2F5", + "RGljayBWYW4gRHlrZQ==", + "YmFzZW1lbnQ=", + "SWYgeW91J3JlIHJlYWRpbmcgdGhpcywgeW91J3ZlIGJlZW4gaW4gYSBjb21hIGZvciBhbG1vc3Qg", + "MjAgeWVhcnMgbm93LiBXZSdyZSB0cnlpbmcgYSBuZXcgdGVjaG5pcXVlLiBXZSBkb24ndCBrbm93", + "IHdoZXJlIHRoaXMgbWVzc2FnZSB3aWxsIGVuZCB1cCBpbiB5b3VyIGRyZWFtLCBidXQgd2UgaG9w", + "ZSBpdCB3b3Jrcy4gUGxlYXNlIHdha2UgdXAsIHdlIG1pc3MgeW91Lg==", + "Um9zZXMgYXJlIBtbMDszMW1yZWQbWzBtLCB2aW9sZXRzIGFyZSAbWzA7MzRtYmx1ZS4gSG9wZSB5", + "b3UgZW5qb3kgdGVybWluYWwgaHVl", + "QnV0IG5vdy4uLhtbMjBDZm9yIG15IGdyZWF0ZXN0IHRyaWNrLi4uG1s4bQ==", + "VGhlIHF1aWMICAgICAhrIGJyb3duIGZvBwcHBwcHBwcHBwd4Li4uIFtCZWVlZXBd", + "UG93ZXLZhNmP2YTZj9i12ZHYqNmP2YTZj9mE2LXZkdio2Y/Ysdix2Ysg4KWjIOClo2gg4KWjIOCl", + "o+WGlw==", + "2q/ahtm+2pg=", + "eyUgcHJpbnQgJ3gnICogNjQgKiAxMDI0KiozICV9", + "e3sgIiIuX19jbGFzc19fLl9fbXJvX19bMl0uX19zdWJjbGFzc2VzX18oKVs0MF0oIi9ldGMvcGFz", + "c3dkIikucmVhZCgpIH19" +] + 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 363ca767a89..265b9e4b5a3 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs @@ -1097,4 +1097,114 @@ mod tests { assert_eq!(got, token); } } + + // returns a vector of: + // (expected_token_discriminator, strings_to_lex) + // expected_token_discriminator matches a given token when + // std::mem::discriminant returns the same discriminant for both. + fn blns_base64_to_statements(base64_str: String) -> Vec<(Option, Vec)> { + use base64::engine::general_purpose; + use std::borrow::Cow; + use std::io::Cursor; + use std::io::Read; + + let mut wrapped_reader = Cursor::new(base64_str); + let mut decoder = + base64::read::DecoderReader::new(&mut wrapped_reader, &general_purpose::STANDARD); + let mut base64_decoded = Vec::new(); + decoder.read_to_end(&mut base64_decoded).unwrap(); + + // NOTE: when successful, this is the same conversion method as used in + // noirc_driver::stdlib::stdlib_paths_with_source, viz. + // + // let source = std::str::from_utf8(..).unwrap().to_string(); + let s: Cow<'_, str> = match std::str::from_utf8(&base64_decoded) { + Ok(s) => std::borrow::Cow::Borrowed(s), + Err(_err) => { + // recover as much of the string as possible + // when str::from_utf8 fails + String::from_utf8_lossy(&base64_decoded) + } + }; + + vec![ + // Token::Ident(_) + (None, vec![format!("let \"{s}\" = ();")]), + (Some(Token::Str("".to_string())), vec![format!("let s = \"{s}\";")]), + ( + Some(Token::RawStr("".to_string(), 0)), + vec![ + // let s = r"Hello world"; + format!("let s = r\"{s}\";"), + // let s = r#"Simon says "hello world""#; + format!("let s = r#\"{s}\"#;"), + // // Any number of hashes may be used (>= 1) as long as the string also terminates with the same number of hashes + // let s = r#####"One "#, Two "##, Three "###, Four "####, Five will end the string."#####; + format!("let s = r##\"{s}\"##;"), + format!("let s = r###\"{s}\"###;"), + format!("let s = r####\"{s}\"####; "), + format!("let s = r#####\"{s}\"#####;"), + ], + ), + (Some(Token::FmtStr("".to_string())), vec![format!("assert(x == y, f\"{s}\");")]), + // expected token not found + // (Some(Token::LineComment("".to_string(), None)), vec![ + (None, vec![format!("//{s}"), format!("// {s}")]), + // expected token not found + // (Some(Token::BlockComment("".to_string(), None)), vec![ + (None, vec![format!("/*{s}*/"), format!("/* {s} */"), format!("/*\n{s}\n*/")]), + ] + } + + #[test] + fn test_big_list_of_naughty_strings() { + use std::mem::discriminant; + + let blns_contents = include_str!("./blns/blns.base64.json"); + let blns_base64: Vec = + serde_json::from_str(blns_contents).expect("BLNS json invalid"); + for blns_base64_str in blns_base64 { + let statements = blns_base64_to_statements(blns_base64_str); + for (token_discriminator_opt, blns_program_strs) in statements { + for blns_program_str in blns_program_strs { + let mut expected_token_found = false; + let mut lexer = Lexer::new(&blns_program_str); + let mut result_tokens = Vec::new(); + loop { + match lexer.next_token() { + Ok(next_token) => { + result_tokens.push(next_token.clone()); + expected_token_found |= token_discriminator_opt + .as_ref() + .map(|token_discriminator| { + discriminant(token_discriminator) + == discriminant(next_token.token()) + }) + .unwrap_or(true); + + if next_token == Token::EOF { + assert!(lexer.done, "lexer not done when EOF emitted!"); + break; + } + } + + Err(LexerErrorKind::InvalidIntegerLiteral { .. }) + | Err(LexerErrorKind::UnexpectedCharacter { .. }) + | Err(LexerErrorKind::UnterminatedBlockComment { .. }) => { + expected_token_found = true; + } + Err(err) => { + panic!("Unexpected lexer error found: {:?}", err) + } + } + } + + assert!( + expected_token_found, + "expected token not found: {token_discriminator_opt:?}\noutput:\n{result_tokens:?}", + ); + } + } + } + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs new file mode 100644 index 00000000000..3011c26cffe --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/errors.rs @@ -0,0 +1,39 @@ +use thiserror::Error; + +use noirc_errors::{CustomDiagnostic, FileDiagnostic, Location}; + +#[derive(Debug, Error)] +pub enum MonomorphizationError { + #[error("Length of generic array could not be determined.")] + UnknownArrayLength { location: Location }, + + #[error("Type annotations needed")] + TypeAnnotationsNeeded { location: Location }, +} + +impl MonomorphizationError { + fn location(&self) -> Location { + match self { + MonomorphizationError::UnknownArrayLength { location } + | MonomorphizationError::TypeAnnotationsNeeded { location } => *location, + } + } +} + +impl From for FileDiagnostic { + fn from(error: MonomorphizationError) -> FileDiagnostic { + let location = error.location(); + let call_stack = vec![location]; + let diagnostic = error.into_diagnostic(); + diagnostic.in_file(location.file).with_call_stack(call_stack) + } +} + +impl MonomorphizationError { + fn into_diagnostic(self) -> CustomDiagnostic { + let message = self.to_string(); + let location = self.location(); + + CustomDiagnostic::simple_error(message, String::new(), location.span) + } +} 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 c6ab681db6c..6aa0abce152 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -8,16 +8,6 @@ //! //! The entry point to this pass is the `monomorphize` function which, starting from a given //! function, will monomorphize the entire reachable program. -use acvm::FieldElement; -use iter_extended::{btree_map, try_vecmap, vecmap}; -use noirc_errors::{CustomDiagnostic, FileDiagnostic, Location}; -use noirc_printable_type::PrintableType; -use std::{ - collections::{BTreeMap, HashMap, VecDeque}, - unreachable, -}; -use thiserror::Error; - use crate::{ debug::DebugInstrumenter, hir_def::{ @@ -31,13 +21,25 @@ use crate::{ FunctionKind, IntegerBitSize, Signedness, Type, TypeBinding, TypeBindings, TypeVariable, TypeVariableKind, UnaryOp, Visibility, }; +use acvm::FieldElement; +use iter_extended::{btree_map, try_vecmap, vecmap}; +use noirc_errors::Location; +use noirc_printable_type::PrintableType; +use std::{ + collections::{BTreeMap, HashMap, VecDeque}, + unreachable, +}; -use self::ast::{Definition, FuncId, Function, LocalId, Program}; use self::debug_types::DebugTypeTracker; +use self::{ + ast::{Definition, FuncId, Function, LocalId, Program}, + errors::MonomorphizationError, +}; pub mod ast; mod debug; pub mod debug_types; +pub mod errors; pub mod printer; struct LambdaContext { @@ -88,40 +90,6 @@ struct Monomorphizer<'interner> { type HirType = crate::Type; -#[derive(Debug, Error)] -pub enum MonomorphizationError { - #[error("Length of generic array could not be determined.")] - UnknownArrayLength { location: Location }, -} - -impl MonomorphizationError { - fn call_stack(&self) -> Vec { - match self { - MonomorphizationError::UnknownArrayLength { location } => vec![*location], - } - } -} - -impl From for FileDiagnostic { - fn from(error: MonomorphizationError) -> FileDiagnostic { - let call_stack = error.call_stack(); - let file_id = call_stack.last().map(|location| location.file).unwrap_or_default(); - let diagnostic = error.into_diagnostic(); - diagnostic.in_file(file_id).with_call_stack(call_stack) - } -} - -impl MonomorphizationError { - fn into_diagnostic(self) -> CustomDiagnostic { - CustomDiagnostic::simple_error( - "Internal Consistency Evaluators Errors: \n - This is likely a bug. Consider opening an issue at https://github.com/noir-lang/noir/issues".to_owned(), - self.to_string(), - noirc_errors::Span::inclusive(0, 0) - ) - } -} - /// Starting from the given `main` function, monomorphize the entire program, /// replacing all references to type variables and NamedGenerics with concrete /// types, duplicating definitions as necessary to do so. @@ -323,15 +291,17 @@ impl<'interner> Monomorphizer<'interner> { let body_expr_id = *self.interner.function(&f).as_expr(); let body_return_type = self.interner.id_type(body_expr_id); - let return_type = self.convert_type(match meta.return_type() { + let return_type = match meta.return_type() { Type::TraitAsType(..) => &body_return_type, - _ => meta.return_type(), - }); + other => other, + }; + + let return_type = Self::convert_type(return_type, meta.location)?; let unconstrained = modifiers.is_unconstrained; let should_fold = meta.should_fold; - let parameters = self.parameters(&meta.parameters); + let parameters = self.parameters(&meta.parameters)?; let body = self.expr(body_expr_id)?; let function = ast::Function { id, @@ -355,12 +325,15 @@ impl<'interner> Monomorphizer<'interner> { /// Monomorphize each parameter, expanding tuple/struct patterns into multiple parameters /// and binding any generic types found. - fn parameters(&mut self, params: &Parameters) -> Vec<(ast::LocalId, bool, String, ast::Type)> { + fn parameters( + &mut self, + params: &Parameters, + ) -> Result, MonomorphizationError> { let mut new_params = Vec::with_capacity(params.len()); for (parameter, typ, _) in ¶ms.0 { - self.parameter(parameter, typ, &mut new_params); + self.parameter(parameter, typ, &mut new_params)?; } - new_params + Ok(new_params) } fn parameter( @@ -368,21 +341,22 @@ impl<'interner> Monomorphizer<'interner> { param: &HirPattern, typ: &HirType, new_params: &mut Vec<(ast::LocalId, bool, String, ast::Type)>, - ) { + ) -> Result<(), MonomorphizationError> { match param { HirPattern::Identifier(ident) => { let new_id = self.next_local_id(); let definition = self.interner.definition(ident.id); let name = definition.name.clone(); - new_params.push((new_id, definition.mutable, name, self.convert_type(typ))); + let typ = Self::convert_type(typ, ident.location)?; + new_params.push((new_id, definition.mutable, name, typ)); self.define_local(ident.id, new_id); } - HirPattern::Mutable(pattern, _) => self.parameter(pattern, typ, new_params), + HirPattern::Mutable(pattern, _) => self.parameter(pattern, typ, new_params)?, HirPattern::Tuple(fields, _) => { let tuple_field_types = unwrap_tuple_type(typ); for (field, typ) in fields.iter().zip(tuple_field_types) { - self.parameter(field, &typ, new_params); + self.parameter(field, &typ, new_params)?; } } HirPattern::Struct(_, fields, _) => { @@ -399,10 +373,11 @@ impl<'interner> Monomorphizer<'interner> { unreachable!("Expected a field named '{field_name}' in the struct pattern") }); - self.parameter(field, &field_type, new_params); + self.parameter(field, &field_type, new_params)?; } } } + Ok(()) } fn expr( @@ -425,9 +400,10 @@ impl<'interner> Monomorphizer<'interner> { } HirExpression::Literal(HirLiteral::Bool(value)) => Literal(Bool(value)), HirExpression::Literal(HirLiteral::Integer(value, sign)) => { + let location = self.interner.id_location(expr); + let typ = Self::convert_type(&self.interner.id_type(expr), location)?; + if sign { - let typ = self.convert_type(&self.interner.id_type(expr)); - let location = self.interner.id_location(expr); match typ { ast::Type::Field => Literal(Integer(-value, typ, location)), ast::Type::Integer(_, bit_size) => { @@ -438,8 +414,6 @@ impl<'interner> Monomorphizer<'interner> { _ => unreachable!("Integer literal must be numeric"), } } else { - let typ = self.convert_type(&self.interner.id_type(expr)); - let location = self.interner.id_location(expr); Literal(Integer(value, typ, location)) } } @@ -463,7 +437,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::Unary(ast::Unary { operator: prefix.operator, rhs: Box::new(self.expr(prefix.rhs)?), - result_type: self.convert_type(&self.interner.id_type(expr)), + result_type: Self::convert_type(&self.interner.id_type(expr), location)?, location, }) } @@ -492,8 +466,8 @@ impl<'interner> Monomorphizer<'interner> { let function_type = Type::Function(args, Box::new(ret.clone()), env); let method = infix.trait_method_id; - let func = self.resolve_trait_method_reference(expr, function_type, method); - self.create_operator_impl_call(func, lhs, infix.operator, rhs, ret, location) + let func = self.resolve_trait_method_reference(expr, function_type, method)?; + self.create_operator_impl_call(func, lhs, infix.operator, rhs, ret, location)? } else { let lhs = Box::new(lhs); let rhs = Box::new(rhs); @@ -511,23 +485,22 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Call(call) => self.function_call(call, expr)?, - HirExpression::Cast(cast) => ast::Expression::Cast(ast::Cast { - lhs: Box::new(self.expr(cast.lhs)?), - r#type: self.convert_type(&cast.r#type), - location: self.interner.expr_location(&expr), - }), + HirExpression::Cast(cast) => { + let location = self.interner.expr_location(&expr); + let typ = Self::convert_type(&cast.r#type, location)?; + let lhs = Box::new(self.expr(cast.lhs)?); + ast::Expression::Cast(ast::Cast { lhs, r#type: typ, location }) + } HirExpression::If(if_expr) => { - let cond = self.expr(if_expr.condition)?; - let then = self.expr(if_expr.consequence)?; + let condition = Box::new(self.expr(if_expr.condition)?); + let consequence = Box::new(self.expr(if_expr.consequence)?); let else_ = if_expr.alternative.map(|alt| self.expr(alt)).transpose()?.map(Box::new); - ast::Expression::If(ast::If { - condition: Box::new(cond), - consequence: Box::new(then), - alternative: else_, - typ: self.convert_type(&self.interner.id_type(expr)), - }) + + let location = self.interner.expr_location(&expr); + let typ = Self::convert_type(&self.interner.id_type(expr), location)?; + ast::Expression::If(ast::If { condition, consequence, alternative: else_, typ }) } HirExpression::Tuple(fields) => { @@ -554,7 +527,8 @@ impl<'interner> Monomorphizer<'interner> { array_elements: Vec, is_slice: bool, ) -> Result { - let typ = self.convert_type(&self.interner.id_type(array)); + let location = self.interner.expr_location(&array); + let typ = Self::convert_type(&self.interner.id_type(array), location)?; let contents = try_vecmap(array_elements, |id| self.expr(id))?; if is_slice { Ok(ast::Expression::Literal(ast::Literal::Slice(ast::ArrayLiteral { contents, typ }))) @@ -570,7 +544,8 @@ impl<'interner> Monomorphizer<'interner> { length: HirType, is_slice: bool, ) -> Result { - let typ = self.convert_type(&self.interner.id_type(array)); + let location = self.interner.expr_location(&array); + let typ = Self::convert_type(&self.interner.id_type(array), location)?; let length = length.evaluate_to_u64().ok_or_else(|| { let location = self.interner.expr_location(&array); @@ -590,7 +565,8 @@ impl<'interner> Monomorphizer<'interner> { id: node_interner::ExprId, index: HirIndexExpression, ) -> Result { - let element_type = self.convert_type(&self.interner.id_type(id)); + let location = self.interner.expr_location(&id); + let element_type = Self::convert_type(&self.interner.id_type(id), location)?; let collection = Box::new(self.expr(index.collection)?); let index = Box::new(self.expr(index.index)?); @@ -621,11 +597,14 @@ impl<'interner> Monomorphizer<'interner> { self.define_local(for_loop.identifier.id, index_variable); let block = Box::new(self.expr(for_loop.block)?); + let index_location = for_loop.identifier.location; + let index_type = self.interner.id_type(for_loop.start_range); + let index_type = Self::convert_type(&index_type, index_location)?; Ok(ast::Expression::For(ast::For { index_variable, index_name: self.interner.definition_name(for_loop.identifier.id).to_owned(), - index_type: self.convert_type(&self.interner.id_type(for_loop.start_range)), + index_type, start_range: Box::new(start), end_range: Box::new(end), start_range_location: self.interner.expr_location(&for_loop.start_range), @@ -649,7 +628,7 @@ impl<'interner> Monomorphizer<'interner> { ) -> Result { let expr = self.expr(let_statement.expression)?; let expected_type = self.interner.id_type(let_statement.expression); - Ok(self.unpack_pattern(let_statement.pattern, expr, &expected_type)) + self.unpack_pattern(let_statement.pattern, expr, &expected_type) } fn constructor( @@ -670,7 +649,8 @@ impl<'interner> Monomorphizer<'interner> { for (field_name, expr_id) in constructor.fields { let new_id = self.next_local_id(); let field_type = field_type_map.get(&field_name.0.contents).unwrap(); - let typ = self.convert_type(field_type); + let location = self.interner.expr_location(&expr_id); + let typ = Self::convert_type(field_type, location)?; field_vars.insert(field_name.0.contents.clone(), (new_id, typ)); let expression = Box::new(self.expr(expr_id)?); @@ -714,19 +694,19 @@ impl<'interner> Monomorphizer<'interner> { pattern: HirPattern, value: ast::Expression, typ: &HirType, - ) -> ast::Expression { + ) -> Result { match pattern { HirPattern::Identifier(ident) => { let new_id = self.next_local_id(); self.define_local(ident.id, new_id); let definition = self.interner.definition(ident.id); - ast::Expression::Let(ast::Let { + Ok(ast::Expression::Let(ast::Let { id: new_id, mutable: definition.mutable, name: definition.name.clone(), expression: Box::new(value), - }) + })) } HirPattern::Mutable(pattern, _) => self.unpack_pattern(*pattern, value, typ), HirPattern::Tuple(patterns, _) => { @@ -755,7 +735,7 @@ impl<'interner> Monomorphizer<'interner> { &mut self, value: ast::Expression, fields: impl Iterator, - ) -> ast::Expression { + ) -> Result { let fresh_id = self.next_local_id(); let mut definitions = vec![ast::Expression::Let(ast::Let { @@ -766,21 +746,22 @@ impl<'interner> Monomorphizer<'interner> { })]; for (i, (field_pattern, field_type)) in fields.into_iter().enumerate() { - let location = None; + let location = field_pattern.location(); let mutable = false; let definition = Definition::Local(fresh_id); let name = i.to_string(); - let typ = self.convert_type(&field_type); + let typ = Self::convert_type(&field_type, location)?; + let location = Some(location); let new_rhs = ast::Expression::Ident(ast::Ident { location, mutable, definition, name, typ }); let new_rhs = ast::Expression::ExtractTupleField(Box::new(new_rhs), i); - let new_expr = self.unpack_pattern(field_pattern, new_rhs, &field_type); + let new_expr = self.unpack_pattern(field_pattern, new_rhs, &field_type)?; definitions.push(new_expr); } - ast::Expression::Block(definitions) + Ok(ast::Expression::Block(definitions)) } /// Find a captured variable in the innermost closure, and construct an expression @@ -806,15 +787,20 @@ impl<'interner> Monomorphizer<'interner> { } /// A local (ie non-global) ident only - fn local_ident(&mut self, ident: &HirIdent) -> Option { + fn local_ident( + &mut self, + ident: &HirIdent, + ) -> Result, MonomorphizationError> { let definition = self.interner.definition(ident.id); let name = definition.name.clone(); let mutable = definition.mutable; - let definition = self.lookup_local(ident.id)?; - let typ = self.convert_type(&self.interner.definition_type(ident.id)); + let Some(definition) = self.lookup_local(ident.id) else { + return Ok(None); + }; - Some(ast::Ident { location: Some(ident.location), mutable, definition, name, typ }) + let typ = Self::convert_type(&self.interner.definition_type(ident.id), ident.location)?; + Ok(Some(ast::Ident { location: Some(ident.location), mutable, definition, name, typ })) } fn ident( @@ -825,7 +811,7 @@ impl<'interner> Monomorphizer<'interner> { let typ = self.interner.id_type(expr_id); if let ImplKind::TraitMethod(method, _, _) = ident.impl_kind { - return Ok(self.resolve_trait_method_reference(expr_id, typ, method)); + return self.resolve_trait_method_reference(expr_id, typ, method); } let definition = self.interner.definition(ident.id); @@ -835,7 +821,7 @@ impl<'interner> Monomorphizer<'interner> { let location = Some(ident.location); let name = definition.name.clone(); let definition = self.lookup_function(*func_id, expr_id, &typ, None); - let typ = self.convert_type(&typ); + let typ = Self::convert_type(&typ, ident.location)?; let ident = ast::Ident { location, mutable, definition, name, typ: typ.clone() }; let ident_expression = ast::Expression::Ident(ident); if self.is_function_closure_type(&typ) { @@ -858,10 +844,13 @@ impl<'interner> Monomorphizer<'interner> { }; self.expr(let_.expression)? } - DefinitionKind::Local(_) => self.lookup_captured_expr(ident.id).unwrap_or_else(|| { - let ident = self.local_ident(&ident).unwrap(); - ast::Expression::Ident(ident) - }), + DefinitionKind::Local(_) => match self.lookup_captured_expr(ident.id) { + Some(expr) => expr, + None => { + let ident = self.local_ident(&ident)?.unwrap(); + ast::Expression::Ident(ident) + } + }, DefinitionKind::GenericType(type_variable) => { let value = match &*type_variable.borrow() { TypeBinding::Unbound(_) => { @@ -874,7 +863,7 @@ impl<'interner> Monomorphizer<'interner> { let value = FieldElement::from(value as u128); let location = self.interner.id_location(expr_id); - let typ = self.convert_type(&typ); + let typ = Self::convert_type(&typ, ident.location)?; ast::Expression::Literal(ast::Literal::Integer(value, typ, location)) } }; @@ -883,26 +872,28 @@ impl<'interner> Monomorphizer<'interner> { } /// Convert a non-tuple/struct type to a monomorphized type - fn convert_type(&self, typ: &HirType) -> ast::Type { - match typ { + fn convert_type(typ: &HirType, location: Location) -> Result { + Ok(match typ { HirType::FieldElement => ast::Type::Field, HirType::Integer(sign, bits) => ast::Type::Integer(*sign, *bits), HirType::Bool => ast::Type::Bool, HirType::String(size) => ast::Type::String(size.evaluate_to_u64().unwrap_or(0)), HirType::FmtString(size, fields) => { let size = size.evaluate_to_u64().unwrap_or(0); - let fields = Box::new(self.convert_type(fields.as_ref())); + let fields = Box::new(Self::convert_type(fields.as_ref(), location)?); ast::Type::FmtString(size, fields) } HirType::Unit => ast::Type::Unit, HirType::Array(length, element) => { - let element = Box::new(self.convert_type(element.as_ref())); - // TODO: convert to MonomorphizationError - let length = length.evaluate_to_u64().unwrap_or(0); + let element = Box::new(Self::convert_type(element.as_ref(), location)?); + let length = match length.evaluate_to_u64() { + Some(length) => length, + None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }), + }; ast::Type::Array(length, element) } HirType::Slice(element) => { - let element = Box::new(self.convert_type(element.as_ref())); + let element = Box::new(Self::convert_type(element.as_ref(), location)?); ast::Type::Slice(element) } HirType::TraitAsType(..) => { @@ -910,55 +901,53 @@ impl<'interner> Monomorphizer<'interner> { } HirType::NamedGeneric(binding, _) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { - return self.convert_type(binding); + return Self::convert_type(binding, location); } // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. - binding.bind(HirType::default_int_type()); + binding.bind(HirType::default_int_or_field_type()); ast::Type::Field } HirType::TypeVariable(binding, kind) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { - return self.convert_type(binding); + return Self::convert_type(binding, location); } // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. - let default = if self.is_range_loop - && (matches!(kind, TypeVariableKind::IntegerOrField) - || matches!(kind, TypeVariableKind::Integer)) - { - Type::default_range_loop_type() - } else { - kind.default_type() + let default = match kind.default_type() { + Some(typ) => typ, + None => return Err(MonomorphizationError::TypeAnnotationsNeeded { location }), }; - let monomorphized_default = self.convert_type(&default); + let monomorphized_default = Self::convert_type(&default, location)?; binding.bind(default); monomorphized_default } HirType::Struct(def, args) => { let fields = def.borrow().get_fields(args); - let fields = vecmap(fields, |(_, field)| self.convert_type(&field)); + let fields = try_vecmap(fields, |(_, field)| Self::convert_type(&field, location))?; ast::Type::Tuple(fields) } - HirType::Alias(def, args) => self.convert_type(&def.borrow().get_type(args)), + HirType::Alias(def, args) => { + Self::convert_type(&def.borrow().get_type(args), location)? + } HirType::Tuple(fields) => { - let fields = vecmap(fields, |x| self.convert_type(x)); + let fields = try_vecmap(fields, |x| Self::convert_type(x, location))?; ast::Type::Tuple(fields) } HirType::Function(args, ret, env) => { - let args = vecmap(args, |x| self.convert_type(x)); - let ret = Box::new(self.convert_type(ret)); - let env = self.convert_type(env); + let args = try_vecmap(args, |x| Self::convert_type(x, location))?; + let ret = Box::new(Self::convert_type(ret, location)?); + let env = Self::convert_type(env, location)?; match &env { ast::Type::Unit => ast::Type::Function(args, ret, Box::new(env)), ast::Type::Tuple(_elements) => ast::Type::Tuple(vec![ @@ -974,7 +963,7 @@ impl<'interner> Monomorphizer<'interner> { } HirType::MutableReference(element) => { - let element = self.convert_type(element); + let element = Self::convert_type(element, location)?; ast::Type::MutableReference(Box::new(element)) } @@ -982,7 +971,7 @@ impl<'interner> Monomorphizer<'interner> { unreachable!("Unexpected type {} found", typ) } HirType::Code => unreachable!("Tried to translate Code type into runtime code"), - } + }) } fn is_function_closure(&self, t: ast::Type) -> bool { @@ -1013,7 +1002,7 @@ impl<'interner> Monomorphizer<'interner> { expr_id: node_interner::ExprId, function_type: HirType, method: TraitMethodId, - ) -> ast::Expression { + ) -> Result { let trait_impl = self .interner .get_selected_impl_for_expression(expr_id) @@ -1040,7 +1029,12 @@ impl<'interner> Monomorphizer<'interner> { Err(constraints) => { let failed_constraints = vecmap(constraints, |constraint| { let id = constraint.trait_id; - let name = self.interner.get_trait(id).name.to_string(); + let mut name = self.interner.get_trait(id).name.to_string(); + if !constraint.trait_generics.is_empty() { + let types = + vecmap(&constraint.trait_generics, |t| format!("{t:?}")); + name += &format!("<{}>", types.join(", ")); + } format!(" {}: {name}", constraint.typ) }) .join("\n"); @@ -1057,13 +1051,15 @@ impl<'interner> Monomorphizer<'interner> { }; let the_trait = self.interner.get_trait(method.trait_id); - ast::Expression::Ident(ast::Ident { + let location = self.interner.expr_location(&expr_id); + + Ok(ast::Expression::Ident(ast::Ident { definition: Definition::Function(func_id), mutable: false, location: None, name: the_trait.methods[method.method_index].name.0.contents.clone(), - typ: self.convert_type(&function_type), - }) + typ: Self::convert_type(&function_type, location)?, + })) } fn function_call( @@ -1078,7 +1074,8 @@ impl<'interner> Monomorphizer<'interner> { self.patch_debug_instrumentation_call(&call, &mut arguments)?; let return_type = self.interner.id_type(id); - let return_type = self.convert_type(&return_type); + let location = self.interner.expr_location(&id); + let return_type = Self::convert_type(&return_type, location)?; let location = call.location; @@ -1098,7 +1095,7 @@ impl<'interner> Monomorphizer<'interner> { let mut block_expressions = vec![]; let func_type = self.interner.id_type(call.func); - let func_type = self.convert_type(&func_type); + let func_type = Self::convert_type(&func_type, location)?; let is_closure = self.is_function_closure(func_type); let func = if is_closure { @@ -1120,7 +1117,7 @@ impl<'interner> Monomorphizer<'interner> { definition: Definition::Local(local_id), mutable: false, name: "tmp".to_string(), - typ: self.convert_type(&self.interner.id_type(call.func)), + typ: Self::convert_type(&self.interner.id_type(call.func), location)?, }); let env_argument = @@ -1319,24 +1316,24 @@ impl<'interner> Monomorphizer<'interner> { fn lvalue(&mut self, lvalue: HirLValue) -> Result { let value = match lvalue { - HirLValue::Ident(ident, _) => self - .lookup_captured_lvalue(ident.id) - .unwrap_or_else(|| ast::LValue::Ident(self.local_ident(&ident).unwrap())), + HirLValue::Ident(ident, _) => match self.lookup_captured_lvalue(ident.id) { + Some(value) => value, + None => ast::LValue::Ident(self.local_ident(&ident)?.unwrap()), + }, HirLValue::MemberAccess { object, field_index, .. } => { let field_index = field_index.unwrap(); let object = Box::new(self.lvalue(*object)?); ast::LValue::MemberAccess { object, field_index } } - HirLValue::Index { array, index, typ } => { - let location = self.interner.expr_location(&index); + HirLValue::Index { array, index, typ, location } => { let array = Box::new(self.lvalue(*array)?); let index = Box::new(self.expr(index)?); - let element_type = self.convert_type(&typ); + let element_type = Self::convert_type(&typ, location)?; ast::LValue::Index { array, index, element_type, location } } - HirLValue::Dereference { lvalue, element_type } => { + HirLValue::Dereference { lvalue, element_type, location } => { let reference = Box::new(self.lvalue(*lvalue)?); - let element_type = self.convert_type(&element_type); + let element_type = Self::convert_type(&element_type, location)?; ast::LValue::Dereference { reference, element_type } } }; @@ -1350,7 +1347,7 @@ impl<'interner> Monomorphizer<'interner> { expr: node_interner::ExprId, ) -> Result { if lambda.captures.is_empty() { - self.lambda_no_capture(lambda) + self.lambda_no_capture(lambda, expr) } else { let (setup, closure_variable) = self.lambda_with_setup(lambda, expr)?; Ok(ast::Expression::Block(vec![setup, closure_variable])) @@ -1360,16 +1357,19 @@ impl<'interner> Monomorphizer<'interner> { fn lambda_no_capture( &mut self, lambda: HirLambda, + expr: node_interner::ExprId, ) -> Result { - let ret_type = self.convert_type(&lambda.return_type); + let location = self.interner.expr_location(&expr); + let ret_type = Self::convert_type(&lambda.return_type, location)?; let lambda_name = "lambda"; - let parameter_types = vecmap(&lambda.parameters, |(_, typ)| self.convert_type(typ)); + let parameter_types = + try_vecmap(&lambda.parameters, |(_, typ)| Self::convert_type(typ, location))?; // Manually convert to Parameters type so we can reuse the self.parameters method let parameters = vecmap(lambda.parameters, |(pattern, typ)| (pattern, typ, Visibility::Private)).into(); - let parameters = self.parameters(¶meters); + let parameters = self.parameters(¶meters)?; let body = self.expr(lambda.body)?; let id = self.next_function_id(); @@ -1421,15 +1421,17 @@ impl<'interner> Monomorphizer<'interner> { // patterns in the resulting tree, // which seems more fragile, we directly reuse the return parameters // of this function in those cases - let ret_type = self.convert_type(&lambda.return_type); + let location = self.interner.expr_location(&expr); + let ret_type = Self::convert_type(&lambda.return_type, location)?; let lambda_name = "lambda"; - let parameter_types = vecmap(&lambda.parameters, |(_, typ)| self.convert_type(typ)); + let parameter_types = + try_vecmap(&lambda.parameters, |(_, typ)| Self::convert_type(typ, location))?; // Manually convert to Parameters type so we can reuse the self.parameters method let parameters = vecmap(lambda.parameters, |(pattern, typ)| (pattern, typ, Visibility::Private)).into(); - let mut converted_parameters = self.parameters(¶meters); + let mut converted_parameters = self.parameters(¶meters)?; let id = self.next_function_id(); let name = lambda_name.to_owned(); @@ -1437,26 +1439,27 @@ impl<'interner> Monomorphizer<'interner> { let env_local_id = self.next_local_id(); let env_name = "env"; - let env_tuple = ast::Expression::Tuple(vecmap(&lambda.captures, |capture| { - match capture.transitive_capture_index { - Some(field_index) => match self.lambda_envs_stack.last() { - Some(lambda_ctx) => ast::Expression::ExtractTupleField( - Box::new(ast::Expression::Ident(lambda_ctx.env_ident.clone())), - field_index, - ), - None => unreachable!( - "Expected to find a parent closure environment, but found none" - ), - }, - None => { - let ident = self.local_ident(&capture.ident).unwrap(); - ast::Expression::Ident(ident) + let env_tuple = + ast::Expression::Tuple(try_vecmap(&lambda.captures, |capture| { + match capture.transitive_capture_index { + Some(field_index) => { + let lambda_ctx = self.lambda_envs_stack.last().expect( + "Expected to find a parent closure environment, but found none", + ); + + let ident = Box::new(ast::Expression::Ident(lambda_ctx.env_ident.clone())); + Ok(ast::Expression::ExtractTupleField(ident, field_index)) + } + None => { + let ident = self.local_ident(&capture.ident)?.unwrap(); + Ok(ast::Expression::Ident(ident)) + } } - } - })); + })?); + let expr_type = self.interner.id_type(expr); let env_typ = if let types::Type::Function(_, _, function_env_type) = expr_type { - self.convert_type(&function_env_type) + Self::convert_type(&function_env_type, location)? } else { unreachable!("expected a Function type for a Lambda node") }; @@ -1665,10 +1668,10 @@ impl<'interner> Monomorphizer<'interner> { rhs: ast::Expression, ret: Type, location: Location, - ) -> ast::Expression { + ) -> Result { let arguments = vec![lhs, rhs]; let func = Box::new(func); - let return_type = self.convert_type(&ret); + let return_type = Self::convert_type(&ret, location)?; let mut result = ast::Expression::Call(ast::Call { func, arguments, return_type, location }); @@ -1713,7 +1716,7 @@ impl<'interner> Monomorphizer<'interner> { _ => (), } - result + Ok(result) } /// Call sites are instantiated against the trait method, but when an impl is later selected, 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 09e0cb04d26..dcfceccdb57 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -1079,6 +1079,7 @@ impl NodeInterner { /// constraint, but when where clauses are involved, the failing constraint may be several /// constraints deep. In this case, all of the constraints are returned, starting with the /// failing one. + /// If this list of failing constraints is empty, this means type annotations are required. pub fn lookup_trait_implementation( &self, object_type: &Type, @@ -1121,6 +1122,10 @@ impl NodeInterner { } /// Similar to `lookup_trait_implementation` but does not apply any type bindings on success. + /// On error returns either: + /// - 1+ failing trait constraints, including the original. + /// Each constraint after the first represents a `where` clause that was followed. + /// - 0 trait constraints indicating type annotations are needed to choose an impl. pub fn try_lookup_trait_implementation( &self, object_type: &Type, @@ -1138,6 +1143,11 @@ impl NodeInterner { Ok((impl_kind, bindings)) } + /// Returns the trait implementation if found. + /// On error returns either: + /// - 1+ failing trait constraints, including the original. + /// Each constraint after the first represents a `where` clause that was followed. + /// - 0 trait constraints indicating type annotations are needed to choose an impl. fn lookup_trait_implementation_helper( &self, object_type: &Type, @@ -1156,15 +1166,22 @@ impl NodeInterner { let object_type = object_type.substitute(type_bindings); + // If the object type isn't known, just return an error saying type annotations are needed. + if object_type.is_bindable() { + return Err(Vec::new()); + } + let impls = self.trait_implementation_map.get(&trait_id).ok_or_else(|| vec![make_constraint()])?; + let mut matching_impls = Vec::new(); + for (existing_object_type2, impl_kind) in impls { // Bug: We're instantiating only the object type's generics here, not all of the trait's generics like we need to let (existing_object_type, instantiation_bindings) = existing_object_type2.instantiate(self); - let mut fresh_bindings = TypeBindings::new(); + let mut fresh_bindings = type_bindings.clone(); let mut check_trait_generics = |impl_generics: &[Type]| { trait_generics.iter().zip(impl_generics).all(|(trait_generic, impl_generic2)| { @@ -1189,16 +1206,13 @@ impl NodeInterner { } if object_type.try_unify(&existing_object_type, &mut fresh_bindings).is_ok() { - // The unification was successful so we can append fresh_bindings to our bindings list - type_bindings.extend(fresh_bindings); - if let TraitImplKind::Normal(impl_id) = impl_kind { let trait_impl = self.get_trait_implementation(*impl_id); let trait_impl = trait_impl.borrow(); if let Err(mut errors) = self.validate_where_clause( &trait_impl.where_clause, - type_bindings, + &mut fresh_bindings, &instantiation_bindings, recursion_limit, ) { @@ -1207,11 +1221,20 @@ impl NodeInterner { } } - return Ok(impl_kind.clone()); + matching_impls.push((impl_kind.clone(), fresh_bindings)); } } - Err(vec![make_constraint()]) + if matching_impls.len() == 1 { + let (impl_, fresh_bindings) = matching_impls.pop().unwrap(); + *type_bindings = fresh_bindings; + Ok(impl_) + } else if matching_impls.is_empty() { + Err(vec![make_constraint()]) + } else { + // multiple matching impls, type annotations needed + Err(vec![]) + } } /// Verifies that each constraint in the given where clause is valid. @@ -1227,12 +1250,11 @@ impl NodeInterner { // Instantiation bindings are generally safe to force substitute into the same type. // This is needed here to undo any bindings done to trait methods by monomorphization. // Otherwise, an impl for (A, B) could get narrowed to only an impl for e.g. (u8, u16). - let constraint_type = constraint.typ.force_substitute(instantiation_bindings); - let constraint_type = constraint_type.substitute(type_bindings); + let constraint_type = + constraint.typ.force_substitute(instantiation_bindings).substitute(type_bindings); let trait_generics = vecmap(&constraint.trait_generics, |generic| { - let generic = generic.force_substitute(instantiation_bindings); - generic.substitute(type_bindings) + generic.force_substitute(instantiation_bindings).substitute(type_bindings) }); self.lookup_trait_implementation_helper( @@ -1241,10 +1263,11 @@ impl NodeInterner { &trait_generics, // Use a fresh set of type bindings here since the constraint_type originates from // our impl list, which we don't want to bind to. - &mut TypeBindings::new(), + type_bindings, recursion_limit - 1, )?; } + Ok(()) } 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 a40355be8aa..cdfa16400ae 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -526,8 +526,8 @@ fn assign_operator() -> impl NoirParser { } enum LValueRhs { - MemberAccess(Ident), - Index(Expression), + MemberAccess(Ident, Span), + Index(Expression, Span), } fn lvalue<'a, P>(expr_parser: P) -> impl NoirParser + 'a @@ -539,23 +539,28 @@ where let dereferences = just(Token::Star) .ignore_then(lvalue.clone()) - .map(|lvalue| LValue::Dereference(Box::new(lvalue))); + .map_with_span(|lvalue, span| LValue::Dereference(Box::new(lvalue), span)); let parenthesized = lvalue.delimited_by(just(Token::LeftParen), just(Token::RightParen)); let term = choice((parenthesized, dereferences, l_ident)); - let l_member_rhs = just(Token::Dot).ignore_then(field_name()).map(LValueRhs::MemberAccess); + let l_member_rhs = + just(Token::Dot).ignore_then(field_name()).map_with_span(LValueRhs::MemberAccess); let l_index = expr_parser .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map(LValueRhs::Index); + .map_with_span(LValueRhs::Index); term.then(l_member_rhs.or(l_index).repeated()).foldl(|lvalue, rhs| match rhs { - LValueRhs::MemberAccess(field_name) => { - LValue::MemberAccess { object: Box::new(lvalue), field_name } + LValueRhs::MemberAccess(field_name, span) => { + let span = lvalue.span().merge(span); + LValue::MemberAccess { object: Box::new(lvalue), field_name, span } + } + LValueRhs::Index(index, span) => { + let span = lvalue.span().merge(span); + LValue::Index { array: Box::new(lvalue), index, span } } - LValueRhs::Index(index) => LValue::Index { array: Box::new(lvalue), index }, }) }) } @@ -1237,7 +1242,7 @@ where mod test { use super::test_helpers::*; use super::*; - use crate::{ArrayLiteral, Literal}; + use crate::ArrayLiteral; #[test] fn parse_infix() { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs index 32f4f03de2e..83d7b832d27 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs @@ -105,6 +105,7 @@ mod test { Case { source: r#" r#"foo" "#, expect: "(none)", errors: 2 }, // empty string Case { source: r#"r"""#, expect: r#"r"""#, errors: 0 }, + #[allow(clippy::needless_raw_string_hashes)] Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, // miscellaneous Case { source: r##" r#\"foo\"# "##, expect: "plain::r", errors: 2 }, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index 6f92cb52a88..c4f0a8d67ba 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -1033,19 +1033,19 @@ mod test { fn resolve_complex_closures() { let src = r#" fn main(x: Field) -> pub Field { - let closure_without_captures = |x| x + x; + let closure_without_captures = |x: Field| -> Field { x + x }; let a = closure_without_captures(1); - let closure_capturing_a_param = |y| y + x; + let closure_capturing_a_param = |y: Field| -> Field { y + x }; let b = closure_capturing_a_param(2); - let closure_capturing_a_local_var = |y| y + b; + let closure_capturing_a_local_var = |y: Field| -> Field { y + b }; let c = closure_capturing_a_local_var(3); - let closure_with_transitive_captures = |y| { + let closure_with_transitive_captures = |y: Field| -> Field { let d = 5; - let nested_closure = |z| { - let doubly_nested_closure = |w| w + x + b; + let nested_closure = |z: Field| -> Field { + let doubly_nested_closure = |w: Field| -> Field { w + x + b }; a + z + y + d + x + doubly_nested_closure(4) + x + y }; let res = nested_closure(5); diff --git a/noir/noir-repo/compiler/wasm/README.md b/noir/noir-repo/compiler/wasm/README.md index 52f7e83e19e..fd534722622 100644 --- a/noir/noir-repo/compiler/wasm/README.md +++ b/noir/noir-repo/compiler/wasm/README.md @@ -26,17 +26,3 @@ for (const path of files) { } const myCompiledCode = await compile(fm); ``` - -## Building from source - -Outside of the [noir repo](https://github.com/noir-lang/noir), this package can be built using the command below: - -```bash -nix build -L github:noir-lang/noir/master#noir_wasm -``` - -If you are within the noir repo and would like to build local changes, you can use: - -```bash -nix build -L #noir_wasm -``` diff --git a/noir/noir-repo/cspell.json b/noir/noir-repo/cspell.json index 8cfc5e695a2..16de9757fb8 100644 --- a/noir/noir-repo/cspell.json +++ b/noir/noir-repo/cspell.json @@ -6,6 +6,7 @@ "words": [ "aarch", "acir", + "acirs", "acvm", "aeiou", "appender", @@ -36,6 +37,7 @@ "castable", "catmcgee", "Celo", + "chrono", "chumsky", "codegen", "codegenned", diff --git a/noir/noir-repo/default.nix b/noir/noir-repo/default.nix deleted file mode 100644 index 9e230590a61..00000000000 --- a/noir/noir-repo/default.nix +++ /dev/null @@ -1,13 +0,0 @@ -let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - flakeCompatRev = lock.nodes.flake-compat.locked.rev; - flakeCompatHash = lock.nodes.flake-compat.locked.narHash; - flakeCompat = fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${flakeCompatRev}.tar.gz"; - sha256 = flakeCompatHash; - }; - compat = import flakeCompat { - src = ./.; - }; -in -compat.defaultNix diff --git a/noir/noir-repo/docs/docs/getting_started/installation/other_install_methods.md b/noir/noir-repo/docs/docs/getting_started/installation/other_install_methods.md index a35e34aaf9c..3634723562b 100644 --- a/noir/noir-repo/docs/docs/getting_started/installation/other_install_methods.md +++ b/noir/noir-repo/docs/docs/getting_started/installation/other_install_methods.md @@ -1,6 +1,6 @@ --- -title: Alternative Install Methods -description: There are different ways to install Nargo, the one-stop shop and command-line tool for developing Noir programs. This guide explains other methods that don't rely on noirup, such as compiling from source, installing from binaries, and using WSL for windows +title: Alternative Installations +description: There are different ways to install Nargo, the one-stop shop and command-line tool for developing Noir programs. This guide explains how to specify which version to install when using noirup, and using WSL for windows. keywords: [ Installation Nargo @@ -12,10 +12,7 @@ keywords: [ Linux Nix Direnv - Shell & editor experience - Building and testing Uninstalling Nargo - Noir vs code extension, ] sidebar_position: 1 --- @@ -86,146 +83,7 @@ With `noirup`, you can easily switch between different Nargo versions, including noirup --path ./path/to/local/source ``` -## Alternate Installation Methods (No Longer Recommended) - -While the following methods are available, they are no longer recommended. We advise using noirup for a more efficient and flexible installation experience. - -However, there are other methods for installing Nargo: - -- [Binaries](#option-1-installing-from-binaries) -- [Compiling from Source](#option-2-compile-from-source) -- [WSL for Windows](#option-3-wsl-for-windows) - -### Option 1: Installing from Binaries - -See [GitHub Releases](https://github.com/noir-lang/noir/releases) for the latest and previous -platform specific binaries. - -#### Step 1 - -Paste and run the following in the terminal to extract and install the binary: - -> **macOS / Linux:** If you are prompted with `Permission denied` when running commands, prepend -> `sudo` and re-run it. - -##### macOS (Apple Silicon) - -```bash -mkdir -p $HOME/.nargo/bin && \ -curl -o $HOME/.nargo/bin/nargo-aarch64-apple-darwin.tar.gz -L https://github.com/noir-lang/noir/releases/download/v0.24.0/nargo-aarch64-apple-darwin.tar.gz && \ -tar -xvf $HOME/.nargo/bin/nargo-aarch64-apple-darwin.tar.gz -C $HOME/.nargo/bin/ && \ -echo '\nexport PATH=$PATH:$HOME/.nargo/bin' >> ~/.zshrc && \ -source ~/.zshrc -``` - -##### macOS (Intel) - -```bash -mkdir -p $HOME/.nargo/bin && \ -curl -o $HOME/.nargo/bin/nargo-x86_64-apple-darwin.tar.gz -L https://github.com/noir-lang/noir/releases/download/v0.24.0/nargo-x86_64-apple-darwin.tar.gz && \ -tar -xvf $HOME/.nargo/bin/nargo-x86_64-apple-darwin.tar.gz -C $HOME/.nargo/bin/ && \ -echo '\nexport PATH=$PATH:$HOME/.nargo/bin' >> ~/.zshrc && \ -source ~/.zshrc -``` - -##### Linux (Bash) - -```bash -mkdir -p $HOME/.nargo/bin && \ -curl -o $HOME/.nargo/bin/nargo-x86_64-unknown-linux-gnu.tar.gz -L https://github.com/noir-lang/noir/releases/download/v0.24.0/nargo-x86_64-unknown-linux-gnu.tar.gz && \ -tar -xvf $HOME/.nargo/bin/nargo-x86_64-unknown-linux-gnu.tar.gz -C $HOME/.nargo/bin/ && \ -echo -e '\nexport PATH=$PATH:$HOME/.nargo/bin' >> ~/.bashrc && \ -source ~/.bashrc -``` - -#### Step 2 - -Check if the installation was successful by running `nargo --version`. You should get a version number. - -> **macOS:** If you are prompted with an OS alert, right-click and open the _nargo_ executable from -> Finder. Close the new terminal popped up and `nargo` should now be accessible. - -### Option 2: Compile from Source - -Due to the large number of native dependencies, Noir projects uses [Nix](https://nixos.org/) and [direnv](https://direnv.net/) to streamline the development experience. It helps mitigating issues commonly associated with dependency management, such as conflicts between required package versions for different projects (often referred to as "dependency hell"). - -Combined with direnv, which automatically sets or clears environment variables based on the directory, it further simplifies the development process by seamlessly integrating with the developer's shell, facilitating an efficient and reliable workflow for managing and deploying Noir projects with multiple dependencies. - -#### Setting up your environment - -For the best experience, please follow these instructions to setup your environment: - -1. Install Nix following [their guide](https://nixos.org/download.html) for your operating system. -2. Create the file `~/.config/nix/nix.conf` with the contents: - -```ini -experimental-features = nix-command -extra-experimental-features = flakes -``` - -3. Install direnv into your Nix profile by running: - -```sh -nix profile install nixpkgs#direnv -``` - -4. Add direnv to your shell following [their guide](https://direnv.net/docs/hook.html). - 1. For bash or zshell, add `eval "$(direnv hook bash)"` or `eval "$(direnv hook zsh)"` to your ~/.bashrc or ~/.zshrc file, respectively. -5. Restart your shell. - -#### Shell & editor experience - -Now that your environment is set up, you can get to work on the project. - -1. Clone the repository, such as: - -```sh -git clone git@github.com:noir-lang/noir -``` - -> Replacing `noir` with whichever repository you want to work on. - -2. Navigate to the directory: - -```sh -cd noir -``` - -> Replacing `noir` with whichever repository you cloned. - -3. You should see a **direnv error** because projects aren't allowed by default. Make sure you've reviewed and trust our `.envrc` file, then you need to run: - -```sh -direnv allow -``` - -4. Now, wait awhile for all the native dependencies to be built. This will take some time and direnv will warn you that it is taking a long time, but we just need to let it run. - -5. Once you are presented with your prompt again, you can start your editor within the project directory (we recommend [VSCode](https://code.visualstudio.com/)): - -```sh -code . -``` - -6. (Recommended) When launching VSCode for the first time, you should be prompted to install our recommended plugins. We highly recommend installing these for the best development experience. - -#### Building and testing - -Assuming you are using `direnv` to populate your environment, building and testing the project can be done -with the typical `cargo build`, `cargo test`, and `cargo clippy` commands. You'll notice that the `cargo` version matches the version we specify in `rust-toolchain.toml`, which is 1.73.0 at the time of this writing. - -If you want to build the entire project in an isolated sandbox, you can use Nix commands: - -1. `nix build .` (or `nix build . -L` for verbose output) to build the project in a Nix sandbox. -2. `nix flake check` (or `nix flake check -L` for verbose output) to run clippy and tests in a Nix sandbox. - -#### Without `direnv` - -If you have hesitations with using direnv, you can launch a subshell with `nix develop` and then launch your editor from within the subshell. However, if VSCode was already launched in the project directory, the environment won't be updated. - -Advanced: If you aren't using direnv nor launching your editor within the subshell, you can try to install Barretenberg and other global dependencies the package needs. This is an advanced workflow and likely won't receive support! - -### Option 3: WSL (for Windows) +## Installation on Windows The default backend for Noir (Barretenberg) doesn't provide Windows binaries at this time. For that reason, Noir cannot be installed natively. However, it is available by using Windows Subsystem for Linux (WSL). @@ -235,20 +93,10 @@ step 2: Follow the [Noirup instructions](#encouraged-installation-method-noirup) ## Uninstalling Nargo -### Noirup - -If you installed Nargo with `noirup` or through directly downloading binaries, you can uninstall Nargo by removing the files in `~/.nargo`, `~/nargo`, and `~/noir_cache`. This ensures that all installed binaries, configurations, and cache related to Nargo are fully removed from your system. +If you installed Nargo with `noirup`, you can uninstall Nargo by removing the files in `~/.nargo`, `~/nargo`, and `~/noir_cache`. This ensures that all installed binaries, configurations, and cache related to Nargo are fully removed from your system. ```bash rm -r ~/.nargo rm -r ~/nargo rm -r ~/noir_cache ``` - -### Nix - -If you installed Nargo with Nix or compiled it from source, you can remove the binary located at `~/.nix-profile/bin/nargo`. - -```bash -rm ~/.nix-profile/bin/nargo -``` diff --git a/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md b/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md new file mode 100644 index 00000000000..d65151da0ab --- /dev/null +++ b/noir/noir-repo/docs/docs/getting_started/tooling/noir_codegen.md @@ -0,0 +1,113 @@ +--- +title: Noir Codegen for TypeScript +description: Learn how to use Noir codegen to generate TypeScript bindings +keywords: [Nargo, Noir, compile, TypeScript] +sidebar_position: 2 +--- + +When using TypeScript, it is extra work to interpret Noir program outputs in a type-safe way. Third party libraries may exist for popular Noir programs, but they are either hard to find or unmaintained. + +Now you can generate TypeScript bindings for your Noir programs in two steps: +1. Exporting Noir functions using `nargo export` +2. Using the TypeScript module `noir_codegen` to generate TypeScript binding + +**Note:** you can only export functions from a Noir *library* (not binary or contract program types). + +## Installation + +### Your TypeScript project + +If you don't already have a TypeScript project you can add the module with `yarn` (or `npm`), then initialize it: + +```bash +yarn add typescript -D +npx tsc --init +``` + +### Add TypeScript module - `noir_codegen` + +The following command will add the module to your project's devDependencies: + +```bash +yarn add @noir-lang/noir_codegen -D +``` + +### Nargo library +Make sure you have Nargo, v0.25.0 or greater, installed. If you don't, follow the [installation guide](../installation/index.md). + +If you're in a new project, make a `circuits` folder and create a new Noir library: + +```bash +mkdir circuits && cd circuits +nargo new --lib myNoirLib +``` + +## Usage + +### Export ABI of specified functions + +First go to the `.nr` files in your Noir library, and add the `#[export]` macro to each function that you want to use in TypeScript. + +```rust +#[export] +fn your_function(... +``` + +From your Noir library (where `Nargo.toml` is), run the following command: + +```bash +nargo export +``` + +You will now have an `export` directory with a .json file per exported function. + +You can also specify the directory of Noir programs using `--program-dir`, for example: + +```bash +nargo export --program-dir=./circuits/myNoirLib +``` + +### Generate TypeScript bindings from exported functions + +To use the `noir-codegen` package we added to the TypeScript project: + +```bash +yarn noir-codegen ./export/your_function.json +``` + +This creates an `exports` directory with an `index.ts` file containing all exported functions. + +**Note:** adding `--out-dir` allows you to specify an output dir for your TypeScript bindings to go. Eg: + +```bash +yarn noir-codegen ./export/*.json --out-dir ./path/to/output/dir +``` + +## Example .nr function to .ts output + +Consider a Noir library with this function: + +```rust +#[export] +fn not_equal(x: Field, y: Field) -> bool { + x != y +} +``` + +After the export and codegen steps, you should have an `index.ts` like: + +```typescript +export type Field = string; + + +export const is_equal_circuit: CompiledCircuit = {"abi":{"parameters":[{"name":"x","type":{"kind":"field"},"visibility":"private"},{"name":"y","type":{"kind":"field"},"visibility":"private"}],"param_witnesses":{"x":[{"start":0,"end":1}],"y":[{"start":1,"end":2}]},"return_type":{"abi_type":{"kind":"boolean"},"visibility":"private"},"return_witnesses":[4]},"bytecode":"H4sIAAAAAAAA/7WUMQ7DIAxFQ0Krrr2JjSGYLVcpKrn/CaqqDQN12WK+hPBgmWd/wEyHbF1SS923uhOs3pfoChI+wKXMAXzIKyNj4PB0TFTYc0w5RUjoqeAeEu1wqK0F54RGkWvW44LPzExnlkbMEs4JNZmN8PxS42uHv82T8a3Jeyn2Ks+VLPcO558HmyLMCDOXAXXtpPt4R/Rt9T36ss6dS9HGPx/eG17nGegKBQAA"}; + +export async function is_equal(x: Field, y: Field, foreignCallHandler?: ForeignCallHandler): Promise { + const program = new Noir(is_equal_circuit); + const args: InputMap = { x, y }; + const { returnValue } = await program.execute(args, foreignCallHandler); + return returnValue as boolean; +} +``` + +Now the `is_equal()` function and relevant types are readily available for use in TypeScript. diff --git a/noir/noir-repo/docs/package.json b/noir/noir-repo/docs/package.json index 78560707795..f9e95fc02f8 100644 --- a/noir/noir-repo/docs/package.json +++ b/noir/noir-repo/docs/package.json @@ -5,10 +5,11 @@ "scripts": { "preprocess": "yarn workspace @noir-lang/acvm_js build && ./scripts/codegen_nargo_reference.sh && yarn node ./scripts/preprocess/index.js", "start": "yarn preprocess && docusaurus start", - "build": "yarn preprocess && yarn version::stables && docusaurus build", + "build": "yarn preprocess && docusaurus build", + "clean": "rm -rf ./processed-docs ./processed-docs ./build", "version::stables": "ts-node ./scripts/setStable.ts", "serve": "serve build", - "version": "yarn preprocess && docusaurus build && docusaurus docs:version" + "version": "yarn version::stables && ./scripts/cut_version.sh" }, "dependencies": { "@docusaurus/core": "^3.0.1", diff --git a/noir/noir-repo/docs/scripts/cut_version.sh b/noir/noir-repo/docs/scripts/cut_version.sh new file mode 100755 index 00000000000..4000707328c --- /dev/null +++ b/noir/noir-repo/docs/scripts/cut_version.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eu + +cd $(dirname "$0")/.. + +VERSION=$1 + +# We assume that the new release tag has been made on github, so setStable.ts will add this to `versions.json`. +# We don't have a version of the docs for this release however (that's what we're doing right now!) so we need to remove it. +jq 'map(select(. != "'"$VERSION"'"))' versions.json > tmp.json && mv tmp.json versions.json + +# We need to build the docs in order to perform all necessary preprocessing. +yarn build + +# Finally cut the actual new docs version. +yarn docusaurus docs:version $VERSION diff --git a/noir/noir-repo/flake.nix b/noir/noir-repo/flake.nix deleted file mode 100644 index 1cb421a49ef..00000000000 --- a/noir/noir-repo/flake.nix +++ /dev/null @@ -1,260 +0,0 @@ -{ - description = "Build the Noir programming language"; - - # All of these inputs (a.k.a. dependencies) need to align with inputs we - # use so they use the `inputs.*.follows` syntax to reference our inputs - inputs = { - nixpkgs = { - url = "github:NixOS/nixpkgs/nixos-23.05"; - }; - - flake-utils = { - url = "github:numtide/flake-utils"; - }; - - flake-compat = { - url = "github:edolstra/flake-compat"; - flake = false; - }; - - fenix = { - url = "github:nix-community/fenix"; - inputs = { - nixpkgs.follows = "nixpkgs"; - }; - }; - - crane = { - url = "github:ipetkov/crane"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "flake-utils"; - flake-compat.follows = "flake-compat"; - }; - }; - }; - - outputs = - { self, nixpkgs, crane, flake-utils, fenix, ... }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = import nixpkgs { - inherit system; - }; - - rustToolchain = fenix.packages.${system}.fromToolchainFile { - file = ./rust-toolchain.toml; - sha256 = "sha256-rLP8+fTxnPHoR96ZJiCa/5Ans1OojI7MLsmSqR2ip8o="; - }; - - craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain; - - # The `self.rev` property is only available when the working tree is not dirty - GIT_COMMIT = if (self ? rev) then self.rev else "unknown"; - GIT_DIRTY = if (self ? rev) then "false" else "true"; - - extraBuildInputs = [ ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ - # Need libiconv and apple Security on Darwin. See https://github.com/ipetkov/crane/issues/156 - pkgs.libiconv - pkgs.darwin.apple_sdk.frameworks.Security - ]; - - environment = { - # We enable backtraces on any failure for help with debugging - RUST_BACKTRACE = "1"; - - # We download the Wasm version of `acvm_backend` in the barretenberg releases for the ACVM `blackbox_solver` - BARRETENBERG_ARCHIVE = pkgs.fetchurl { - url = "https://github.com/AztecProtocol/barretenberg/releases/download/barretenberg-v0.4.5/acvm_backend.wasm.tar.gz"; - sha256 = "sha256-xONt5pTKWf/YbVnX/NXl/VNBbtKd+CP7CLkB1jf0RHw="; - }; - }; - - # Configuration shared between builds - config = { - # x-release-please-start-version - version = "0.26.0"; - # x-release-please-end - - src = pkgs.lib.cleanSourceWith { - src = craneLib.path ./.; - # Custom filter with various file extensions that we rely upon to build packages - # Currently: `.nr`, `.sol`, `.sh`, `.json`, `.md` and `.wasm` - filter = path: type: - (builtins.match ".*\.(nr|sol|sh|json|md|wasm|txt)$" path != null) || (craneLib.filterCargoSources path type); - }; - - # TODO(#1198): It'd be nice to include these flags when running `cargo clippy` in a devShell. - cargoClippyExtraArgs = "--all-targets -- -D warnings"; - - # TODO(#1198): It'd be nice to include this flag when running `cargo test` in a devShell. - cargoTestExtraArgs = "--workspace"; - }; - - # Combine the environment and other configuration needed for Crane to build our Rust packages - nativeConfig = environment // config // { - nativeBuildInputs = [ ]; - - buildInputs = [ ] ++ extraBuildInputs; - }; - - # Combine the environmnet and other configuration needed for Crane to build our Wasm packages - wasmConfig = environment // config // { - CARGO_TARGET_DIR = "./target"; - - nativeBuildInputs = with pkgs; [ - which - git - jq - rustToolchain - wasm-bindgen-cli - binaryen - ]; - - buildInputs = [ ] ++ extraBuildInputs; - }; - - # Build *just* the cargo dependencies, so we can reuse all of that work between runs - native-cargo-artifacts = craneLib.buildDepsOnly (nativeConfig // { - pname = "nargo"; - }); - noirc-abi-wasm-cargo-artifacts = craneLib.buildDepsOnly (wasmConfig // { - pname = "noirc_abi_wasm"; - }); - acvm-js-cargo-artifacts = craneLib.buildDepsOnly (wasmConfig // { - pname = "acvm_js"; - }); - - nargo = craneLib.buildPackage (nativeConfig // { - pname = "nargo"; - - inherit GIT_COMMIT GIT_DIRTY; - - cargoArtifacts = native-cargo-artifacts; - - # We don't want to run tests because they don't work in the Nix sandbox - doCheck = false; - }); - - noirc_abi_wasm = craneLib.buildPackage (wasmConfig // rec { - pname = "noirc_abi_wasm"; - - inherit GIT_COMMIT GIT_DIRTY; - - cargoArtifacts = noirc-abi-wasm-cargo-artifacts; - - cargoExtraArgs = "--package ${pname} --target wasm32-unknown-unknown"; - - buildPhaseCargoCommand = '' - bash tooling/noirc_abi_wasm/buildPhaseCargoCommand.sh release - ''; - - installPhase = '' - bash tooling/noirc_abi_wasm/installPhase.sh - ''; - - # We don't want to run tests because they don't work in the Nix sandbox - doCheck = false; - }); - - acvm_js = craneLib.buildPackage (wasmConfig // rec { - pname = "acvm_js"; - - inherit GIT_COMMIT GIT_DIRTY; - - cargoArtifacts = acvm-js-cargo-artifacts; - - cargoExtraArgs = "--package ${pname} --target wasm32-unknown-unknown"; - - buildPhaseCargoCommand = '' - bash acvm-repo/acvm_js/buildPhaseCargoCommand.sh release - ''; - - installPhase = '' - bash acvm-repo/acvm_js/installPhase.sh - ''; - - # We don't want to run tests because they don't work in the Nix sandbox - doCheck = false; - }); - - wasm-bindgen-cli = pkgs.callPackage ./wasm-bindgen-cli.nix { - rustPlatform = pkgs.makeRustPlatform { - rustc = rustToolchain; - cargo = rustToolchain; - }; - }; - in - { - # We use `checks` to run `cargo clippy` and `cargo fmt` since we disable checks in the primary derivations - checks = { - cargo-clippy = craneLib.cargoClippy (nativeConfig // { - pname = "noir"; - - inherit GIT_COMMIT GIT_DIRTY; - - cargoArtifacts = native-cargo-artifacts; - }); - - cargo-fmt = craneLib.cargoFmt (nativeConfig // { - pname = "noir"; - - inherit GIT_COMMIT GIT_DIRTY; - - cargoArtifacts = native-cargo-artifacts; - }); - }; - - packages = { - default = nargo; - - # Nix flakes cannot build more than one derivation in one command (see https://github.com/NixOS/nix/issues/5591) - # so we use `symlinkJoin` to build everything as the "all" package. - all = pkgs.symlinkJoin { name = "all"; paths = [ nargo noirc_abi_wasm acvm_js ]; }; - all_wasm = pkgs.symlinkJoin { name = "all_wasm"; paths = [ noirc_abi_wasm acvm_js ]; }; - - # We also export individual packages to enable `nix build .#nargo -L`, etc. - inherit nargo; - inherit noirc_abi_wasm; - inherit acvm_js; - - # We expose the `*-cargo-artifacts` derivations so we can cache our cargo dependencies in CI - inherit native-cargo-artifacts; - inherit noirc-abi-wasm-cargo-artifacts; - inherit acvm-js-cargo-artifacts; - }; - - # Setup the environment to match the environment settings, the inputs from our checks derivations, - # and extra tooling via `nativeBuildInputs` - devShells.default = pkgs.mkShell (environment // { - inputsFrom = [ - nargo - noirc_abi_wasm - acvm_js - ]; - - # Additional tools that weren't included as `nativeBuildInputs` of any of the derivations in `inputsFrom` - nativeBuildInputs = with pkgs; [ - # Rust toolchain - rustToolchain - # Other tools - starship - yarn - nodejs-18_x - # Used by the `bb` binary - curl - gzip - # This ensures the right lldb is in the environment for running rust-lldb - llvmPackages.lldb - # Nix tools - nil - nixpkgs-fmt - ]; - - shellHook = '' - eval "$(starship init bash)" - ''; - }); - }); -} - diff --git a/noir/noir-repo/noir_stdlib/src/cmp.nr b/noir/noir-repo/noir_stdlib/src/cmp.nr index 0eed50eb42b..dde29d7ee87 100644 --- a/noir/noir-repo/noir_stdlib/src/cmp.nr +++ b/noir/noir-repo/noir_stdlib/src/cmp.nr @@ -6,10 +6,10 @@ trait Eq { impl Eq for Field { fn eq(self, other: Field) -> bool { self == other } } -impl Eq for u1 { fn eq(self, other: u1) -> bool { self == other } } -impl Eq for u8 { fn eq(self, other: u8) -> bool { self == other } } -impl Eq for u32 { fn eq(self, other: u32) -> bool { self == other } } impl Eq for u64 { fn eq(self, other: u64) -> bool { self == other } } +impl Eq for u32 { fn eq(self, other: u32) -> bool { self == other } } +impl Eq for u8 { fn eq(self, other: u8) -> bool { self == other } } +impl Eq for u1 { fn eq(self, other: u1) -> bool { self == other } } impl Eq for i8 { fn eq(self, other: i8) -> bool { self == other } } impl Eq for i32 { fn eq(self, other: i32) -> bool { self == other } } @@ -107,8 +107,8 @@ trait Ord { // Note: Field deliberately does not implement Ord -impl Ord for u8 { - fn cmp(self, other: u8) -> Ordering { +impl Ord for u64 { + fn cmp(self, other: u64) -> Ordering { if self < other { Ordering::less() } else if self > other { @@ -131,8 +131,8 @@ impl Ord for u32 { } } -impl Ord for u64 { - fn cmp(self, other: u64) -> Ordering { +impl Ord for u8 { + fn cmp(self, other: u8) -> Ordering { if self < other { Ordering::less() } else if self > other { diff --git a/noir/noir-repo/noir_stdlib/src/field/bn254.nr b/noir/noir-repo/noir_stdlib/src/field/bn254.nr index 765f8a9d849..d70310be391 100644 --- a/noir/noir-repo/noir_stdlib/src/field/bn254.nr +++ b/noir/noir-repo/noir_stdlib/src/field/bn254.nr @@ -21,11 +21,23 @@ unconstrained fn decompose_unsafe(x: Field) -> (Field, Field) { (low, high) } +// Assert that (alo > blo && ahi >= bhi) || (alo <= blo && ahi > bhi) +fn assert_gt_limbs(a: (Field, Field), b: (Field, Field)) { + let (alo, ahi) = a; + let (blo, bhi) = b; + let borrow = lte_unsafe(alo, blo, 16); + + let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; + let rhi = ahi - bhi - (borrow as Field); + + rlo.assert_max_bit_size(128); + rhi.assert_max_bit_size(128); +} + /// Decompose a single field into two 16 byte fields. pub fn decompose(x: Field) -> (Field, Field) { // Take hints of the decomposition let (xlo, xhi) = decompose_unsafe(x); - let borrow = lt_unsafe(PLO, xlo, 16); // Range check the limbs xlo.assert_max_bit_size(128); @@ -34,13 +46,8 @@ pub fn decompose(x: Field) -> (Field, Field) { // Check that the decomposition is correct assert_eq(x, xlo + TWO_POW_128 * xhi); - // Check that (xlo < plo && xhi <= phi) || (xlo >= plo && xhi < phi) - let rlo = PLO - xlo + (borrow as Field) * TWO_POW_128; - let rhi = PHI - xhi - (borrow as Field); - - rlo.assert_max_bit_size(128); - rhi.assert_max_bit_size(128); - + // Assert that the decomposition of P is greater than the decomposition of x + assert_gt_limbs((PLO, PHI), (xlo, xhi)); (xlo, xhi) } @@ -69,17 +76,11 @@ unconstrained fn lte_unsafe(x: Field, y: Field, num_bytes: u32) -> bool { pub fn assert_gt(a: Field, b: Field) { // Decompose a and b - let (alo, ahi) = decompose(a); - let (blo, bhi) = decompose(b); - - let borrow = lte_unsafe(alo, blo, 16); + let a_limbs = decompose(a); + let b_limbs = decompose(b); - // Assert that (alo > blo && ahi >= bhi) || (alo <= blo && ahi > bhi) - let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128; - let rhi = ahi - bhi - (borrow as Field); - - rlo.assert_max_bit_size(128); - rhi.assert_max_bit_size(128); + // Assert that a_limbs is greater than b_limbs + assert_gt_limbs(a_limbs, b_limbs) } pub fn assert_lt(a: Field, b: Field) { diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 5b97d809896..12bf373e671 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -1,7 +1,7 @@ use crate::hash::Hasher; use crate::default::Default; -global RATE = 3; +global RATE: u32 = 3; struct Poseidon2 { cache: [Field;3], diff --git a/noir/noir-repo/noir_stdlib/src/ops.nr b/noir/noir-repo/noir_stdlib/src/ops.nr index e561265629e..d855e794fb4 100644 --- a/noir/noir-repo/noir_stdlib/src/ops.nr +++ b/noir/noir-repo/noir_stdlib/src/ops.nr @@ -6,9 +6,9 @@ trait Add { impl Add for Field { fn add(self, other: Field) -> Field { self + other } } -impl Add for u8 { fn add(self, other: u8) -> u8 { self + other } } -impl Add for u32 { fn add(self, other: u32) -> u32 { self + other } } impl Add for u64 { fn add(self, other: u64) -> u64 { self + other } } +impl Add for u32 { fn add(self, other: u32) -> u32 { self + other } } +impl Add for u8 { fn add(self, other: u8) -> u8 { self + other } } impl Add for i8 { fn add(self, other: i8) -> i8 { self + other } } impl Add for i32 { fn add(self, other: i32) -> i32 { self + other } } @@ -22,9 +22,9 @@ trait Sub { impl Sub for Field { fn sub(self, other: Field) -> Field { self - other } } -impl Sub for u8 { fn sub(self, other: u8) -> u8 { self - other } } -impl Sub for u32 { fn sub(self, other: u32) -> u32 { self - other } } impl Sub for u64 { fn sub(self, other: u64) -> u64 { self - other } } +impl Sub for u32 { fn sub(self, other: u32) -> u32 { self - other } } +impl Sub for u8 { fn sub(self, other: u8) -> u8 { self - other } } impl Sub for i8 { fn sub(self, other: i8) -> i8 { self - other } } impl Sub for i32 { fn sub(self, other: i32) -> i32 { self - other } } @@ -38,9 +38,9 @@ trait Mul { impl Mul for Field { fn mul(self, other: Field) -> Field { self * other } } -impl Mul for u8 { fn mul(self, other: u8) -> u8 { self * other } } -impl Mul for u32 { fn mul(self, other: u32) -> u32 { self * other } } impl Mul for u64 { fn mul(self, other: u64) -> u64 { self * other } } +impl Mul for u32 { fn mul(self, other: u32) -> u32 { self * other } } +impl Mul for u8 { fn mul(self, other: u8) -> u8 { self * other } } impl Mul for i8 { fn mul(self, other: i8) -> i8 { self * other } } impl Mul for i32 { fn mul(self, other: i32) -> i32 { self * other } } @@ -54,9 +54,9 @@ trait Div { impl Div for Field { fn div(self, other: Field) -> Field { self / other } } -impl Div for u8 { fn div(self, other: u8) -> u8 { self / other } } -impl Div for u32 { fn div(self, other: u32) -> u32 { self / other } } impl Div for u64 { fn div(self, other: u64) -> u64 { self / other } } +impl Div for u32 { fn div(self, other: u32) -> u32 { self / other } } +impl Div for u8 { fn div(self, other: u8) -> u8 { self / other } } impl Div for i8 { fn div(self, other: i8) -> i8 { self / other } } impl Div for i32 { fn div(self, other: i32) -> i32 { self / other } } @@ -68,9 +68,9 @@ trait Rem{ } // docs:end:rem-trait -impl Rem for u8 { fn rem(self, other: u8) -> u8 { self % other } } -impl Rem for u32 { fn rem(self, other: u32) -> u32 { self % other } } impl Rem for u64 { fn rem(self, other: u64) -> u64 { self % other } } +impl Rem for u32 { fn rem(self, other: u32) -> u32 { self % other } } +impl Rem for u8 { fn rem(self, other: u8) -> u8 { self % other } } impl Rem for i8 { fn rem(self, other: i8) -> i8 { self % other } } impl Rem for i32 { fn rem(self, other: i32) -> i32 { self % other } } @@ -84,9 +84,9 @@ trait BitOr { impl BitOr for bool { fn bitor(self, other: bool) -> bool { self | other } } -impl BitOr for u8 { fn bitor(self, other: u8) -> u8 { self | other } } -impl BitOr for u32 { fn bitor(self, other: u32) -> u32 { self | other } } impl BitOr for u64 { fn bitor(self, other: u64) -> u64 { self | other } } +impl BitOr for u32 { fn bitor(self, other: u32) -> u32 { self | other } } +impl BitOr for u8 { fn bitor(self, other: u8) -> u8 { self | other } } impl BitOr for i8 { fn bitor(self, other: i8) -> i8 { self | other } } impl BitOr for i32 { fn bitor(self, other: i32) -> i32 { self | other } } @@ -100,9 +100,9 @@ trait BitAnd { impl BitAnd for bool { fn bitand(self, other: bool) -> bool { self & other } } -impl BitAnd for u8 { fn bitand(self, other: u8) -> u8 { self & other } } -impl BitAnd for u32 { fn bitand(self, other: u32) -> u32 { self & other } } impl BitAnd for u64 { fn bitand(self, other: u64) -> u64 { self & other } } +impl BitAnd for u32 { fn bitand(self, other: u32) -> u32 { self & other } } +impl BitAnd for u8 { fn bitand(self, other: u8) -> u8 { self & other } } impl BitAnd for i8 { fn bitand(self, other: i8) -> i8 { self & other } } impl BitAnd for i32 { fn bitand(self, other: i32) -> i32 { self & other } } @@ -116,9 +116,9 @@ trait BitXor { impl BitXor for bool { fn bitxor(self, other: bool) -> bool { self ^ other } } -impl BitXor for u8 { fn bitxor(self, other: u8) -> u8 { self ^ other } } -impl BitXor for u32 { fn bitxor(self, other: u32) -> u32 { self ^ other } } impl BitXor for u64 { fn bitxor(self, other: u64) -> u64 { self ^ other } } +impl BitXor for u32 { fn bitxor(self, other: u32) -> u32 { self ^ other } } +impl BitXor for u8 { fn bitxor(self, other: u8) -> u8 { self ^ other } } impl BitXor for i8 { fn bitxor(self, other: i8) -> i8 { self ^ other } } impl BitXor for i32 { fn bitxor(self, other: i32) -> i32 { self ^ other } } @@ -130,14 +130,14 @@ trait Shl { } // docs:end:shl-trait -impl Shl for u8 { fn shl(self, other: u8) -> u8 { self << other } } impl Shl for u32 { fn shl(self, other: u32) -> u32 { self << other } } impl Shl for u64 { fn shl(self, other: u64) -> u64 { self << other } } +impl Shl for u8 { fn shl(self, other: u8) -> u8 { self << other } } +impl Shl for u1 { fn shl(self, other: u1) -> u1 { self << other } } -// Bit shifting is not currently supported for signed integer types -// impl Shl for i8 { fn shl(self, other: i8) -> i8 { self << other } } -// impl Shl for i32 { fn shl(self, other: i32) -> i32 { self << other } } -// impl Shl for i64 { fn shl(self, other: i64) -> i64 { self << other } } +impl Shl for i8 { fn shl(self, other: i8) -> i8 { self << other } } +impl Shl for i32 { fn shl(self, other: i32) -> i32 { self << other } } +impl Shl for i64 { fn shl(self, other: i64) -> i64 { self << other } } // docs:start:shr-trait trait Shr { @@ -145,11 +145,11 @@ trait Shr { } // docs:end:shr-trait -impl Shr for u8 { fn shr(self, other: u8) -> u8 { self >> other } } -impl Shr for u32 { fn shr(self, other: u32) -> u32 { self >> other } } impl Shr for u64 { fn shr(self, other: u64) -> u64 { self >> other } } +impl Shr for u32 { fn shr(self, other: u32) -> u32 { self >> other } } +impl Shr for u8 { fn shr(self, other: u8) -> u8 { self >> other } } +impl Shr for u1 { fn shr(self, other: u1) -> u1 { self >> other } } -// Bit shifting is not currently supported for signed integer types -// impl Shr for i8 { fn shr(self, other: i8) -> i8 { self >> other } } -// impl Shr for i32 { fn shr(self, other: i32) -> i32 { self >> other } } -// impl Shr for i64 { fn shr(self, other: i64) -> i64 { self >> other } } +impl Shr for i8 { fn shr(self, other: i8) -> i8 { self >> other } } +impl Shr for i32 { fn shr(self, other: i32) -> i32 { self >> other } } +impl Shr for i64 { fn shr(self, other: i64) -> i64 { self >> other } } diff --git a/noir/noir-repo/package.json b/noir/noir-repo/package.json index 049c1634dd2..3cffdf4c802 100644 --- a/noir/noir-repo/package.json +++ b/noir/noir-repo/package.json @@ -17,19 +17,12 @@ "test": "yarn workspaces foreach --parallel --verbose run test", "test:integration": "yarn workspace integration-tests test", "clean:workspaces": "yarn workspaces foreach --exclude @noir-lang/root run clean", - "clean:root": "rm -rf ./result ./target ./packages", + "clean:root": "rm -rf ./target ./packages", "clean": "yarn clean:workspaces && yarn clean:root", "lint": "yarn workspaces foreach --verbose run lint", "spellcheck": "cspell '**/*.{md,rs}' -c ./cspell.json", - "install:acvm_js": "yarn workspace @noir-lang/acvm_js run install:from:nix", - "install:noir_wasm": "yarn workspace @noir-lang/noir_wasm run install:from:nix", - "install:noirc_abi_wasm": "yarn workspace @noir-lang/noirc_abi run install:from:nix", - "install:from:nix": "yarn install:acvm_js && yarn install:noir_wasm && yarn install:noirc_abi_wasm", - "build:types": "yarn workspace @noir-lang/types run build", - "build:backend_barretenberg": "yarn workspace @noir-lang/backend_barretenberg run build", - "build:noir_js": "yarn workspace @noir-lang/noir_js run build", - "build:js:only": "yarn workspaces foreach -vtp --from \"{@noir-lang/types,@noir-lang/backend_barretenberg,@noir-lang/noir_js,@noir-lang/noir_codegen}\" run build", - "prepare:publish": "yarn clean && yarn install:from:nix && yarn build:js:only", + "prepare:publish": "yarn clean && yarn build", + "build:js:only": "yarn workspaces foreach -vtp --exclude \"{@noir-lang/acvm_js,@noir-lang/noirc_abi,@noir-lang/noir_wasm,docs,@noir-lang/root}\" run build", "nightly:version": "yarn workspaces foreach run nightly:version", "publish:all": "yarn install && yarn workspaces foreach run publish" }, diff --git a/noir/noir-repo/release-please-config.json b/noir/noir-repo/release-please-config.json index 217a86303a1..0ba192754a0 100644 --- a/noir/noir-repo/release-please-config.json +++ b/noir/noir-repo/release-please-config.json @@ -13,7 +13,6 @@ "include-component-in-tag": false, "extra-files": [ "Cargo.toml", - "flake.nix", { "type": "json", "path": "compiler/wasm/package.json", @@ -82,4 +81,4 @@ "sentence-case" ], "bootstrap-sha": "690cfc0468de0b9aee53ccfe832c71c16e61e5fc" -} \ No newline at end of file +} diff --git a/noir/noir-repo/shell.nix b/noir/noir-repo/shell.nix deleted file mode 100644 index b72d4a4697b..00000000000 --- a/noir/noir-repo/shell.nix +++ /dev/null @@ -1,13 +0,0 @@ -let - lock = builtins.fromJSON (builtins.readFile ./flake.lock); - flakeCompatRev = lock.nodes.flake-compat.locked.rev; - flakeCompatHash = lock.nodes.flake-compat.locked.narHash; - flakeCompat = fetchTarball { - url = "https://github.com/edolstra/flake-compat/archive/${flakeCompatRev}.tar.gz"; - sha256 = flakeCompatHash; - }; - compat = import flakeCompat { - src = ./.; - }; -in -compat.shellNix diff --git a/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/Nargo.toml new file mode 100644 index 00000000000..fa376596ee2 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "array_length_defaulting" +type = "bin" +authors = [""] +compiler_version = ">=0.26.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/src/main.nr b/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/src/main.nr new file mode 100644 index 00000000000..216a9ae3f0c --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/array_length_defaulting/src/main.nr @@ -0,0 +1,10 @@ +fn main() { + let x = dep::std::unsafe::zeroed(); + foo(x); +} + +fn foo(array: [Field; N]) { + for elem in array { + println(elem); + } +} diff --git a/noir/noir-repo/test_programs/compile_failure/typevar_default/Nargo.toml b/noir/noir-repo/test_programs/compile_failure/typevar_default/Nargo.toml new file mode 100644 index 00000000000..b3cd08bb8fe --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/typevar_default/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "typevar_default" +type = "bin" +authors = [""] +compiler_version = ">=0.26.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_failure/typevar_default/src/main.nr b/noir/noir-repo/test_programs/compile_failure/typevar_default/src/main.nr new file mode 100644 index 00000000000..1eb9cf63de3 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_failure/typevar_default/src/main.nr @@ -0,0 +1,12 @@ +fn main() { + // Expecting error: type annotations needed (N not known) + let _ = slice_to_array(&[1, 2, 3]); +} + +fn slice_to_array(slice: [Field]) -> [Field; N] { + let mut array = [0; N]; + for i in 0 .. N { + array[i] = slice[i]; + } + array +} diff --git a/noir/noir-repo/test_programs/compile_success_contract/fold_non_contract_method/Nargo.toml b/noir/noir-repo/test_programs/compile_success_contract/fold_non_contract_method/Nargo.toml new file mode 100644 index 00000000000..ff64bbb6f1b --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_contract/fold_non_contract_method/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fold_non_contract_method" +type = "contract" +authors = [""] +compiler_version = ">=0.26.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_contract/fold_non_contract_method/src/main.nr b/noir/noir-repo/test_programs/compile_success_contract/fold_non_contract_method/src/main.nr new file mode 100644 index 00000000000..2e00ffa1381 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_contract/fold_non_contract_method/src/main.nr @@ -0,0 +1,18 @@ +contract Foo { + use crate::times_10; + + fn double(x: Field) -> pub Field { + x * 2 + } + fn triple(x: Field) -> pub Field { + x * 3 + } + fn times_40(x: Field) -> pub Field { + times_10(x) * 4 + } +} + +#[fold] +fn times_10(x: Field) -> Field { + x * 10 +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr index 989c8f65bf4..c5f321256b1 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/option/src/main.nr @@ -39,9 +39,9 @@ fn main() { let add1_u64 = |value: Field| Option::some(value as u64 + 1); - assert(none.and_then(|_value| Option::none()).is_none()); + assert(none.and_then(|_value| none).is_none()); assert(none.and_then(add1_u64).is_none()); - assert(some.and_then(|_value| Option::none()).is_none()); + assert(some.and_then(|_value| none).is_none()); assert(some.and_then(add1_u64).unwrap() == 4); assert(some.and_then(|x| Option::some(x + ten)).unwrap() == 13); diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_4635/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/regression_4635/Nargo.toml new file mode 100644 index 00000000000..563e262410f --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_4635/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_4635" +type = "bin" +authors = [""] +compiler_version = ">=0.26.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/compile_success_empty/regression_4635/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/regression_4635/src/main.nr new file mode 100644 index 00000000000..23918e30785 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/regression_4635/src/main.nr @@ -0,0 +1,59 @@ +trait FromField { + fn from_field(field: Field) -> Self; +} + +impl FromField for Field { + fn from_field(value: Field) -> Self { + value + } +} + +trait Deserialize { + fn deserialize(fields: [Field; N]) -> Self; +} + +global AZTEC_ADDRESS_LENGTH = 1; + +struct AztecAddress { + inner : Field +} + +impl FromField for AztecAddress { + fn from_field(value: Field) -> Self { + Self { inner: value } + } +} + +impl Deserialize for AztecAddress { + fn deserialize(fields: [Field; AZTEC_ADDRESS_LENGTH]) -> Self { + AztecAddress::from_field(fields[0]) + } +} + +impl Eq for AztecAddress { + fn eq(self, other: Self) -> bool { + self.inner == other.inner + } +} + +// Custom code + +struct MyStruct { + a: T +} + +impl Deserialize<1> for MyStruct { + fn deserialize(fields: [Field; 1]) -> Self where T: FromField { + Self{ a: FromField::from_field(fields[0]) } + } +} + +fn main() { + let fields = [5; 1]; + let foo = MyStruct::deserialize(fields); // Note I don't specify T here (the type of `foo.a`) + + let bar = AztecAddress { inner: 5 }; + + // Here `T` is apparently inferred to be `AztecAddress`, presumably because of the comparison. + assert(foo.a == bar); +} diff --git a/noir/noir-repo/test_programs/compile_failure/hashmap_load_factor/Nargo.toml b/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/Nargo.toml similarity index 100% rename from noir/noir-repo/test_programs/compile_failure/hashmap_load_factor/Nargo.toml rename to noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/Nargo.toml diff --git a/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/Prover.toml b/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/Prover.toml new file mode 100644 index 00000000000..6d72cab47fa --- /dev/null +++ b/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/Prover.toml @@ -0,0 +1,23 @@ +[[input]] +key = 1 +value = 0 + +[[input]] +key = 2 +value = 0 + +[[input]] +key = 3 +value = 0 + +[[input]] +key = 4 +value = 0 + +[[input]] +key = 5 +value = 0 + +[[input]] +key = 6 +value = 0 diff --git a/noir/noir-repo/test_programs/compile_failure/hashmap_load_factor/src/main.nr b/noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/src/main.nr similarity index 100% rename from noir/noir-repo/test_programs/compile_failure/hashmap_load_factor/src/main.nr rename to noir/noir-repo/test_programs/execution_failure/hashmap_load_factor/src/main.nr diff --git a/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/Nargo.toml b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/Nargo.toml new file mode 100644 index 00000000000..b338cf9b6ae --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "array_to_slice_constant_length" +type = "bin" +authors = [""] +compiler_version = ">=0.26.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/Prover.toml b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/Prover.toml new file mode 100644 index 00000000000..a52e9d3c46a --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/Prover.toml @@ -0,0 +1 @@ +val = "42" 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 new file mode 100644 index 00000000000..e81dd4a0c5f --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/array_to_slice_constant_length/src/main.nr @@ -0,0 +1,10 @@ +// Regression test for https://github.com/noir-lang/noir/issues/4722 + +unconstrained fn return_array(val: Field) -> [Field; 1] { + [val; 1] +} + +fn main(val: Field) { + let array = return_array(val); + assert_constant(array.as_slice().len()); +} diff --git a/noir/noir-repo/test_programs/execution_success/bit_shifts_runtime/src/main.nr b/noir/noir-repo/test_programs/execution_success/bit_shifts_runtime/src/main.nr index 28b3ef656c1..ff424c9fbf6 100644 --- a/noir/noir-repo/test_programs/execution_success/bit_shifts_runtime/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/bit_shifts_runtime/src/main.nr @@ -7,7 +7,7 @@ fn main(x: u64, y: u64) { assert(x >> y == 32); // Bit-shift with signed integers - let mut a :i8 = y as i8; + let mut a: i8 = y as i8; let mut b: i8 = x as i8; assert(b << 1 == -128); assert(b >> 2 == 16); diff --git a/noir/noir-repo/test_programs/execution_success/brillig_slice_input/Nargo.toml b/noir/noir-repo/test_programs/execution_success/brillig_slice_input/Nargo.toml new file mode 100644 index 00000000000..a1c8cc3242b --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/brillig_slice_input/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "brillig_slice_input" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/brillig_slice_input/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_slice_input/src/main.nr new file mode 100644 index 00000000000..09a9d9aef9d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/brillig_slice_input/src/main.nr @@ -0,0 +1,40 @@ +struct Point { + x: Field, + y: Field, +} + +unconstrained fn sum_slice(slice: [[Point; 2]]) -> Field { + let mut sum = 0; + for i in 0..slice.len() { + for j in 0..slice[i].len() { + sum += slice[i][j].x + slice[i][j].y; + } + } + sum +} + +fn main() { + let mut slice = &[]; + slice = slice.push_back([ + Point { + x: 13, + y: 14, + }, + Point { + x: 20, + y: 8, + } + ]); + slice = slice.push_back([ + Point { + x: 15, + y: 5, + }, + Point { + x: 12, + y: 13, + } + ]); + let brillig_sum = sum_slice(slice); + assert_eq(brillig_sum, 100); +} diff --git a/noir/noir-repo/test_programs/execution_success/fold_basic_nested_call/src/main.nr b/noir/noir-repo/test_programs/execution_success/fold_basic_nested_call/src/main.nr index 3eb16b409ff..6d02b734727 100644 --- a/noir/noir-repo/test_programs/execution_success/fold_basic_nested_call/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/fold_basic_nested_call/src/main.nr @@ -13,4 +13,4 @@ fn func_with_nested_foo_call(x: Field, y: Field) -> Field { fn foo(x: Field, y: Field) -> Field { assert(x != y); x -} \ No newline at end of file +} diff --git a/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/Nargo.toml b/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/Nargo.toml new file mode 100644 index 00000000000..cedaea348c5 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fold_call_witness_condition" +type = "bin" +authors = [""] +compiler_version = ">=0.26.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/Prover.toml b/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/Prover.toml new file mode 100644 index 00000000000..8481ce25648 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/Prover.toml @@ -0,0 +1,5 @@ +# TODO(https://github.com/noir-lang/noir/issues/4707): Change these inputs to fail the assertion in `fn return_value` +# and change `enable` to false. For now we need the inputs to pass as we do not handle predicates with ACIR calls +x = "5" +y = "10" +enable = true \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/src/main.nr b/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/src/main.nr new file mode 100644 index 00000000000..5dc75e4a99f --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/fold_call_witness_condition/src/main.nr @@ -0,0 +1,16 @@ +global NUM_RESULTS = 2; +fn main(x: Field, y: pub Field, enable: bool) -> pub [Field; NUM_RESULTS] { + let mut result = [0; NUM_RESULTS]; + for i in 0..NUM_RESULTS { + if enable { + result[i] = return_value(x, y); + } + } + result +} + +#[fold] +fn return_value(x: Field, y: Field) -> Field { + assert(x != y); + x +} diff --git a/noir/noir-repo/test_programs/execution_success/prelude/src/main.nr b/noir/noir-repo/test_programs/execution_success/prelude/src/main.nr index c9ae448c486..226341f1e7b 100644 --- a/noir/noir-repo/test_programs/execution_success/prelude/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/prelude/src/main.nr @@ -1,6 +1,6 @@ fn main() { - let _xs = Vec::new(); - let _option = Option::none(); + let _xs: Vec = Vec::new(); + let _option: Option = Option::none(); print("42\n"); println("42"); diff --git a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Nargo.toml b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Nargo.toml new file mode 100644 index 00000000000..759c3b20ba8 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "regression_sha256_slice" +type = "bin" +authors = [""] +compiler_version = ">=0.26.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Prover.toml b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Prover.toml new file mode 100644 index 00000000000..8a027e9eca9 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/Prover.toml @@ -0,0 +1 @@ +x = ["5", "10"] diff --git a/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/src/main.nr new file mode 100644 index 00000000000..60b0911cf09 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_sha256_slice/src/main.nr @@ -0,0 +1,12 @@ +use dep::std; + +fn main(x: [u8; 2]) { + let mut y = x.as_slice(); + let digest1 = std::hash::sha256_slice(y); + let mut v = y; + if x[0] != 0 { + v = y.push_back(x[0]); + } + let digest2 = std::hash::sha256_slice(v); + assert(digest1 != digest2); +} diff --git a/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs b/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs index 362d2132952..52f7783851a 100644 --- a/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs +++ b/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs @@ -10,7 +10,7 @@ use const_format::formatcp; const USERNAME: &str = "AztecProtocol"; const REPO: &str = "aztec-packages"; -const VERSION: &str = "0.30.1"; +const VERSION: &str = "0.32.0"; const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = diff --git a/noir/noir-repo/tooling/debugger/build.rs b/noir/noir-repo/tooling/debugger/build.rs index 26a8bc64b0e..ebdf2036894 100644 --- a/noir/noir-repo/tooling/debugger/build.rs +++ b/noir/noir-repo/tooling/debugger/build.rs @@ -7,8 +7,7 @@ use std::{env, fs}; const GIT_COMMIT: &&str = &"GIT_COMMIT"; fn main() { - // Only use build_data if the environment variable isn't set - // The environment variable is always set when working via Nix + // Only use build_data if the environment variable isn't set. if std::env::var(GIT_COMMIT).is_err() { build_data::set_GIT_COMMIT(); build_data::set_GIT_DIRTY(); diff --git a/noir/noir-repo/tooling/debugger/ignored-tests.txt b/noir/noir-repo/tooling/debugger/ignored-tests.txt index 854e284dd43..4507aeb8545 100644 --- a/noir/noir-repo/tooling/debugger/ignored-tests.txt +++ b/noir/noir-repo/tooling/debugger/ignored-tests.txt @@ -12,3 +12,6 @@ references scalar_mul signed_comparison to_bytes_integration +fold_basic +fold_basic_nested_call +fold_call_witness_condition diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index ba12a20460d..1acd581b2be 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -128,9 +128,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { line: i64, ) -> Option { let line = line as usize; - let Some(line_to_opcodes) = self.source_to_opcodes.get(file_id) else { - return None; - }; + let line_to_opcodes = self.source_to_opcodes.get(file_id)?; let found_index = match line_to_opcodes.binary_search_by(|x| x.0.cmp(&line)) { Ok(index) => { // move backwards to find the first opcode which matches the line @@ -631,7 +629,6 @@ fn build_source_to_opcode_debug_mappings( #[cfg(test)] mod tests { use super::*; - use crate::context::{DebugCommandResult, DebugContext}; use crate::foreign_calls::DefaultDebugForeignCallExecutor; use acvm::{ @@ -647,8 +644,6 @@ mod tests { BinaryFieldOp, HeapValueType, MemoryAddress, Opcode as BrilligOpcode, ValueOrArray, }, }; - use nargo::artifacts::debug::DebugArtifact; - use std::collections::BTreeMap; #[test] fn test_resolve_foreign_calls_stepping_into_brillig() { diff --git a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs index aae2212fd54..f11ac22cd75 100644 --- a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs +++ b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs @@ -65,7 +65,7 @@ impl DefaultDebugForeignCallExecutor { pub fn load_artifact(&mut self, artifact: &DebugArtifact) { // TODO: handle loading from the correct DebugInfo when we support // debugging contracts - let Some(info) = artifact.debug_symbols.get(0) else { + let Some(info) = artifact.debug_symbols.first() else { return; }; self.debug_vars.insert_debug_info(info); diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs b/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs index 8e5c2bc46a4..6a42a4c3311 100644 --- a/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs +++ b/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs @@ -36,9 +36,7 @@ impl DebugVars { fn lookup_var(&self, var_id: DebugVarId) -> Option<(&str, &PrintableType)> { self.variables.get(&var_id).and_then(|debug_var| { - let Some(ptype) = self.types.get(&debug_var.debug_type_id) else { - return None; - }; + let ptype = self.types.get(&debug_var.debug_type_id)?; Some((debug_var.name.as_str(), ptype)) }) } diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/program.rs b/noir/noir-repo/tooling/nargo/src/artifacts/program.rs index 046db1cd8fa..9e660cbd359 100644 --- a/noir/noir-repo/tooling/nargo/src/artifacts/program.rs +++ b/noir/noir-repo/tooling/nargo/src/artifacts/program.rs @@ -34,6 +34,8 @@ pub struct ProgramArtifact { /// Map of file Id to the source code so locations in debug info can be mapped to source code they point to. pub file_map: BTreeMap, + + pub names: Vec, } impl From for ProgramArtifact { @@ -45,6 +47,7 @@ impl From for ProgramArtifact { bytecode: compiled_program.program, debug_symbols: compiled_program.debug, file_map: compiled_program.file_map, + names: compiled_program.names, } } } @@ -59,6 +62,7 @@ impl From for CompiledProgram { debug: program.debug_symbols, file_map: program.file_map, warnings: vec![], + names: program.names, } } } diff --git a/noir/noir-repo/tooling/nargo_cli/build.rs b/noir/noir-repo/tooling/nargo_cli/build.rs index a796eeac326..bf97dfb3e96 100644 --- a/noir/noir-repo/tooling/nargo_cli/build.rs +++ b/noir/noir-repo/tooling/nargo_cli/build.rs @@ -16,8 +16,7 @@ const GIT_COMMIT: &&str = &"GIT_COMMIT"; fn main() { check_rustc_version(); - // Only use build_data if the environment variable isn't set - // The environment variable is always set when working via Nix + // Only use build_data if the environment variable isn't set. if std::env::var(GIT_COMMIT).is_err() { build_data::set_GIT_COMMIT(); build_data::set_GIT_DIRTY(); @@ -245,7 +244,7 @@ fn compile_success_empty_{test_name}() {{ let json: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap_or_else(|e| {{ panic!("JSON was not well-formatted {{:?}}\n\n{{:?}}", e, std::str::from_utf8(&output.stdout)) }}); - let num_opcodes = &json["programs"][0]["acir_opcodes"]; + let num_opcodes = &json["programs"][0]["functions"][0]["acir_opcodes"]; assert_eq!(num_opcodes.as_u64().expect("number of opcodes should fit in a u64"), 0); }} "#, diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs index 897073f4e20..2b729e44b8a 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/check_cmd.rs @@ -34,6 +34,10 @@ pub(crate) struct CheckCommand { #[clap(long, conflicts_with = "package")] workspace: bool, + /// Force overwrite of existing files + #[clap(long = "overwrite")] + allow_overwrite: bool, + #[clap(flatten)] compile_options: CompileOptions, } @@ -58,18 +62,29 @@ pub(crate) fn run( let parsed_files = parse_all(&workspace_file_manager); for package in &workspace { - check_package(&workspace_file_manager, &parsed_files, package, &args.compile_options)?; - println!("[{}] Constraint system successfully built!", package.name); + let any_file_written = check_package( + &workspace_file_manager, + &parsed_files, + package, + &args.compile_options, + args.allow_overwrite, + )?; + if any_file_written { + println!("[{}] Constraint system successfully built!", package.name); + } } Ok(()) } +/// Evaluates the necessity to create or update Prover.toml and Verifier.toml based on the allow_overwrite flag and files' existence. +/// Returns `true` if any file was generated or updated, `false` otherwise. fn check_package( file_manager: &FileManager, parsed_files: &ParsedFiles, package: &Package, compile_options: &CompileOptions, -) -> Result<(), CompileError> { + allow_overwrite: bool, +) -> Result { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); check_crate_and_report_errors( &mut context, @@ -81,27 +96,39 @@ fn check_package( if package.is_library() || package.is_contract() { // Libraries do not have ABIs while contracts have many, so we cannot generate a `Prover.toml` file. - Ok(()) + Ok(false) } else { // XXX: We can have a --overwrite flag to determine if you want to overwrite the Prover/Verifier.toml files if let Some((parameters, return_type)) = compute_function_abi(&context, &crate_id) { let path_to_prover_input = package.prover_input_path(); let path_to_verifier_input = package.verifier_input_path(); - // If they are not available, then create them and populate them based on the ABI - if !path_to_prover_input.exists() { + // Before writing the file, check if it exists and whether overwrite is set + let should_write_prover = !path_to_prover_input.exists() || allow_overwrite; + let should_write_verifier = !path_to_verifier_input.exists() || allow_overwrite; + + if should_write_prover { let prover_toml = create_input_toml_template(parameters.clone(), None); write_to_file(prover_toml.as_bytes(), &path_to_prover_input); + } else { + eprintln!("Note: Prover.toml already exists. Use --overwrite to force overwrite."); } - if !path_to_verifier_input.exists() { + + if should_write_verifier { let public_inputs = parameters.into_iter().filter(|param| param.is_public()).collect(); let verifier_toml = create_input_toml_template(public_inputs, return_type); write_to_file(verifier_toml.as_bytes(), &path_to_verifier_input); + } else { + eprintln!( + "Note: Verifier.toml already exists. Use --overwrite to force overwrite." + ); } - Ok(()) + let any_file_written = should_write_prover || should_write_verifier; + + Ok(any_file_written) } else { Err(CompileError::MissingMainFunction(package.name.clone())) } diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs index 49924622392..72784013e17 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/info_cmd.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use acvm::acir::circuit::ExpressionWidth; +use acvm::acir::circuit::{ExpressionWidth, Program}; use backend_interface::BackendError; use clap::Args; use iter_extended::vecmap; @@ -24,9 +24,9 @@ use crate::errors::CliError; use super::{compile_cmd::compile_workspace, NargoConfig}; -/// Provides detailed information on a circuit +/// Provides detailed information on each of a program's function (represented by a single circuit) /// -/// Current information provided: +/// Current information provided per circuit: /// 1. The number of ACIR opcodes /// 2. Counts the final number gates in the circuit used by a backend #[derive(Debug, Clone, Args)] @@ -114,6 +114,7 @@ pub(crate) fn run( let binary_packages = workspace.into_iter().filter(|package| package.is_binary()).zip(compiled_programs); + let program_info = binary_packages .par_bridge() .map(|(package, program)| { @@ -134,10 +135,13 @@ pub(crate) fn run( } else { // Otherwise print human-readable table. if !info_report.programs.is_empty() { - let mut program_table = table!([Fm->"Package", Fm->"Expression Width", Fm->"ACIR Opcodes", Fm->"Backend Circuit Size"]); + let mut program_table = table!([Fm->"Package", Fm->"Function", Fm->"Expression Width", Fm->"ACIR Opcodes", Fm->"Backend Circuit Size"]); - for program in info_report.programs { - program_table.add_row(program.into()); + for program_info in info_report.programs { + let program_rows: Vec = program_info.into(); + for row in program_rows { + program_table.add_row(row); + } } program_table.printstd(); } @@ -223,18 +227,20 @@ struct ProgramInfo { name: String, #[serde(skip)] expression_width: ExpressionWidth, - acir_opcodes: usize, - circuit_size: u32, + functions: Vec, } -impl From for Row { +impl From for Vec { fn from(program_info: ProgramInfo) -> Self { - row![ - Fm->format!("{}", program_info.name), - format!("{:?}", program_info.expression_width), - Fc->format!("{}", program_info.acir_opcodes), - Fc->format!("{}", program_info.circuit_size), - ] + vecmap(program_info.functions, |function| { + row![ + Fm->format!("{}", program_info.name), + Fc->format!("{}", function.name), + format!("{:?}", program_info.expression_width), + Fc->format!("{}", function.acir_opcodes), + Fc->format!("{}", function.circuit_size), + ] + }) } } @@ -243,6 +249,7 @@ struct ContractInfo { name: String, #[serde(skip)] expression_width: ExpressionWidth, + // TODO(https://github.com/noir-lang/noir/issues/4720): Settle on how to display contract functions with non-inlined Acir calls functions: Vec, } @@ -273,13 +280,22 @@ fn count_opcodes_and_gates_in_program( package: &Package, expression_width: ExpressionWidth, ) -> Result { - Ok(ProgramInfo { - name: package.name.to_string(), - expression_width, - // TODO(https://github.com/noir-lang/noir/issues/4428) - acir_opcodes: compiled_program.program.functions[0].opcodes.len(), - circuit_size: backend.get_exact_circuit_size(&compiled_program.program)?, - }) + let functions = compiled_program + .program + .functions + .into_par_iter() + .enumerate() + .map(|(i, function)| -> Result<_, BackendError> { + Ok(FunctionInfo { + name: compiled_program.names[i].clone(), + acir_opcodes: function.opcodes.len(), + circuit_size: backend + .get_exact_circuit_size(&Program { functions: vec![function] })?, + }) + }) + .collect::>()?; + + Ok(ProgramInfo { name: package.name.to_string(), expression_width, functions }) } fn count_opcodes_and_gates_in_contract( @@ -293,7 +309,7 @@ fn count_opcodes_and_gates_in_contract( .map(|function| -> Result<_, BackendError> { Ok(FunctionInfo { name: function.name, - // TODO(https://github.com/noir-lang/noir/issues/4428) + // TODO(https://github.com/noir-lang/noir/issues/4720) acir_opcodes: function.bytecode.functions[0].opcodes.len(), circuit_size: backend.get_exact_circuit_size(&function.bytecode)?, }) diff --git a/noir/noir-repo/tooling/nargo_cli/src/main.rs b/noir/noir-repo/tooling/nargo_cli/src/main.rs index 3f797b0bf0c..6e2b7069bc4 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/main.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/main.rs @@ -25,14 +25,14 @@ fn main() { if let Ok(log_dir) = env::var("NARGO_LOG_DIR") { let debug_file = rolling::daily(log_dir, "nargo-log"); tracing_subscriber::fmt() - .with_span_events(FmtSpan::ACTIVE) + .with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE) .with_writer(debug_file) .with_ansi(false) .with_env_filter(EnvFilter::from_default_env()) .init(); } else { tracing_subscriber::fmt() - .with_span_events(FmtSpan::ACTIVE) + .with_span_events(FmtSpan::ENTER | FmtSpan::CLOSE) .with_ansi(true) .with_env_filter(EnvFilter::from_env("NOIR_LOG")) .init(); diff --git a/noir/noir-repo/tooling/nargo_fmt/build.rs b/noir/noir-repo/tooling/nargo_fmt/build.rs index c356b403ae5..6f41768c1dc 100644 --- a/noir/noir-repo/tooling/nargo_fmt/build.rs +++ b/noir/noir-repo/tooling/nargo_fmt/build.rs @@ -58,10 +58,10 @@ fn format_{test_name}() {{ let expected_output = r#"{output_source}"#; - let (parsed_module, _errors) = noirc_frontend::parse_program(&input); + let (parsed_module, _errors) = noirc_frontend::parse_program(input); let config = nargo_fmt::Config::of("{config}").unwrap(); - let fmt_text = nargo_fmt::format(&input, parsed_module, &config); + let fmt_text = nargo_fmt::format(input, parsed_module, &config); if std::env::var("UPDATE_EXPECT").is_ok() {{ std::fs::write("{output_source_path}", fmt_text.clone()).unwrap(); diff --git a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json index 1ea384cdd49..251dd80c2f4 100644 --- a/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json +++ b/noir/noir-repo/tooling/noir_js_backend_barretenberg/package.json @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "portal:../../../../barretenberg/ts", + "@aztec/bb.js": "0.32.0", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/tooling/noirc_abi/src/lib.rs b/noir/noir-repo/tooling/noirc_abi/src/lib.rs index cffb0c20cd5..d0dcb373963 100644 --- a/noir/noir-repo/tooling/noirc_abi/src/lib.rs +++ b/noir/noir-repo/tooling/noirc_abi/src/lib.rs @@ -147,7 +147,9 @@ impl AbiType { Type::TypeVariable(binding, TypeVariableKind::IntegerOrField) | Type::TypeVariable(binding, TypeVariableKind::Integer) => match &*binding.borrow() { TypeBinding::Bound(typ) => Self::from_type(context, typ), - TypeBinding::Unbound(_) => Self::from_type(context, &Type::default_int_type()), + TypeBinding::Unbound(_) => { + Self::from_type(context, &Type::default_int_or_field_type()) + } }, Type::Bool => Self::Boolean, Type::String(size) => { diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/README.md b/noir/noir-repo/tooling/noirc_abi_wasm/README.md index 77bc1f5fae2..2b0cf9b74d4 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/README.md +++ b/noir/noir-repo/tooling/noirc_abi_wasm/README.md @@ -1,17 +1,3 @@ # Noir Lang ABI JavaScript Package This JavaScript package enables users to ABI encode inputs to a Noir program, i.e. generating an initial witness. - -## Building from source - -Outside of the [noir repo](https://github.com/noir-lang/noir), this package can be built using the command below: - -```bash -nix build -L github:noir-lang/noir/master#abi_wasm -``` - -If you are within the noir repo and would like to build local changes, you can use: - -```bash -nix build -L #abi_wasm -``` diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/build.rs b/noir/noir-repo/tooling/noirc_abi_wasm/build.rs index 3b96be74ef3..7a6eb861de2 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/build.rs +++ b/noir/noir-repo/tooling/noirc_abi_wasm/build.rs @@ -1,8 +1,7 @@ const GIT_COMMIT: &&str = &"GIT_COMMIT"; fn main() { - // Only use build_data if the environment variable isn't set - // The environment variable is always set when working via Nix + // Only use build_data if the environment variable isn't set. if std::env::var(GIT_COMMIT).is_err() { build_data::set_GIT_COMMIT(); build_data::set_GIT_DIRTY(); diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh index 24af149bcea..fe0b4dcbfff 100755 --- a/noir/noir-repo/tooling/noirc_abi_wasm/build.sh +++ b/noir/noir-repo/tooling/noirc_abi_wasm/build.sh @@ -6,13 +6,6 @@ function require_command { exit 1 fi } -function check_installed { - if ! command -v "$1" >/dev/null 2>&1; then - echo "$1 is not installed. Please install it." >&2 - return 1 - fi - return 0 -} function run_or_fail { "$@" local status=$? @@ -25,23 +18,29 @@ function run_or_fail { require_command jq require_command cargo require_command wasm-bindgen -check_installed wasm-opt +require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") -export pname=$(cargo read-manifest | jq -r '.name') -export CARGO_TARGET_DIR=$self_path/target +pname=$(cargo read-manifest | jq -r '.name') -rm -rf $self_path/outputs >/dev/null 2>&1 -rm -rf $self_path/result >/dev/null 2>&1 +NODE_DIR=$self_path/nodejs +BROWSER_DIR=$self_path/web -if [ -n "$out" ]; then - echo "Will install package to $out (defined outside installPhase.sh script)" -else - export out="$self_path/outputs/out" - echo "Will install package to $out" +# Clear out the existing build artifacts as these aren't automatically removed by wasm-bindgen. +if [ -d ./pkg/ ]; then + rm -r $NODE_DIR + rm -r $BROWSER_DIR fi -run_or_fail $self_path/buildPhaseCargoCommand.sh -run_or_fail $self_path/installPhase.sh +TARGET=wasm32-unknown-unknown +WASM_BINARY=${self_path}/../../target/$TARGET/release/${pname}.wasm + +NODE_WASM=${NODE_DIR}/${pname}_bg.wasm +BROWSER_WASM=${BROWSER_DIR}/${pname}_bg.wasm -ln -s $out $self_path/result +# Build the new wasm package +run_or_fail cargo build --lib --release --target $TARGET --package ${pname} +run_or_fail wasm-bindgen $WASM_BINARY --out-dir $NODE_DIR --typescript --target nodejs +run_or_fail wasm-bindgen $WASM_BINARY --out-dir $BROWSER_DIR --typescript --target web +run_or_fail wasm-opt $NODE_WASM -o $NODE_WASM -O +run_or_fail wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/buildPhaseCargoCommand.sh b/noir/noir-repo/tooling/noirc_abi_wasm/buildPhaseCargoCommand.sh deleted file mode 100755 index 1188d00953e..00000000000 --- a/noir/noir-repo/tooling/noirc_abi_wasm/buildPhaseCargoCommand.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -function run_or_fail { - "$@" - local status=$? - if [ $status -ne 0 ]; then - echo "Command '$*' failed with exit code $status" >&2 - exit $status - fi -} -function run_if_available { - if command -v "$1" >/dev/null 2>&1; then - "$@" - else - echo "$1 is not installed. Please install it to use this feature." >&2 - fi -} - -export self_path=$(dirname "$(readlink -f "$0")") - -# Clear out the existing build artifacts as these aren't automatically removed by wasm-pack. -if [ -d ./pkg/ ]; then - rm -rf $self_path/pkg/ -fi - -TARGET=wasm32-unknown-unknown -WASM_BINARY=$CARGO_TARGET_DIR/$TARGET/release/${pname}.wasm - -NODE_DIR=$self_path/nodejs/ -BROWSER_DIR=$self_path/web/ -NODE_WASM=${NODE_DIR}/${pname}_bg.wasm -BROWSER_WASM=${BROWSER_DIR}/${pname}_bg.wasm - -# Build the new wasm package -run_or_fail cargo build --lib --release --target $TARGET --package ${pname} -run_or_fail wasm-bindgen $WASM_BINARY --out-dir $NODE_DIR --typescript --target nodejs -run_or_fail wasm-bindgen $WASM_BINARY --out-dir $BROWSER_DIR --typescript --target web -run_if_available wasm-opt $NODE_WASM -o $NODE_WASM -O -run_if_available wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O \ No newline at end of file diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/installPhase.sh b/noir/noir-repo/tooling/noirc_abi_wasm/installPhase.sh deleted file mode 100755 index d9b94f2d171..00000000000 --- a/noir/noir-repo/tooling/noirc_abi_wasm/installPhase.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -export self_path=$(dirname "$(readlink -f "$0")") - -export out_path=$out/noirc_abi_wasm - -mkdir -p $out_path -cp $self_path/README.md $out_path/ -cp $self_path/package.json $out_path/ -cp -r $self_path/nodejs $out_path/ -cp -r $self_path/web $out_path/ diff --git a/noir/noir-repo/tooling/noirc_abi_wasm/package.json b/noir/noir-repo/tooling/noirc_abi_wasm/package.json index e93a8e6a5e3..14e528c3b15 100644 --- a/noir/noir-repo/tooling/noirc_abi_wasm/package.json +++ b/noir/noir-repo/tooling/noirc_abi_wasm/package.json @@ -33,9 +33,7 @@ "clean": "chmod u+w web nodejs || true && rm -rf ./nodejs ./web ./target ./result", "nightly:version": "jq --arg new_version \"-$(git rev-parse --short HEAD)$1\" '.version = .version + $new_version' package.json > package-tmp.json && mv package-tmp.json package.json", "publish": "echo 📡 publishing `$npm_package_name` && yarn npm publish", - "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0", - "build:nix": "nix build -L .#noirc_abi_wasm", - "install:from:nix": "yarn clean && yarn build:nix && cp -rL ./result/noirc_abi_wasm/nodejs ./ && cp -rL ./result/noirc_abi_wasm/web ./" + "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { "@noir-lang/types": "workspace:*" diff --git a/noir/noir-repo/wasm-bindgen-cli.nix b/noir/noir-repo/wasm-bindgen-cli.nix deleted file mode 100644 index 7c3910f032e..00000000000 --- a/noir/noir-repo/wasm-bindgen-cli.nix +++ /dev/null @@ -1,43 +0,0 @@ -{ lib -, rustPlatform -, fetchCrate -, nodejs -, pkg-config -, openssl -, stdenv -, curl -, darwin -, libiconv -, runCommand -}: - -rustPlatform.buildRustPackage rec { - pname = "wasm-bindgen-cli"; - version = "0.2.86"; - - src = fetchCrate { - inherit pname version; - sha256 = "sha256-56EOiLbdgAcoTrkyvB3t9TjtLaRvGxFUXx4haLwE2QY="; - }; - - cargoSha256 = "sha256-4CPBmz92PuPN6KeGDTdYPAf5+vTFk9EN5Cmx4QJy6yI="; - - nativeBuildInputs = [ pkg-config ]; - - buildInputs = [ openssl ] ++ lib.optionals stdenv.isDarwin [ - curl - # Need libiconv and apple Security on Darwin. See https://github.com/ipetkov/crane/issues/156 - libiconv - darwin.apple_sdk.frameworks.Security - ]; - - doCheck = false; - - meta = with lib; { - homepage = "https://rustwasm.github.io/docs/wasm-bindgen/"; - license = with licenses; [ asl20 /* or */ mit ]; - description = "Facilitating high-level interactions between wasm modules and JavaScript"; - maintainers = with maintainers; [ nitsky rizary ]; - mainProgram = "wasm-bindgen"; - }; -} diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index b45678f5d8b..a39ae9921da 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,18 +221,19 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": - version: 0.0.0-use.local - resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" +"@aztec/bb.js@npm:0.32.0": + version: 0.32.0 + resolution: "@aztec/bb.js@npm:0.32.0" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: ./dest/node/main.js + bb.js: dest/node/main.js + checksum: 0919957e141ae0a65cfab961dce122fa06de628a10b7cb661d31d8ed4793ce80980fcf315620ceffffa45581db941bad43c392f4b2aa9becaaf7d2faaba01ffc languageName: node - linkType: soft + linkType: hard "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4395,7 +4396,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": "portal:../../../../barretenberg/ts" + "@aztec/bb.js": 0.32.0 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3