diff --git a/noir/noir-repo/.github/JS_PUBLISH_FAILED.md b/noir/noir-repo/.github/JS_PUBLISH_FAILED.md index 5b9f79aac1f..9adba2776c8 100644 --- a/noir/noir-repo/.github/JS_PUBLISH_FAILED.md +++ b/noir/noir-repo/.github/JS_PUBLISH_FAILED.md @@ -1,6 +1,6 @@ --- title: "JS packages failed to publish" -assignees: TomAFrench kevaundray savio-sou +assignees: TomAFrench kevaundray Savio-Sou labels: js --- diff --git a/noir/noir-repo/.github/workflows/publish-es-packages.yml b/noir/noir-repo/.github/workflows/publish-es-packages.yml index f72a97b2684..470db3b78f7 100644 --- a/noir/noir-repo/.github/workflows/publish-es-packages.yml +++ b/noir/noir-repo/.github/workflows/publish-es-packages.yml @@ -20,7 +20,9 @@ jobs: steps: - name: Checkout Noir repo uses: actions/checkout@v4 - + with: + ref: ${{ inputs.noir-ref }} + - name: Setup toolchain uses: dtolnay/rust-toolchain@1.73.0 @@ -87,6 +89,8 @@ jobs: steps: - name: Checkout sources uses: actions/checkout@v4 + with: + ref: ${{ inputs.noir-ref }} - name: Setup toolchain uses: dtolnay/rust-toolchain@1.73.0 @@ -164,4 +168,4 @@ jobs: WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} with: update_existing: true - filename: .github/JS_PUBLISH_FAILED.md \ No newline at end of file + filename: .github/JS_PUBLISH_FAILED.md diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index b2b6f8037bb..317418276b1 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -391,7 +391,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138985dd8aefbefeaa66b01b7f5b2b6b4c333fcef1cc5f32c63a2aabe37d6de3" dependencies = [ - "futures 0.3.28", + "futures 0.3.30", "lsp-types 0.94.1", "pin-project-lite", "rustix", @@ -521,9 +521,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bitmaps" @@ -947,14 +947,14 @@ checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" [[package]] name = "console" -version = "0.15.7" +version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" dependencies = [ "encode_unicode 0.3.6", "lazy_static", "libc", - "windows-sys 0.45.0", + "windows-sys 0.52.0", ] [[package]] @@ -1585,12 +1585,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1743,9 +1743,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1758,9 +1758,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1768,15 +1768,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1786,15 +1786,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -1803,21 +1803,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures 0.1.31", "futures-channel", @@ -2076,9 +2076,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -2091,7 +2091,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", @@ -2100,9 +2100,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -2343,7 +2343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" dependencies = [ "derive_more", - "futures 0.3.28", + "futures 0.3.30", "jsonrpc-core", "jsonrpc-pubsub", "log", @@ -2358,7 +2358,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures 0.3.28", + "futures 0.3.30", "futures-executor", "futures-util", "log", @@ -2373,7 +2373,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" dependencies = [ - "futures 0.3.28", + "futures 0.3.30", "jsonrpc-client-transports", ] @@ -2395,7 +2395,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" dependencies = [ - "futures 0.3.28", + "futures 0.3.30", "hyper", "jsonrpc-core", "jsonrpc-server-utils", @@ -2411,7 +2411,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" dependencies = [ - "futures 0.3.28", + "futures 0.3.30", "jsonrpc-core", "lazy_static", "log", @@ -2427,7 +2427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" dependencies = [ "bytes", - "futures 0.3.28", + "futures 0.3.30", "globset", "jsonrpc-core", "lazy_static", @@ -2485,9 +2485,9 @@ checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "linux-raw-sys" -version = "0.4.3" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -2946,7 +2946,6 @@ dependencies = [ "noirc_errors", "noirc_evaluator", "noirc_frontend", - "noirc_macros", "rust-embed", "serde", "thiserror", @@ -3012,14 +3011,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "noirc_macros" -version = "0.24.0" -dependencies = [ - "iter-extended", - "noirc_frontend", -] - [[package]] name = "noirc_printable_type" version = "0.24.0" @@ -3460,7 +3451,7 @@ checksum = "7c003ac8c77cb07bb74f5f198bce836a689bcd5a42574612bf14d17bfd08c20e" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.3.3", + "bitflags 2.4.2", "lazy_static", "num-traits", "rand 0.8.5", @@ -3922,15 +3913,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" dependencies = [ - "bitflags 2.3.3", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -4301,9 +4292,9 @@ dependencies = [ [[package]] name = "shared-buffer" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cf61602ee61e2f83dd016b3e6387245291cf728ea071c378b35088125b4d995" +checksum = "f6c99835bad52957e7aa241d3975ed17c1e5f8c92026377d117a606f36b84b16" dependencies = [ "bytes", "memmap2 0.6.2", @@ -4421,16 +4412,6 @@ dependencies = [ "serde", ] -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.5" @@ -4742,7 +4723,7 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -5258,9 +5239,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "4.2.4" +version = "4.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce45cc009177ca345a6d041f9062305ad467d15e7d41494f5b81ab46d62d7a58" +checksum = "5c15724dc25d1ee57962334aea8e41ade2675e5ea2ac6b8d42da6051b0face66" dependencies = [ "bytes", "cfg-if 1.0.0", @@ -5274,23 +5255,23 @@ dependencies = [ "shared-buffer", "target-lexicon", "thiserror", + "tracing", "wasm-bindgen", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-derive", "wasmer-types", "wasmer-vm", - "wasmparser 0.83.0", - "wasmparser 0.95.0", + "wasmparser", "wat", "winapi", ] [[package]] name = "wasmer-compiler" -version = "4.2.4" +version = "4.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e044f6140c844602b920deb4526aea3cc9c0d7cf23f00730bb9b2034669f522a" +checksum = "55a7f3b3a96f8d844c25e2c032af9572306dd63fa93dc17bcca4c5458ac569bd" dependencies = [ "backtrace", "bytes", @@ -5309,15 +5290,15 @@ dependencies = [ "thiserror", "wasmer-types", "wasmer-vm", - "wasmparser 0.95.0", + "wasmparser", "winapi", ] [[package]] name = "wasmer-compiler-cranelift" -version = "4.2.4" +version = "4.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ce02358eb44a149d791c1d6648fb7f8b2f99cd55e3c4eef0474653ec8cc889" +checksum = "102e2c5bacac69495c4025767e2fa26797ffb27f242dccb7cf57d9cefd944386" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -5334,9 +5315,9 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "4.2.4" +version = "4.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c782d80401edb08e1eba206733f7859db6c997fc5a7f5fb44edc3ecd801468f6" +checksum = "0ea737fa08f95d6abc4459f42a70a9833e8974b814e74971d77ef473814f4d4c" dependencies = [ "proc-macro-error", "proc-macro2", @@ -5346,9 +5327,9 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "4.2.4" +version = "4.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd09e80d4d74bb9fd0ce6c3c106b1ceba1a050f9948db9d9b78ae53c172d6157" +checksum = "b0689110e291b0f07fc665f2824e5ff81df120848e8a9acfbf1a9bf7990773f9" dependencies = [ "bytecheck", "enum-iterator", @@ -5362,9 +5343,9 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "4.2.4" +version = "4.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdcd8a4fd36414a7b6a003dbfbd32393bce3e155d715dd877c05c1b7a41d224d" +checksum = "4cd41f822a1ac4242d478754e8ceba2806a00ea5072803622e1fe91e8e28b2a1" dependencies = [ "backtrace", "cc", @@ -5390,18 +5371,13 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.83.0" +version = "0.121.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" - -[[package]] -name = "wasmparser" -version = "0.95.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ea896273ea99b15132414be1da01ab0d8836415083298ecaffbe308eaac87a" +checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" dependencies = [ - "indexmap 1.9.3", - "url 2.4.0", + "bitflags 2.4.2", + "indexmap 2.0.0", + "semver", ] [[package]] @@ -5494,15 +5470,6 @@ dependencies = [ "windows_x86_64_msvc 0.33.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -5513,18 +5480,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.42.2" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.4", ] [[package]] @@ -5543,10 +5504,19 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" +name = "windows-targets" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] [[package]] name = "windows_aarch64_gnullvm" @@ -5554,6 +5524,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.33.0" @@ -5562,15 +5538,15 @@ checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -5580,15 +5556,15 @@ checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -5598,15 +5574,15 @@ checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -5616,27 +5592,27 @@ checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -5646,15 +5622,15 @@ checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" [[package]] name = "winnow" diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 7d5da7b00d0..38f39137360 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -1,9 +1,8 @@ [workspace] members = [ - # Macros crates for metaprogramming + # Aztec Macro crate for metaprogramming "aztec_macros", - "noirc_macros", # Compiler crates "compiler/noirc_evaluator", "compiler/noirc_frontend", 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 ea601a6b80f..a0a15409604 100644 --- a/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/bn254_blackbox_solver/Cargo.toml @@ -32,7 +32,7 @@ ark-ff = { version = "^0.4.0", default-features = false } num-bigint.workspace = true [target.'cfg(target_arch = "wasm32")'.dependencies] -wasmer = { version = "4.2.3", default-features = false, features = [ +wasmer = { version = "4.2.6", default-features = false, features = [ "js-default", ] } @@ -42,7 +42,7 @@ js-sys.workspace = true [target.'cfg(not(target_arch = "wasm32"))'.dependencies] getrandom.workspace = true -wasmer = "4.2.3" +wasmer = "4.2.6" [build-dependencies] pkg-config = "0.3" diff --git a/noir/noir-repo/bootstrap.sh b/noir/noir-repo/bootstrap.sh deleted file mode 100755 index 54129c3d61a..00000000000 --- a/noir/noir-repo/bootstrap.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -set -eu - -cd $(dirname "$0") - -CMD=${1:-} - -if [ -n "$CMD" ]; then - if [ "$CMD" = "clean" ]; then - git clean -fdx - exit 0 - else - echo "Unknown command: $CMD" - exit 1 - fi -fi - -# Attempt to just pull artefacts from CI and exit on success. -[ -n "${USE_CACHE:-}" ] && ./bootstrap_cache.sh && exit - -./scripts/bootstrap_native.sh -./scripts/bootstrap_packages.sh diff --git a/noir/noir-repo/bootstrap_cache.sh b/noir/noir-repo/bootstrap_cache.sh deleted file mode 100755 index 1cec6c81d8e..00000000000 --- a/noir/noir-repo/bootstrap_cache.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -eu - -cd "$(dirname "$0")" -source ../build-system/scripts/setup_env '' '' mainframe_$USER > /dev/null - -echo -e "\033[1mRetrieving noir packages from remote cache...\033[0m" -extract_repo noir-packages /usr/src/noir/packages ./ -echo -e "\033[1mRetrieving nargo from remote cache...\033[0m" -extract_repo noir /usr/src/noir/target/release ./target/ - -remove_old_images noir-packages -remove_old_images noir diff --git a/noir/noir-repo/compiler/noirc_driver/Cargo.toml b/noir/noir-repo/compiler/noirc_driver/Cargo.toml index 681976735f3..a7fe0b4b610 100644 --- a/noir/noir-repo/compiler/noirc_driver/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_driver/Cargo.toml @@ -26,4 +26,3 @@ tracing.workspace = true thiserror.workspace = true aztec_macros = { path = "../../aztec_macros" } -noirc_macros = { path = "../../noirc_macros" } diff --git a/noir/noir-repo/compiler/noirc_driver/src/lib.rs b/noir/noir-repo/compiler/noirc_driver/src/lib.rs index 11f53cdb749..fa134f8a0dd 100644 --- a/noir/noir-repo/compiler/noirc_driver/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_driver/src/lib.rs @@ -237,14 +237,8 @@ pub fn check_crate( deny_warnings: bool, disable_macros: bool, ) -> CompilationResult<()> { - let macros: Vec<&dyn MacroProcessor> = if disable_macros { - vec![&noirc_macros::AssertMessageMacro as &dyn MacroProcessor] - } else { - vec![ - &aztec_macros::AztecMacro as &dyn MacroProcessor, - &noirc_macros::AssertMessageMacro as &dyn MacroProcessor, - ] - }; + let macros: &[&dyn MacroProcessor] = + if disable_macros { &[] } else { &[&aztec_macros::AztecMacro as &dyn MacroProcessor] }; let mut errors = vec![]; let diagnostics = CrateDefMap::collect_defs(crate_id, context, macros); diff --git a/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs index 67ec851d46d..09117bdc3b7 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/debug_info.rs @@ -24,6 +24,9 @@ use serde::{ #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Deserialize, Serialize)] pub struct DebugVarId(pub u32); +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Deserialize, Serialize)] +pub struct DebugFnId(pub u32); + #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Deserialize, Serialize)] pub struct DebugTypeId(pub u32); @@ -33,7 +36,14 @@ pub struct DebugVariable { pub debug_type_id: DebugTypeId, } +#[derive(Debug, Clone, Hash, Deserialize, Serialize)] +pub struct DebugFunction { + pub name: String, + pub arg_names: Vec, +} + pub type DebugVariables = BTreeMap; +pub type DebugFunctions = BTreeMap; pub type DebugTypes = BTreeMap; #[serde_as] @@ -45,6 +55,7 @@ pub struct DebugInfo { #[serde_as(as = "BTreeMap")] pub locations: BTreeMap>, pub variables: DebugVariables, + pub functions: DebugFunctions, pub types: DebugTypes, } @@ -60,9 +71,10 @@ impl DebugInfo { pub fn new( locations: BTreeMap>, variables: DebugVariables, + functions: DebugFunctions, types: DebugTypes, ) -> Self { - Self { locations, variables, types } + Self { locations, variables, functions, types } } /// Updates the locations map when the [`Circuit`][acvm::acir::circuit::Circuit] is modified. diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index 0bb81efe977..56cb76adbe4 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -88,6 +88,7 @@ pub fn create_circuit( ) -> Result<(Circuit, DebugInfo, Vec, Vec, Vec), RuntimeError> { let debug_variables = program.debug_variables.clone(); let debug_types = program.debug_types.clone(); + let debug_functions = program.debug_functions.clone(); let func_sig = program.main_function_signature.clone(); let recursive = program.recursive; let mut generated_acir = optimize_into_acir( @@ -130,7 +131,7 @@ pub fn create_circuit( .map(|(index, locations)| (index, locations.into_iter().collect())) .collect(); - let mut debug_info = DebugInfo::new(locations, debug_variables, debug_types); + let mut debug_info = DebugInfo::new(locations, debug_variables, debug_functions, debug_types); // Perform any ACIR-level optimizations let (optimized_circuit, transformation_map) = acvm::compiler::optimize(circuit); 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 9d27554dcaa..bf34a47485b 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 @@ -393,10 +393,25 @@ impl FunctionBuilder { self.increment_array_reference_count(value); } } - Type::Array(..) | Type::Slice(..) => { - self.insert_instruction(Instruction::IncrementRc { value }, None); + typ @ Type::Array(..) | typ @ Type::Slice(..) => { // If there are nested arrays or slices, we wait until ArrayGet // is issued to increment the count of that array. + self.insert_instruction(Instruction::IncrementRc { value }, None); + + // This is a bit odd, but in brillig the inc_rc instruction operates on + // a copy of the array's metadata, so we need to re-store a loaded array + // even if there have been no other changes to it. + if let Value::Instruction { instruction, .. } = &self.current_function.dfg[value] { + let instruction = &self.current_function.dfg[*instruction]; + if let Instruction::Load { address } = instruction { + // We can't re-use `value` in case the original address was stored + // to again in the meantime. So introduce another load. + let address = *address; + let value = self.insert_load(address, typ); + self.insert_instruction(Instruction::IncrementRc { value }, None); + self.insert_store(address, value); + } + } } } } 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 943a57c1bc0..46f1e7a2765 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 @@ -199,22 +199,47 @@ struct Context<'f> { /// found, the top of this conditions stack is popped since we are no longer under that /// condition. If we are under multiple conditions (a nested if), the topmost condition is /// the most recent condition combined with all previous conditions via `And` instructions. - conditions: Vec<(BasicBlockId, ValueId)>, + condition_stack: Vec, /// Maps SSA array values with a slice type to their size. /// This is maintained by appropriate calls to the `SliceCapacityTracker` and is used by the `ValueMerger`. slice_sizes: HashMap, + + /// Stack of block arguments + /// When processing a block, we pop this stack to get its arguments + /// and at the end we push the arguments for his successor + arguments_stack: Vec>, } +#[derive(Clone)] pub(crate) struct Store { old_value: ValueId, new_value: ValueId, } -struct Branch { - condition: ValueId, +#[derive(Clone)] +struct ConditionalBranch { + // Contains the last processed block during the processing of the branch. last_block: BasicBlockId, + // The unresolved condition of the branch + old_condition: ValueId, + // The condition of the branch + condition: ValueId, + // The store values accumulated when processing the branch store_values: HashMap, + // The allocations accumulated when processing the branch + local_allocations: HashSet, +} + +struct ConditionalContext { + // Condition from the conditional statement + condition: ValueId, + // Block containing the conditional statement + entry_block: BasicBlockId, + // First block of the then branch + then_branch: ConditionalBranch, + // First block of the else branch + else_branch: Option, } fn flatten_function_cfg(function: &mut Function) { @@ -233,90 +258,117 @@ fn flatten_function_cfg(function: &mut Function) { store_values: HashMap::default(), local_allocations: HashSet::new(), branch_ends, - conditions: Vec::new(), slice_sizes: HashMap::default(), + condition_stack: Vec::new(), + arguments_stack: Vec::new(), }; context.flatten(); } impl<'f> Context<'f> { fn flatten(&mut self) { - // Start with following the terminator of the entry block since we don't - // need to flatten the entry block into itself. - self.handle_terminator(self.inserter.function.entry_block()); + // Flatten the CFG by inlining all instructions from the queued blocks + // until all blocks have been flattened. + // We follow the terminator of each block to determine which blocks to + // process next + let mut queue = vec![self.inserter.function.entry_block()]; + while let Some(block) = queue.pop() { + self.inline_block(block); + let to_process = self.handle_terminator(block, &queue); + for incoming_block in to_process { + if !queue.contains(&incoming_block) { + queue.push(incoming_block); + } + } + } } - /// Check the terminator of the given block and recursively inline any blocks reachable from - /// it. Since each block from a jmpif terminator is inlined successively, we must handle - /// instructions with side effects like constrain and store specially to preserve correctness. - /// For these instructions we must keep track of what the current condition is and modify - /// the instructions according to the module-level comment at the top of this file. Note that - /// the current condition is all the jmpif conditions required to reach the current block, - /// combined via `And` instructions. - /// - /// Returns the last block to be inlined. This is either the return block of the function or, - /// if self.conditions is not empty, the end block of the most recent condition. - fn handle_terminator(&mut self, block: BasicBlockId) -> BasicBlockId { - // As we recursively flatten inner blocks, we need to track the slice information - // for the outer block before we start recursively inlining - let outer_block_instructions = self.inserter.function.dfg[block].instructions(); - let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); - for instruction in outer_block_instructions { - let results = self.inserter.function.dfg.instruction_results(*instruction); - let instruction = &self.inserter.function.dfg[*instruction]; + /// Returns the updated condition so that + /// it is 'AND-ed' with the previous condition (if any) + fn link_condition(&mut self, condition: ValueId) -> ValueId { + // Retrieve the previous condition + if let Some(context) = self.condition_stack.last() { + let previous_branch = context.else_branch.as_ref().unwrap_or(&context.then_branch); + let and = Instruction::binary(BinaryOp::And, previous_branch.condition, condition); + self.insert_instruction(and, CallStack::new()) + } else { + condition + } + } + + /// Returns the current condition + fn get_last_condition(&self) -> Option { + self.condition_stack.last().map(|context| match &context.else_branch { + Some(else_branch) => else_branch.condition, + None => context.then_branch.condition, + }) + } + + // Inline all instructions from the given block into the entry block, and track slice capacities + fn inline_block(&mut self, block: BasicBlockId) { + if self.inserter.function.entry_block() == block { + // we do not inline the entry block into itself + // for the outer block before we start inlining + let outer_block_instructions = self.inserter.function.dfg[block].instructions(); + let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); + for instruction in outer_block_instructions { + let results = self.inserter.function.dfg.instruction_results(*instruction); + let instruction = &self.inserter.function.dfg[*instruction]; + capacity_tracker.collect_slice_information( + instruction, + &mut self.slice_sizes, + results.to_vec(), + ); + } + + return; + } + + let arguments = self.arguments_stack.pop().unwrap(); + self.inserter.remember_block_params(block, &arguments); + + // If this is not a separate variable, clippy gets confused and says the to_vec is + // unnecessary, when removing it actually causes an aliasing/mutability error. + let instructions = self.inserter.function.dfg[block].instructions().to_vec(); + for instruction in instructions.iter() { + let results = self.push_instruction(*instruction); + let (instruction, _) = self.inserter.map_instruction(*instruction); + let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); capacity_tracker.collect_slice_information( - instruction, + &instruction, &mut self.slice_sizes, - results.to_vec(), + results, ); } + } - match self.inserter.function.dfg[block].unwrap_terminator() { + /// Returns the list of blocks that need to be processed after the given block + /// For a normal block, it would be its successor + /// For blocks related to a conditional statement, we ensure to process + /// the 'then-branch', then the 'else-branch' (if it exists), and finally the end block + fn handle_terminator( + &mut self, + block: BasicBlockId, + work_list: &[BasicBlockId], + ) -> Vec { + let terminator = self.inserter.function.dfg[block].unwrap_terminator().clone(); + match &terminator { TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { - let old_condition = *condition; - let then_block = *then_destination; - let else_block = *else_destination; - let then_condition = self.inserter.resolve(old_condition); - - let one = FieldElement::one(); - let then_branch = - self.inline_branch(block, then_block, old_condition, then_condition, one); - - let else_condition = - self.insert_instruction(Instruction::Not(then_condition), CallStack::new()); - let zero = FieldElement::zero(); - - // Make sure the else branch sees the previous values of each store - // rather than any values created in the 'then' branch. - self.undo_stores_in_then_branch(&then_branch); - - let else_branch = - self.inline_branch(block, else_block, old_condition, else_condition, zero); - - // We must remember to reset whether side effects are enabled when both branches - // end, in addition to resetting the value of old_condition since it is set to - // known to be true/false within the then/else branch respectively. - self.insert_current_side_effects_enabled(); - - // We must map back to `then_condition` here. Mapping `old_condition` to itself would - // lose any previous mappings. - self.inserter.map_value(old_condition, then_condition); - - // While there is a condition on the stack we don't compile outside the condition - // until it is popped. This ensures we inline the full then and else branches - // before continuing from the end of the conditional here where they can be merged properly. - let end = self.branch_ends[&block]; - self.inline_branch_end(end, then_branch, else_branch) + self.arguments_stack.push(vec![]); + self.if_start(condition, then_destination, else_destination, &block) } TerminatorInstruction::Jmp { destination, arguments, call_stack: _ } => { - if let Some((end_block, _)) = self.conditions.last() { - if destination == end_block { - return block; + let arguments = vecmap(arguments.clone(), |value| self.inserter.resolve(value)); + self.arguments_stack.push(arguments); + if work_list.contains(destination) { + if work_list.last() == Some(destination) { + self.else_stop(&block) + } else { + self.then_stop(&block) } + } else { + vec![*destination] } - let destination = *destination; - let arguments = vecmap(arguments.clone(), |value| self.inserter.resolve(value)); - self.inline_block(destination, &arguments) } TerminatorInstruction::Return { return_values, call_stack } => { let call_stack = call_stack.clone(); @@ -326,133 +378,133 @@ impl<'f> Context<'f> { let entry = self.inserter.function.entry_block(); self.inserter.function.dfg.set_block_terminator(entry, new_return); - block + vec![] } } } - /// Push a condition to the stack of conditions. - /// - /// This condition should be present while we're inlining each block reachable from the 'then' - /// branch of a jmpif instruction, until the branches eventually join back together. Likewise, - /// !condition should be present while we're inlining each block reachable from the 'else' - /// branch of a jmpif instruction until the join block. - fn push_condition(&mut self, start_block: BasicBlockId, condition: ValueId) { - let end_block = self.branch_ends[&start_block]; - - if let Some((_, previous_condition)) = self.conditions.last() { - let and = Instruction::binary(BinaryOp::And, *previous_condition, condition); - let new_condition = self.insert_instruction(and, CallStack::new()); - self.conditions.push((end_block, new_condition)); - } else { - self.conditions.push((end_block, condition)); + /// Process a conditional statement + fn if_start( + &mut self, + condition: &ValueId, + then_destination: &BasicBlockId, + else_destination: &BasicBlockId, + if_entry: &BasicBlockId, + ) -> Vec { + // manage conditions + let old_condition = *condition; + let then_condition = self.inserter.resolve(old_condition); + + let one = FieldElement::one(); + let old_stores = std::mem::take(&mut self.store_values); + let old_allocations = std::mem::take(&mut self.local_allocations); + let branch = ConditionalBranch { + old_condition, + condition: self.link_condition(then_condition), + store_values: old_stores, + local_allocations: old_allocations, + last_block: *then_destination, + }; + let cond_context = ConditionalContext { + condition: then_condition, + entry_block: *if_entry, + then_branch: branch, + else_branch: None, + }; + self.condition_stack.push(cond_context); + self.insert_current_side_effects_enabled(); + // Optimization: within the then branch we know the condition to be true, so replace + // any references of it within this branch with true. Likewise, do the same with false + // with the else branch. We must be careful not to replace the condition if it is a + // known constant, otherwise we can end up setting 1 = 0 or vice-versa. + if self.inserter.function.dfg.get_numeric_constant(old_condition).is_none() { + let known_value = self.inserter.function.dfg.make_constant(one, Type::bool()); + + self.inserter.map_value(old_condition, known_value); } + vec![self.branch_ends[if_entry], *else_destination, *then_destination] } - /// Insert a new instruction into the function's entry block. - /// Unlike push_instruction, this function will not map any ValueIds. - /// within the given instruction, nor will it modify self.values in any way. - fn insert_instruction(&mut self, instruction: Instruction, call_stack: CallStack) -> ValueId { - let block = self.inserter.function.entry_block(); - self.inserter - .function - .dfg - .insert_instruction_and_results(instruction, block, None, call_stack) - .first() + /// Switch context to the 'else-branch' + fn then_stop(&mut self, block: &BasicBlockId) -> Vec { + let mut cond_context = self.condition_stack.pop().unwrap(); + cond_context.then_branch.last_block = *block; + + let else_condition = + self.insert_instruction(Instruction::Not(cond_context.condition), CallStack::new()); + let else_condition = self.link_condition(else_condition); + + let zero = FieldElement::zero(); + // Make sure the else branch sees the previous values of each store + // rather than any values created in the 'then' branch. + let old_stores = std::mem::take(&mut cond_context.then_branch.store_values); + cond_context.then_branch.store_values = std::mem::take(&mut self.store_values); + self.undo_stores_in_then_branch(&cond_context.then_branch.store_values); + + let old_allocations = std::mem::take(&mut self.local_allocations); + let else_branch = ConditionalBranch { + old_condition: cond_context.then_branch.old_condition, + condition: else_condition, + store_values: old_stores, + local_allocations: old_allocations, + last_block: *block, + }; + let old_condition = else_branch.old_condition; + cond_context.then_branch.local_allocations.clear(); + cond_context.else_branch = Some(else_branch); + self.condition_stack.push(cond_context); + + self.insert_current_side_effects_enabled(); + // Optimization: within the then branch we know the condition to be true, so replace + // any references of it within this branch with true. Likewise, do the same with false + // with the else branch. We must be careful not to replace the condition if it is a + // known constant, otherwise we can end up setting 1 = 0 or vice-versa. + if self.inserter.function.dfg.get_numeric_constant(old_condition).is_none() { + let known_value = self.inserter.function.dfg.make_constant(zero, Type::bool()); + + self.inserter.map_value(old_condition, known_value); + } + assert_eq!(self.cfg.successors(*block).len(), 1); + vec![self.cfg.successors(*block).next().unwrap()] } - /// Inserts a new instruction into the function's entry block, using the given - /// control type variables to specify result types if needed. - /// Unlike push_instruction, this function will not map any ValueIds. - /// within the given instruction, nor will it modify self.values in any way. - fn insert_instruction_with_typevars( - &mut self, - instruction: Instruction, - ctrl_typevars: Option>, - ) -> InsertInstructionResult { - let block = self.inserter.function.entry_block(); - self.inserter.function.dfg.insert_instruction_and_results( - instruction, - block, - ctrl_typevars, - CallStack::new(), - ) - } + /// Process the 'exit' block of a conditional statement + fn else_stop(&mut self, block: &BasicBlockId) -> Vec { + let mut cond_context = self.condition_stack.pop().unwrap(); + if cond_context.else_branch.is_none() { + // then_stop() has not been called, this means that the conditional statement has no else branch + // so we simply do the then_stop() now + self.condition_stack.push(cond_context); + self.then_stop(block); + cond_context = self.condition_stack.pop().unwrap(); + } - /// Checks the branch condition on the top of the stack and uses it to build and insert an - /// `EnableSideEffects` instruction into the entry block. - /// - /// If the stack is empty, a "true" u1 constant is taken to be the active condition. This is - /// necessary for re-enabling side-effects when re-emerging to a branch depth of 0. - fn insert_current_side_effects_enabled(&mut self) { - let condition = match self.conditions.last() { - Some((_, cond)) => *cond, - None => { - self.inserter.function.dfg.make_constant(FieldElement::one(), Type::unsigned(1)) - } - }; - let enable_side_effects = Instruction::EnableSideEffects { condition }; - self.insert_instruction_with_typevars(enable_side_effects, None); - } + let mut else_branch = cond_context.else_branch.unwrap(); + let stores_in_branch = std::mem::replace(&mut self.store_values, else_branch.store_values); + self.local_allocations = std::mem::take(&mut else_branch.local_allocations); + else_branch.last_block = *block; + else_branch.store_values = stores_in_branch; + cond_context.else_branch = Some(else_branch); - /// Inline one branch of a jmpif instruction. - /// - /// This will continue inlining recursively until the next end block is reached where each branch - /// of the jmpif instruction is joined back into a single block. - /// - /// Within a branch of a jmpif instruction, we can assume the condition of the jmpif to be - /// always true or false, depending on which branch we're in. - /// - /// Returns the ending block / join block of this branch. - fn inline_branch( - &mut self, - jmpif_block: BasicBlockId, - destination: BasicBlockId, - old_condition: ValueId, - new_condition: ValueId, - condition_value: FieldElement, - ) -> Branch { - if destination == self.branch_ends[&jmpif_block] { - // If the branch destination is the same as the end of the branch, this must be the - // 'else' case of an if with no else - so there is no else branch. - Branch { - condition: new_condition, - // The last block here is somewhat arbitrary. It only matters that it has no Jmp - // args that will be merged by inline_branch_end. Since jmpifs don't have - // block arguments, it is safe to use the jmpif block here. - last_block: jmpif_block, - store_values: HashMap::default(), - } - } else { - self.push_condition(jmpif_block, new_condition); - self.insert_current_side_effects_enabled(); - let old_stores = std::mem::take(&mut self.store_values); - let old_allocations = std::mem::take(&mut self.local_allocations); - - // Optimization: within the then branch we know the condition to be true, so replace - // any references of it within this branch with true. Likewise, do the same with false - // with the else branch. We must be careful not to replace the condition if it is a - // known constant, otherwise we can end up setting 1 = 0 or vice-versa. - if self.inserter.function.dfg.get_numeric_constant(old_condition).is_none() { - let known_value = - self.inserter.function.dfg.make_constant(condition_value, Type::bool()); - - self.inserter.map_value(old_condition, known_value); - } + // We must remember to reset whether side effects are enabled when both branches + // end, in addition to resetting the value of old_condition since it is set to + // known to be true/false within the then/else branch respectively. + self.insert_current_side_effects_enabled(); - let final_block = self.inline_block(destination, &[]); + // We must map back to `then_condition` here. Mapping `old_condition` to itself would + // lose any previous mappings. + self.inserter + .map_value(cond_context.then_branch.old_condition, cond_context.then_branch.condition); - self.conditions.pop(); + // While there is a condition on the stack we don't compile outside the condition + // until it is popped. This ensures we inline the full then and else branches + // before continuing from the end of the conditional here where they can be merged properly. + let end = self.branch_ends[&cond_context.entry_block]; - let stores_in_branch = std::mem::replace(&mut self.store_values, old_stores); - self.local_allocations = old_allocations; + // Merge arguments and stores from the else/end branches + self.inline_branch_end(end, cond_context); - Branch { - condition: new_condition, - last_block: final_block, - store_values: stores_in_branch, - } - } + vec![self.cfg.successors(*block).next().unwrap()] } /// Inline the ending block of a branch, the point where all blocks from a jmpif instruction @@ -467,15 +519,17 @@ impl<'f> Context<'f> { fn inline_branch_end( &mut self, destination: BasicBlockId, - then_branch: Branch, - else_branch: Branch, + cond_context: ConditionalContext, ) -> BasicBlockId { assert_eq!(self.cfg.predecessors(destination).len(), 2); + let last_then = cond_context.then_branch.last_block; + let mut else_args = Vec::new(); + if cond_context.else_branch.is_some() { + let last_else = cond_context.else_branch.clone().unwrap().last_block; + else_args = self.inserter.function.dfg[last_else].terminator_arguments().to_vec(); + } - let then_args = - self.inserter.function.dfg[then_branch.last_block].terminator_arguments().to_vec(); - let else_args = - self.inserter.function.dfg[else_branch.last_block].terminator_arguments().to_vec(); + let then_args = self.inserter.function.dfg[last_then].terminator_arguments().to_vec(); let params = self.inserter.function.dfg.block_parameters(destination); assert_eq!(params.len(), then_args.len()); @@ -500,17 +554,64 @@ impl<'f> Context<'f> { // Cannot include this in the previous vecmap since it requires exclusive access to self let args = vecmap(args, |(then_arg, else_arg)| { value_merger.merge_values( - then_branch.condition, - else_branch.condition, + cond_context.then_branch.condition, + cond_context.else_branch.clone().unwrap().condition, then_arg, else_arg, ) }); - self.merge_stores(then_branch, else_branch); + self.merge_stores(cond_context.then_branch, cond_context.else_branch); + self.arguments_stack.pop(); + self.arguments_stack.pop(); + self.arguments_stack.push(args); + destination + } - // insert merge instruction - self.inline_block(destination, &args) + /// Insert a new instruction into the function's entry block. + /// Unlike push_instruction, this function will not map any ValueIds. + /// within the given instruction, nor will it modify self.values in any way. + fn insert_instruction(&mut self, instruction: Instruction, call_stack: CallStack) -> ValueId { + let block = self.inserter.function.entry_block(); + self.inserter + .function + .dfg + .insert_instruction_and_results(instruction, block, None, call_stack) + .first() + } + + /// Inserts a new instruction into the function's entry block, using the given + /// control type variables to specify result types if needed. + /// Unlike push_instruction, this function will not map any ValueIds. + /// within the given instruction, nor will it modify self.values in any way. + fn insert_instruction_with_typevars( + &mut self, + instruction: Instruction, + ctrl_typevars: Option>, + ) -> InsertInstructionResult { + let block = self.inserter.function.entry_block(); + self.inserter.function.dfg.insert_instruction_and_results( + instruction, + block, + ctrl_typevars, + CallStack::new(), + ) + } + + /// Checks the branch condition on the top of the stack and uses it to build and insert an + /// `EnableSideEffects` instruction into the entry block. + /// + /// If the stack is empty, a "true" u1 constant is taken to be the active condition. This is + /// necessary for re-enabling side-effects when re-emerging to a branch depth of 0. + fn insert_current_side_effects_enabled(&mut self) { + let condition = match self.get_last_condition() { + Some(cond) => cond, + None => { + self.inserter.function.dfg.make_constant(FieldElement::one(), Type::unsigned(1)) + } + }; + let enable_side_effects = Instruction::EnableSideEffects { condition }; + self.insert_instruction_with_typevars(enable_side_effects, None); } /// Merge any store instructions found in each branch. @@ -518,7 +619,11 @@ impl<'f> Context<'f> { /// This function relies on the 'then' branch being merged before the 'else' branch of a jmpif /// instruction. If this ordering is changed, the ordering that store values are merged within /// this function also needs to be changed to reflect that. - fn merge_stores(&mut self, then_branch: Branch, else_branch: Branch) { + fn merge_stores( + &mut self, + then_branch: ConditionalBranch, + else_branch: Option, + ) { // Address -> (then_value, else_value, value_before_the_if) let mut new_map = BTreeMap::new(); @@ -526,11 +631,13 @@ impl<'f> Context<'f> { new_map.insert(address, (store.new_value, store.old_value, store.old_value)); } - for (address, store) in else_branch.store_values { - if let Some(entry) = new_map.get_mut(&address) { - entry.1 = store.new_value; - } else { - new_map.insert(address, (store.old_value, store.new_value, store.old_value)); + if else_branch.is_some() { + for (address, store) in else_branch.clone().unwrap().store_values { + if let Some(entry) = new_map.get_mut(&address) { + entry.1 = store.new_value; + } else { + new_map.insert(address, (store.old_value, store.new_value, store.old_value)); + } } } @@ -544,8 +651,11 @@ impl<'f> Context<'f> { } let then_condition = then_branch.condition; - let else_condition = else_branch.condition; - + let else_condition = if let Some(branch) = else_branch { + branch.condition + } else { + self.inserter.function.dfg.make_constant(FieldElement::zero(), Type::bool()) + }; let block = self.inserter.function.entry_block(); let mut value_merger = @@ -607,35 +717,6 @@ impl<'f> Context<'f> { } } - /// Inline all instructions from the given destination block into the entry block. - /// Afterwards, check the block's terminator and continue inlining recursively. - /// - /// Returns the final block that was inlined. - /// - /// Expects that the `arguments` given are already translated via self.inserter.resolve. - /// If they are not, it is possible some values which no longer exist, such as block - /// parameters, will be kept in the program. - fn inline_block(&mut self, destination: BasicBlockId, arguments: &[ValueId]) -> BasicBlockId { - self.inserter.remember_block_params(destination, arguments); - - // If this is not a separate variable, clippy gets confused and says the to_vec is - // unnecessary, when removing it actually causes an aliasing/mutability error. - let instructions = self.inserter.function.dfg[destination].instructions().to_vec(); - - for instruction in instructions.iter() { - let results = self.push_instruction(*instruction); - let (instruction, _) = self.inserter.map_instruction(*instruction); - let mut capacity_tracker = SliceCapacityTracker::new(&self.inserter.function.dfg); - capacity_tracker.collect_slice_information( - &instruction, - &mut self.slice_sizes, - results, - ); - } - - self.handle_terminator(destination) - } - /// Push the given instruction to the end of the entry block of the current function. /// /// Note that each ValueId of the instruction will be mapped via self.inserter.resolve. @@ -666,7 +747,7 @@ impl<'f> Context<'f> { instruction: Instruction, call_stack: CallStack, ) -> Instruction { - if let Some((_, condition)) = self.conditions.last().copied() { + if let Some(condition) = self.get_last_condition() { match instruction { Instruction::Constrain(lhs, rhs, message) => { // Replace constraint `lhs == rhs` with `condition * lhs == condition * rhs`. @@ -741,8 +822,8 @@ impl<'f> Context<'f> { } } - fn undo_stores_in_then_branch(&mut self, then_branch: &Branch) { - for (address, store) in &then_branch.store_values { + fn undo_stores_in_then_branch(&mut self, store_values: &HashMap) { + for (address, store) in store_values { let address = *address; let value = store.old_value; self.insert_instruction_with_typevars(Instruction::Store { address, value }, None); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs index 7cd0fe3084e..bdfc04f0bbe 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg/capacity_tracker.rs @@ -5,6 +5,7 @@ use crate::ssa::ir::{ value::{Value, ValueId}, }; +use acvm::FieldElement; use fxhash::FxHashMap as HashMap; pub(crate) struct SliceCapacityTracker<'a> { @@ -62,21 +63,27 @@ impl<'a> SliceCapacityTracker<'a> { | Intrinsic::SlicePushFront | Intrinsic::SlicePopBack | Intrinsic::SliceInsert - | Intrinsic::SliceRemove => (1, 1), + | Intrinsic::SliceRemove => (Some(1), 1), // `pop_front` returns the popped element, and then the respective slice. // This means in the case of a slice with structs, the result index of the popped slice // will change depending on the number of elements in the struct. // For example, a slice with four elements will look as such in SSA: // v3, v4, v5, v6, v7, v8 = call slice_pop_front(v1, v2) // where v7 is the slice length and v8 is the popped slice itself. - Intrinsic::SlicePopFront => (1, results.len() - 1), + Intrinsic::SlicePopFront => (Some(1), results.len() - 1), + // The slice capacity of these intrinsics is not determined by the arguments of the function. + Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => (None, 1), _ => return, }; - let slice_contents = arguments[argument_index]; + let result_slice = results[result_index]; match intrinsic { Intrinsic::SlicePushBack | Intrinsic::SlicePushFront | Intrinsic::SliceInsert => { + let argument_index = argument_index + .expect("ICE: Should have an argument index for slice intrinsics"); + let slice_contents = arguments[argument_index]; + for arg in &arguments[(argument_index + 1)..] { let element_typ = self.dfg.type_of_value(*arg); if element_typ.contains_slice_element() { @@ -85,20 +92,35 @@ impl<'a> SliceCapacityTracker<'a> { } if let Some(contents_capacity) = slice_sizes.get(&slice_contents) { let new_capacity = *contents_capacity + 1; - slice_sizes.insert(results[result_index], new_capacity); + slice_sizes.insert(result_slice, new_capacity); } } Intrinsic::SlicePopBack | Intrinsic::SliceRemove | Intrinsic::SlicePopFront => { + let argument_index = argument_index + .expect("ICE: Should have an argument index for slice intrinsics"); + let slice_contents = arguments[argument_index]; + // We do not decrement the size on intrinsics that could remove values from a slice. // This is because we could potentially go back to the smaller slice and not fill in dummies. // This pass should be tracking the potential max that a slice ***could be*** if let Some(contents_capacity) = slice_sizes.get(&slice_contents) { let new_capacity = *contents_capacity - 1; - slice_sizes.insert(results[result_index], new_capacity); + slice_sizes.insert(result_slice, new_capacity); } } + Intrinsic::ToBits(_) => { + // Compiler sanity check + assert!(matches!(self.dfg.type_of_value(result_slice), Type::Slice(_))); + slice_sizes.insert(result_slice, FieldElement::max_num_bits() as usize); + } + Intrinsic::ToRadix(_) => { + // Compiler sanity check + assert!(matches!(self.dfg.type_of_value(result_slice), Type::Slice(_))); + slice_sizes + .insert(result_slice, FieldElement::max_num_bytes() as usize); + } _ => {} } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index d95295ae3c9..f3fa5d1d2f8 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -722,6 +722,11 @@ impl<'a> FunctionContext<'a> { let lhs = self.extract_current_value(&assign.lvalue)?; let rhs = self.codegen_expression(&assign.expression)?; + rhs.clone().for_each(|value| { + let value = value.eval(self); + self.builder.increment_array_reference_count(value); + }); + self.assign_new_value(lhs, rhs); Ok(Self::unit_value()) } 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 f39b71405d3..387840b57c4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -240,6 +240,17 @@ pub trait Recoverable { fn error(span: Span) -> Self; } +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct ModuleDeclaration { + pub ident: Ident, +} + +impl std::fmt::Display for ModuleDeclaration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "mod {}", self.ident) + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct ImportStatement { pub path: Path, 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 a88567fcaf9..05916502d73 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -4,9 +4,11 @@ use crate::{ ast::{Path, PathKind}, parser::{Item, ItemKind}, }; +use noirc_errors::debug_info::{DebugFnId, DebugFunction}; use noirc_errors::{Span, Spanned}; use std::collections::HashMap; use std::collections::VecDeque; +use std::mem::take; const MAX_MEMBER_ASSIGN_DEPTH: usize = 8; @@ -26,8 +28,12 @@ pub struct DebugInstrumenter { // all field names referenced when assigning to a member of a variable pub field_names: HashMap, + // all collected function metadata (name + argument names) + pub functions: HashMap, + next_var_id: u32, next_field_name_id: u32, + next_fn_id: u32, // last seen variable names and their IDs grouped by scope scope: Vec>, @@ -38,9 +44,11 @@ impl Default for DebugInstrumenter { Self { variables: HashMap::default(), field_names: HashMap::default(), + functions: HashMap::default(), scope: vec![], next_var_id: 0, next_field_name_id: 1, + next_fn_id: 0, } } } @@ -76,10 +84,22 @@ impl DebugInstrumenter { field_name_id } + fn insert_function(&mut self, fn_name: String, arguments: Vec) -> DebugFnId { + let fn_id = DebugFnId(self.next_fn_id); + self.next_fn_id += 1; + self.functions.insert(fn_id, DebugFunction { name: fn_name, arg_names: arguments }); + fn_id + } + fn walk_fn(&mut self, func: &mut ast::FunctionDefinition) { + let func_name = func.name.0.contents.clone(); + let func_args = + func.parameters.iter().map(|param| pattern_to_string(¶m.pattern)).collect(); + let fn_id = self.insert_function(func_name, func_args); + let enter_stmt = build_debug_call_stmt("enter", fn_id, func.span); self.scope.push(HashMap::default()); - let set_fn_params = func + let set_fn_params: Vec<_> = func .parameters .iter() .flat_map(|param| { @@ -93,10 +113,21 @@ impl DebugInstrumenter { }) .collect(); - self.walk_scope(&mut func.body.0, func.span); + let func_body = &mut func.body.0; + let mut statements = take(func_body); + + self.walk_scope(&mut statements, func.span); - // prepend fn params: - func.body.0 = [set_fn_params, func.body.0.clone()].concat(); + // walk_scope ensures that the last statement is the return value of the function + let last_stmt = statements.pop().expect("at least one statement after walk_scope"); + let exit_stmt = build_debug_call_stmt("exit", fn_id, last_stmt.span); + + // rebuild function body + func_body.push(enter_stmt); + func_body.extend(set_fn_params); + func_body.extend(statements); + func_body.push(exit_stmt); + func_body.push(last_stmt); } // Modify a vector of statements in-place, adding instrumentation for sets and drops. @@ -427,6 +458,8 @@ impl DebugInstrumenter { use dep::__debug::{{ __debug_var_assign, __debug_var_drop, + __debug_fn_enter, + __debug_fn_exit, __debug_dereference_assign, {member_assigns}, }};"# @@ -451,14 +484,32 @@ pub fn build_debug_crate_file() -> String { } #[oracle(__debug_var_drop)] - unconstrained fn __debug_var_drop_oracle(_var_id: u32) {} - unconstrained fn __debug_var_drop_inner(var_id: u32) { + unconstrained fn __debug_var_drop_oracle(_var_id: u32) {} + unconstrained fn __debug_var_drop_inner(var_id: u32) { __debug_var_drop_oracle(var_id); } - pub fn __debug_var_drop(var_id: u32) { + pub fn __debug_var_drop(var_id: u32) { __debug_var_drop_inner(var_id); } + #[oracle(__debug_fn_enter)] + unconstrained fn __debug_fn_enter_oracle(_fn_id: u32) {} + unconstrained fn __debug_fn_enter_inner(fn_id: u32) { + __debug_fn_enter_oracle(fn_id); + } + pub fn __debug_fn_enter(fn_id: u32) { + __debug_fn_enter_inner(fn_id); + } + + #[oracle(__debug_fn_exit)] + unconstrained fn __debug_fn_exit_oracle(_fn_id: u32) {} + unconstrained fn __debug_fn_exit_inner(fn_id: u32) { + __debug_fn_exit_oracle(fn_id); + } + pub fn __debug_fn_exit(fn_id: u32) { + __debug_fn_exit_inner(fn_id); + } + #[oracle(__debug_dereference_assign)] unconstrained fn __debug_dereference_assign_oracle(_var_id: u32, _value: T) {} unconstrained fn __debug_dereference_assign_inner(var_id: u32, value: T) { @@ -561,6 +612,21 @@ fn build_assign_member_stmt( ast::Statement { kind: ast::StatementKind::Semi(ast::Expression { kind, span }), span } } +fn build_debug_call_stmt(fname: &str, fn_id: DebugFnId, span: Span) -> ast::Statement { + let kind = ast::ExpressionKind::Call(Box::new(ast::CallExpression { + func: Box::new(ast::Expression { + kind: ast::ExpressionKind::Variable(ast::Path { + segments: vec![ident(&format!["__debug_fn_{fname}"], span)], + kind: PathKind::Plain, + span, + }), + span, + }), + arguments: vec![uint_expr(fn_id.0 as u128, span)], + })); + ast::Statement { kind: ast::StatementKind::Semi(ast::Expression { kind, span }), span } +} + fn pattern_vars(pattern: &ast::Pattern) -> Vec<(ast::Ident, bool)> { let mut vars = vec![]; let mut stack = VecDeque::from([(pattern, false)]); @@ -585,6 +651,30 @@ fn pattern_vars(pattern: &ast::Pattern) -> Vec<(ast::Ident, bool)> { vars } +fn pattern_to_string(pattern: &ast::Pattern) -> String { + match pattern { + ast::Pattern::Identifier(id) => id.0.contents.clone(), + ast::Pattern::Mutable(mpat, _, _) => format!("mut {}", pattern_to_string(mpat.as_ref())), + ast::Pattern::Tuple(elements, _) => format!( + "({})", + elements.iter().map(pattern_to_string).collect::>().join(", ") + ), + ast::Pattern::Struct(name, fields, _) => { + format!( + "{} {{ {} }}", + name, + fields + .iter() + .map(|(field_ident, field_pattern)| { + format!("{}: {}", &field_ident.0.contents, pattern_to_string(field_pattern)) + }) + .collect::>() + .join(", "), + ) + } + } +} + fn ident(s: &str, span: Span) -> ast::Ident { ast::Ident(Spanned::from(span, s.to_string())) } 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 7f36af5b30e..62febc89899 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 @@ -5,13 +5,14 @@ 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::resolver::Resolver; 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, resolve_type_aliases, }; -use crate::hir::type_check::{type_check_func, TypeCheckError, TypeChecker}; +use crate::hir::type_check::{ + check_trait_impl_method_matches_declaration, type_check_func, TypeCheckError, TypeChecker, +}; use crate::hir::Context; use crate::macros_api::{MacroError, MacroProcessor}; @@ -20,8 +21,7 @@ use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId, TraitId, Ty use crate::parser::{ParserError, SortedModule}; use crate::{ ExpressionKind, Ident, LetStatement, Literal, NoirFunction, NoirStruct, NoirTrait, - NoirTypeAlias, Path, PathKind, Type, TypeBindings, UnresolvedGenerics, - UnresolvedTraitConstraint, UnresolvedType, + NoirTypeAlias, Path, PathKind, UnresolvedGenerics, UnresolvedTraitConstraint, UnresolvedType, }; use fm::FileId; use iter_extended::vecmap; @@ -207,7 +207,7 @@ impl DefCollector { context: &mut Context, ast: SortedModule, root_file_id: FileId, - macro_processors: Vec<&dyn MacroProcessor>, + macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; let crate_id = def_map.krate; @@ -220,11 +220,7 @@ impl DefCollector { let crate_graph = &context.crate_graph[crate_id]; for dep in crate_graph.dependencies.clone() { - errors.extend(CrateDefMap::collect_defs( - dep.crate_id, - context, - macro_processors.clone(), - )); + errors.extend(CrateDefMap::collect_defs(dep.crate_id, context, macro_processors)); let dep_def_root = context.def_map(&dep.crate_id).expect("ice: def map was just created").root; @@ -257,7 +253,7 @@ impl DefCollector { context.def_maps.insert(crate_id, def_collector.def_map); // TODO(#4653): generalize this function - for macro_processor in ¯o_processors { + for macro_processor in macro_processors { macro_processor .process_unresolved_traits_impls( &crate_id, @@ -282,7 +278,7 @@ impl DefCollector { // Resolve unresolved imports collected from the crate, one by one. for collected_import in def_collector.collected_imports { - match resolve_import(crate_id, collected_import, &context.def_maps) { + match resolve_import(crate_id, &collected_import, &context.def_maps) { Ok(resolved_import) => { // Populate module namespaces according to the imports used let current_def_map = context.def_maps.get_mut(&crate_id).unwrap(); @@ -372,12 +368,12 @@ impl DefCollector { &mut errors, )); - functions.extend(resolve_trait_impls( + let impl_functions = resolve_trait_impls( context, def_collector.collected_traits_impls, crate_id, &mut errors, - )); + ); for macro_processor in macro_processors { macro_processor.process_typed_ast(&crate_id, context).unwrap_or_else( @@ -391,6 +387,8 @@ impl DefCollector { errors.extend(type_check_globals(&mut context.def_interner, resolved_globals.globals)); errors.extend(type_check_functions(&mut context.def_interner, functions)); + errors.extend(type_check_trait_impl_signatures(&mut context.def_interner, &impl_functions)); + errors.extend(type_check_functions(&mut context.def_interner, impl_functions)); errors } } @@ -472,158 +470,24 @@ fn type_check_functions( .into_iter() .flat_map(|(file, func)| { type_check_func(interner, func) - .iter() - .cloned() + .into_iter() .map(|e| (e.into(), file)) .collect::>() }) .collect() } -// TODO(vitkov): Move this out of here and into type_check -#[allow(clippy::too_many_arguments)] -pub(crate) fn check_methods_signatures( - resolver: &mut Resolver, - impl_methods: &[(FileId, FuncId)], - trait_id: TraitId, - trait_name_span: Span, - // These are the generics on the trait itself from the impl. - // E.g. in `impl Foo for Bar`, this is `vec![A, B]`. - trait_generics: Vec, - trait_impl_generic_count: usize, - file_id: FileId, - errors: &mut Vec<(CompilationError, FileId)>, -) { - let self_type = resolver.get_self_type().expect("trait impl must have a Self type").clone(); - let trait_generics = vecmap(trait_generics, |typ| resolver.resolve_type(typ)); - - // Temporarily bind the trait's Self type to self_type so we can type check - let the_trait = resolver.interner.get_trait_mut(trait_id); - the_trait.self_type_typevar.bind(self_type); - - if trait_generics.len() != the_trait.generics.len() { - let error = DefCollectorErrorKind::MismatchGenericCount { - actual_generic_count: trait_generics.len(), - expected_generic_count: the_trait.generics.len(), - // Preferring to use 'here' over a more precise term like 'this reference' - // to try to make the error easier to understand for newer users. - location: "here it", - origin: the_trait.name.to_string(), - span: trait_name_span, - }; - errors.push((error.into(), file_id)); - } - - // We also need to bind the traits generics to the trait's generics on the impl - for (generic, binding) in the_trait.generics.iter().zip(trait_generics) { - generic.bind(binding); - } - - // Temporarily take the trait's methods so we can use both them and a mutable reference - // to the interner within the loop. - let trait_methods = std::mem::take(&mut the_trait.methods); - - for (file_id, func_id) in impl_methods { - let func_name = resolver.interner.function_name(func_id).to_owned(); - - // This is None in the case where the impl block has a method that's not part of the trait. - // If that's the case, a `MethodNotInTrait` error has already been thrown, and we can ignore - // the impl method, since there's nothing in the trait to match its signature against. - if let Some(trait_method) = - trait_methods.iter().find(|method| method.name.0.contents == func_name) - { - let impl_method = resolver.interner.function_meta(func_id); - - let impl_method_generic_count = - impl_method.typ.generic_count() - trait_impl_generic_count; - - // We subtract 1 here to account for the implicit generic `Self` type that is on all - // traits (and thus trait methods) but is not required (or allowed) for users to specify. - let the_trait = resolver.interner.get_trait(trait_id); - let trait_method_generic_count = - trait_method.generics().len() - 1 - the_trait.generics.len(); - - if impl_method_generic_count != trait_method_generic_count { - let trait_name = resolver.interner.get_trait(trait_id).name.clone(); - - let error = DefCollectorErrorKind::MismatchGenericCount { - actual_generic_count: impl_method_generic_count, - expected_generic_count: trait_method_generic_count, - origin: format!("{}::{}", trait_name, func_name), - location: "this method", - span: impl_method.location.span, - }; - errors.push((error.into(), *file_id)); - } - - // This instantiation is technically not needed. We could bind each generic in the - // trait function to the impl's corresponding generic but to do so we'd have to rely - // on the trait function's generics being first in the generic list, since the same - // list also contains the generic `Self` variable, and any generics on the trait itself. - // - // Instantiating the impl method's generics here instead is a bit less precise but - // doesn't rely on any orderings that may be changed. - let impl_function_type = impl_method.typ.instantiate(resolver.interner).0; - - let mut bindings = TypeBindings::new(); - let mut typecheck_errors = Vec::new(); - - if let Type::Function(impl_params, impl_return, _) = impl_function_type.as_monotype() { - if trait_method.arguments().len() != impl_params.len() { - let error = DefCollectorErrorKind::MismatchTraitImplementationNumParameters { - actual_num_parameters: impl_method.parameters.0.len(), - expected_num_parameters: trait_method.arguments().len(), - trait_name: resolver.interner.get_trait(trait_id).name.to_string(), - method_name: func_name.to_string(), - span: impl_method.location.span, - }; - errors.push((error.into(), *file_id)); - } - - // Check the parameters of the impl method against the parameters of the trait method - let args = trait_method.arguments().iter(); - let args_and_params = args.zip(impl_params).zip(&impl_method.parameters.0); - - for (parameter_index, ((expected, actual), (hir_pattern, _, _))) in - args_and_params.enumerate() - { - if expected.try_unify(actual, &mut bindings).is_err() { - typecheck_errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { - method_name: func_name.to_string(), - expected_typ: expected.to_string(), - actual_typ: actual.to_string(), - parameter_span: hir_pattern.span(), - parameter_index: parameter_index + 1, - }); - } - } - - if trait_method.return_type().try_unify(impl_return, &mut bindings).is_err() { - let impl_method = resolver.interner.function_meta(func_id); - let ret_type_span = impl_method.return_type.get_type().span; - let expr_span = ret_type_span.expect("return type must always have a span"); - - let expected_typ = trait_method.return_type().to_string(); - let expr_typ = impl_method.return_type().to_string(); - let error = TypeCheckError::TypeMismatch { expr_typ, expected_typ, expr_span }; - typecheck_errors.push(error); - } - } else { - unreachable!( - "impl_function_type is not a function type, it is: {impl_function_type}" - ); - } - - errors.extend(typecheck_errors.iter().cloned().map(|e| (e.into(), *file_id))); - } - } - - // Now unbind `Self` and the trait's generics - let the_trait = resolver.interner.get_trait_mut(trait_id); - the_trait.set_methods(trait_methods); - the_trait.self_type_typevar.unbind(the_trait.self_type_typevar_id); - - for generic in &the_trait.generics { - generic.unbind(generic.id()); - } +fn type_check_trait_impl_signatures( + interner: &mut NodeInterner, + file_func_ids: &[(FileId, FuncId)], +) -> Vec<(CompilationError, fm::FileId)> { + file_func_ids + .iter() + .flat_map(|(file, func)| { + check_trait_impl_method_matches_declaration(interner, *func) + .into_iter() + .map(|e| (e.into(), *file)) + .collect::>() + }) + .collect() } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs index 77224cc311c..5b2f815d636 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/dc_mod.rs @@ -9,8 +9,8 @@ use crate::{ hir::def_collector::dc_crate::{UnresolvedStruct, UnresolvedTrait}, node_interner::{FunctionModifiers, TraitId, TypeAliasId}, parser::{SortedModule, SortedSubModule}, - FunctionDefinition, Ident, LetStatement, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, - NoirTypeAlias, TraitImplItem, TraitItem, TypeImpl, + FunctionDefinition, Ident, LetStatement, ModuleDeclaration, NoirFunction, NoirStruct, + NoirTrait, NoirTraitImpl, NoirTypeAlias, TraitImplItem, TraitItem, TypeImpl, }; use super::{ @@ -522,15 +522,15 @@ impl<'a> ModCollector<'a> { fn parse_module_declaration( &mut self, context: &mut Context, - mod_name: &Ident, + mod_decl: &ModuleDeclaration, crate_id: CrateId, ) -> Vec<(CompilationError, FileId)> { let mut errors: Vec<(CompilationError, FileId)> = vec![]; let child_file_id = - match find_module(&context.file_manager, self.file_id, &mod_name.0.contents) { + match find_module(&context.file_manager, self.file_id, &mod_decl.ident.0.contents) { Ok(child_file_id) => child_file_id, Err(expected_path) => { - let mod_name = mod_name.clone(); + let mod_name = mod_decl.ident.clone(); let err = DefCollectorErrorKind::UnresolvedModuleDecl { mod_name, expected_path }; errors.push((err.into(), self.file_id)); @@ -538,17 +538,17 @@ impl<'a> ModCollector<'a> { } }; - let location = Location { file: self.file_id, span: mod_name.span() }; + let location = Location { file: self.file_id, span: mod_decl.ident.span() }; if let Some(old_location) = context.visited_files.get(&child_file_id) { let error = DefCollectorErrorKind::ModuleAlreadyPartOfCrate { - mod_name: mod_name.clone(), + mod_name: mod_decl.ident.clone(), span: location.span, }; errors.push((error.into(), location.file)); let error = DefCollectorErrorKind::ModuleOriginallyDefined { - mod_name: mod_name.clone(), + mod_name: mod_decl.ident.clone(), span: old_location.span, }; errors.push((error.into(), old_location.file)); @@ -566,7 +566,7 @@ impl<'a> ModCollector<'a> { ); // Add module into def collector and get a ModuleId - match self.push_child_module(mod_name, child_file_id, true, false) { + match self.push_child_module(&mod_decl.ident, child_file_id, true, false) { Ok(child_mod_id) => { errors.extend(collect_defs( self.def_collector, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs index de45be48c4e..29daf5d6369 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_collector/errors.rs @@ -41,14 +41,6 @@ pub enum DefCollectorErrorKind { OverlappingImplNote { span: Span }, #[error("Cannot `impl` a type defined outside the current crate")] ForeignImpl { span: Span, type_name: String }, - #[error("Mismatched number of parameters in trait implementation")] - MismatchTraitImplementationNumParameters { - actual_num_parameters: usize, - expected_num_parameters: usize, - trait_name: String, - method_name: String, - span: Span, - }, #[error("Mismatched number of generics in {location}")] MismatchGenericCount { actual_generic_count: usize, @@ -176,18 +168,6 @@ impl From for Diagnostic { "".to_string(), trait_path.span(), ), - DefCollectorErrorKind::MismatchTraitImplementationNumParameters { - expected_num_parameters, - actual_num_parameters, - trait_name, - method_name, - span, - } => { - let plural = if expected_num_parameters == 1 { "" } else { "s" }; - let primary_message = format!( - "`{trait_name}::{method_name}` expects {expected_num_parameters} parameter{plural}, but this method has {actual_num_parameters}"); - Diagnostic::simple_error(primary_message, "".to_string(), span) - } DefCollectorErrorKind::MismatchGenericCount { actual_generic_count, expected_generic_count, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index 8721bdb6c3c..1326ffca9f7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -73,7 +73,7 @@ impl CrateDefMap { pub fn collect_defs( crate_id: CrateId, context: &mut Context, - macro_processors: Vec<&dyn MacroProcessor>, + macro_processors: &[&dyn MacroProcessor], ) -> Vec<(CompilationError, FileId)> { // Check if this Crate has already been compiled // XXX: There is probably a better alternative for this. @@ -90,7 +90,7 @@ impl CrateDefMap { let (ast, parsing_errors) = context.parsed_file_results(root_file_id); let mut ast = ast.into_sorted(); - for macro_processor in ¯o_processors { + for macro_processor in macro_processors { match macro_processor.process_untyped_ast(ast.clone(), &crate_id, context) { Ok(processed_ast) => { ast = processed_ast; @@ -115,13 +115,7 @@ impl CrateDefMap { }; // Now we want to populate the CrateDefMap using the DefCollector - errors.extend(DefCollector::collect( - def_map, - context, - ast, - root_file_id, - macro_processors.clone(), - )); + errors.extend(DefCollector::collect(def_map, context, ast, root_file_id, macro_processors)); errors.extend( parsing_errors.iter().map(|e| (e.clone().into(), root_file_id)).collect::>(), 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 d2fe67da38c..1049599f079 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 @@ -80,8 +80,6 @@ pub enum ResolverError { PrivateFunctionCalled { name: String, span: Span }, #[error("{name} is not visible from the current crate")] NonCrateFunctionCalled { name: String, span: Span }, - #[error("Only sized types may be used in the entry point to a program")] - InvalidTypeForEntryPoint { span: Span }, #[error("Nested slices are not supported")] NestedSlices { span: Span }, #[error("#[recursive] attribute is only allowed on entry points to a program")] @@ -309,9 +307,6 @@ impl From for Diagnostic { 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::InvalidTypeForEntryPoint { span } => Diagnostic::simple_error( - "Only sized types may be used in the entry point to a program".to_string(), - "Slices, references, or any type containing them may not be used in main or a contract function".to_string(), 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 e6ac33053a0..9c8418daf80 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 @@ -52,7 +52,7 @@ impl From for CustomDiagnostic { pub fn resolve_import( crate_id: CrateId, - import_directive: ImportDirective, + import_directive: &ImportDirective, def_maps: &BTreeMap, ) -> Result { let def_map = &def_maps[&crate_id]; @@ -62,10 +62,10 @@ pub fn resolve_import( let module_scope = import_directive.module_id; let resolved_namespace = - resolve_path_to_ns(&import_directive, def_map, def_maps, allow_contracts) + resolve_path_to_ns(import_directive, def_map, def_maps, allow_contracts) .map_err(|error| (error, module_scope))?; - let name = resolve_path_name(&import_directive); + let name = resolve_path_name(import_directive); Ok(ResolvedImport { name, resolved_namespace, 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 7f9e48353a7..875d0ceb85e 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 @@ -217,6 +217,7 @@ impl<'a> Resolver<'a> { pub fn resolve_trait_function( &mut self, name: &Ident, + generics: &UnresolvedGenerics, parameters: &[(Ident, UnresolvedType)], return_type: &FunctionReturnType, where_clause: &[UnresolvedTraitConstraint], @@ -237,7 +238,7 @@ impl<'a> Resolver<'a> { is_internal: false, is_unconstrained: false, visibility: FunctionVisibility::Public, // Trait functions are always public - generics: Vec::new(), // self.generics should already be set + generics: generics.clone(), parameters: vecmap(parameters, |(name, typ)| Param { visibility: Visibility::Private, pattern: Pattern::Identifier(name.clone()), @@ -911,10 +912,6 @@ impl<'a> Resolver<'a> { }); } - if self.is_entry_point_function(func) { - self.verify_type_valid_for_program_input(&typ); - } - let pattern = self.resolve_pattern(pattern, DefinitionKind::Local(None)); let typ = self.resolve_type_inner(typ, &mut generics); @@ -979,11 +976,18 @@ impl<'a> Resolver<'a> { self.handle_function_type(&func_id); self.handle_is_function_internal(&func_id); + let direct_generics = func.def.generics.iter(); + let direct_generics = direct_generics + .filter_map(|generic| self.find_generic(&generic.0.contents)) + .map(|(name, typevar, _span)| (name.clone(), typevar.clone())) + .collect(); + FuncMeta { name: name_ident, kind: func.kind, location, typ, + direct_generics, trait_impl: self.current_trait_impl, parameters: parameters.into(), return_type: func.def.return_type.clone(), @@ -991,6 +995,7 @@ impl<'a> Resolver<'a> { return_distinctness: func.def.return_distinctness, has_body: !func.def.body.is_empty(), trait_constraints: self.resolve_trait_constraints(&func.def.where_clause), + is_entry_point: self.is_entry_point_function(func), } } @@ -1256,13 +1261,17 @@ impl<'a> Resolver<'a> { let is_in_stdlib = self.path_resolver.module_id().krate.is_stdlib(); let assert_msg_call_path = if is_in_stdlib { ExpressionKind::Variable(Path { - segments: vec![Ident::from("resolve_assert_message")], + segments: vec![Ident::from("internal"), Ident::from("resolve_assert_message")], kind: PathKind::Crate, span, }) } else { ExpressionKind::Variable(Path { - segments: vec![Ident::from("std"), Ident::from("resolve_assert_message")], + segments: vec![ + Ident::from("std"), + Ident::from("internal"), + Ident::from("resolve_assert_message"), + ], kind: PathKind::Dep, span, }) @@ -1999,89 +2008,6 @@ impl<'a> Resolver<'a> { } HirLiteral::FmtStr(str, fmt_str_idents) } - - /// Only sized types are valid to be used as main's parameters or the parameters to a contract - /// function. If the given type is not sized (e.g. contains a slice or NamedGeneric type), an - /// error is issued. - fn verify_type_valid_for_program_input(&mut self, typ: &UnresolvedType) { - match &typ.typ { - UnresolvedTypeData::FieldElement - | UnresolvedTypeData::Integer(_, _) - | UnresolvedTypeData::Bool - | UnresolvedTypeData::Unit - | UnresolvedTypeData::Error => (), - - UnresolvedTypeData::MutableReference(_) - | UnresolvedTypeData::Function(_, _, _) - | UnresolvedTypeData::FormatString(_, _) - | UnresolvedTypeData::TraitAsType(..) - | UnresolvedTypeData::Unspecified => { - let span = typ.span.expect("Function parameters should always have spans"); - self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); - } - - UnresolvedTypeData::Array(length, element) => { - if let Some(length) = length { - self.verify_type_expression_valid_for_program_input(length); - } else { - let span = typ.span.expect("Function parameters should always have spans"); - self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); - } - self.verify_type_valid_for_program_input(element); - } - UnresolvedTypeData::Expression(expression) => { - self.verify_type_expression_valid_for_program_input(expression); - } - UnresolvedTypeData::String(length) => { - if let Some(length) = length { - self.verify_type_expression_valid_for_program_input(length); - } else { - let span = typ.span.expect("Function parameters should always have spans"); - self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); - } - } - UnresolvedTypeData::Named(path, generics, _) => { - // Since the type is named, we need to resolve it to see what it actually refers to - // in order to check whether it is valid. Since resolving it may lead to a - // resolution error, we have to truncate our error count to the previous count just - // in case. This is to ensure resolution errors are not issued twice when this type - // is later resolved properly. - let error_count = self.errors.len(); - let resolved = self.resolve_named_type(path.clone(), generics.clone(), &mut vec![]); - self.errors.truncate(error_count); - - if !resolved.is_valid_for_program_input() { - let span = typ.span.expect("Function parameters should always have spans"); - self.push_err(ResolverError::InvalidTypeForEntryPoint { span }); - } - } - UnresolvedTypeData::Tuple(elements) => { - for element in elements { - self.verify_type_valid_for_program_input(element); - } - } - UnresolvedTypeData::Parenthesized(typ) => self.verify_type_valid_for_program_input(typ), - } - } - - fn verify_type_expression_valid_for_program_input(&mut self, expr: &UnresolvedTypeExpression) { - match expr { - UnresolvedTypeExpression::Constant(_, _) => (), - UnresolvedTypeExpression::Variable(path) => { - let error_count = self.errors.len(); - let resolved = self.resolve_named_type(path.clone(), vec![], &mut vec![]); - self.errors.truncate(error_count); - - if !resolved.is_valid_for_program_input() { - self.push_err(ResolverError::InvalidTypeForEntryPoint { span: path.span() }); - } - } - UnresolvedTypeExpression::BinaryOperation(lhs, _, rhs, _) => { - self.verify_type_expression_valid_for_program_input(lhs); - self.verify_type_expression_valid_for_program_input(rhs); - } - } - } } /// Gives an error if a user tries to create a mutable reference 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 8f966be312b..5d546954f0d 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 @@ -8,9 +8,7 @@ use crate::{ graph::CrateId, hir::{ def_collector::{ - dc_crate::{ - check_methods_signatures, CompilationError, UnresolvedTrait, UnresolvedTraitImpl, - }, + dc_crate::{CompilationError, UnresolvedTrait, UnresolvedTraitImpl}, errors::{DefCollectorErrorKind, DuplicateType}, }, def_map::{CrateDefMap, ModuleDefId, ModuleId}, @@ -131,6 +129,7 @@ fn resolve_trait_methods( let func_id = unresolved_trait.method_ids[&name.0.contents]; let (_, func_meta) = resolver.resolve_trait_function( name, + generics, parameters, return_type, where_clause, @@ -365,6 +364,7 @@ pub(crate) fn resolve_trait_by_path( Err(_) => Err(DefCollectorErrorKind::TraitNotFound { trait_path: path }), } } + pub(crate) fn resolve_trait_impls( context: &mut Context, traits: Vec, @@ -424,17 +424,6 @@ pub(crate) fn resolve_trait_impls( new_resolver.set_self_type(Some(self_type.clone())); if let Some(trait_id) = maybe_trait_id { - check_methods_signatures( - &mut new_resolver, - &impl_methods, - trait_id, - trait_impl.trait_path.span(), - trait_impl.trait_generics, - trait_impl.generics.len(), - trait_impl.file_id, - errors, - ); - let where_clause = trait_impl .where_clause .into_iter() 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 96d30100d8b..7eacc8eb2d1 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 @@ -44,7 +44,7 @@ pub enum TypeCheckError { #[error("Expected type {expected} is not the same as {actual}")] TypeMismatchWithSource { expected: Type, actual: Type, span: Span, source: Source }, #[error("Expected {expected:?} found {found:?}")] - ArityMisMatch { expected: u16, found: u16, span: Span }, + ArityMisMatch { expected: usize, found: usize, span: Span }, #[error("Return type in a function cannot be public")] PublicReturnType { typ: Type, span: Span }, #[error("Cannot cast type {from}, 'as' is only for primitive field or integer types")] @@ -53,8 +53,10 @@ pub enum TypeCheckError { ExpectedFunction { found: Type, span: Span }, #[error("Type {lhs_type} has no member named {field_name}")] AccessUnknownMember { lhs_type: Type, field_name: String, span: Span }, - #[error("Function expects {expected} parameters but {found} given")] + #[error("Function expects {expected} parameters but {found} were given")] ParameterCountMismatch { expected: usize, found: usize, span: Span }, + #[error("{item} expects {expected} generics but {found} were given")] + GenericCountMismatch { item: String, expected: usize, found: usize, span: Span }, #[error("Only integer and Field types may be casted to")] UnsupportedCast { span: Span }, #[error("Index {index} is out of bounds for this tuple {lhs_type} of length {length}")] @@ -122,6 +124,16 @@ pub enum TypeCheckError { ConstrainedReferenceToUnconstrained { span: Span }, #[error("Slices cannot be returned from an unconstrained runtime to a constrained runtime")] UnconstrainedSliceReturnToConstrained { span: Span }, + #[error("Only sized types may be used in the entry point to a program")] + InvalidTypeForEntryPoint { span: Span }, + #[error("Mismatched number of parameters in trait implementation")] + MismatchTraitImplNumParameters { + actual_num_parameters: usize, + expected_num_parameters: usize, + trait_name: String, + method_name: String, + span: Span, + }, } impl TypeCheckError { @@ -191,6 +203,12 @@ impl From for Diagnostic { let msg = format!("Function expects {expected} parameter{empty_or_s} but {found} {was_or_were} given"); Diagnostic::simple_error(msg, String::new(), span) } + TypeCheckError::GenericCountMismatch { item, expected, found, span } => { + let empty_or_s = if expected == 1 { "" } else { "s" }; + let was_or_were = if found == 1 { "was" } else { "were" }; + let msg = format!("{item} expects {expected} generic{empty_or_s} but {found} {was_or_were} given"); + Diagnostic::simple_error(msg, String::new(), span) + } TypeCheckError::InvalidCast { span, .. } | TypeCheckError::ExpectedFunction { span, .. } | TypeCheckError::AccessUnknownMember { span, .. } @@ -284,6 +302,21 @@ impl From for Diagnostic { let msg = format!("Constraint for `{typ}: {trait_name}` is not needed, another matching impl is already in scope"); Diagnostic::simple_warning(msg, "Unnecessary trait constraint in where clause".into(), span) } + TypeCheckError::InvalidTypeForEntryPoint { span } => Diagnostic::simple_error( + "Only sized types may be used in the entry point to a program".to_string(), + "Slices, references, or any type containing them may not be used in main or a contract function".to_string(), span), + TypeCheckError::MismatchTraitImplNumParameters { + expected_num_parameters, + actual_num_parameters, + trait_name, + method_name, + span, + } => { + let plural = if expected_num_parameters == 1 { "" } else { "s" }; + let primary_message = format!( + "`{trait_name}::{method_name}` expects {expected_num_parameters} parameter{plural}, but this method has {actual_num_parameters}"); + Diagnostic::simple_error(primary_message, "".to_string(), 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 7b854e58fca..7219f4d09c6 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 @@ -863,7 +863,13 @@ impl<'interner> TypeChecker<'interner> { span: op.location.span, }); - self.comparator_operand_type_rules(x_type, y_type, op, span) + let (_, use_impl) = self.comparator_operand_type_rules(x_type, y_type, op, span)?; + + // If the size is not constant, we must fall back to a user-provided impl for + // equality on slices. + let size = x_size.follow_bindings(); + let use_impl = use_impl || size.evaluate_to_u64().is_none(); + Ok((Bool, use_impl)) } (String(x_size), String(y_size)) => { @@ -1034,9 +1040,9 @@ impl<'interner> TypeChecker<'interner> { } ret } + // ignoring env for subtype on purpose Type::Function(parameters, ret, _env) => { - // ignoring env for subtype on purpose - self.bind_function_type_impl(parameters.as_ref(), ret.as_ref(), args.as_ref(), span) + self.bind_function_type_impl(¶meters, &ret, &args, span) } Type::Error => Type::Error, found => { 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 21d1c75a0f2..ab759f454e5 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 @@ -12,11 +12,17 @@ mod expr; mod stmt; pub use errors::TypeCheckError; +use noirc_errors::Span; use crate::{ - hir_def::{expr::HirExpression, stmt::HirStatement, traits::TraitConstraint}, + hir_def::{ + expr::HirExpression, + function::{Param, Parameters}, + stmt::HirStatement, + traits::TraitConstraint, + }, node_interner::{ExprId, FuncId, GlobalId, NodeInterner}, - Type, + Type, TypeBindings, }; use self::errors::Source; @@ -74,6 +80,7 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec Vec, + func_id: FuncId, + param: &Param, + errors: &mut Vec, +) { + let meta = type_checker.interner.function_meta(&func_id); + if meta.is_entry_point && !param.1.is_valid_for_program_input() { + let span = param.0.span(); + errors.push(TypeCheckError::InvalidTypeForEntryPoint { span }); + } +} + fn function_info(interner: &NodeInterner, function_body_id: &ExprId) -> (noirc_errors::Span, bool) { let (expr_span, empty_function) = if let HirExpression::Block(block) = interner.expression(function_body_id) { @@ -162,6 +185,154 @@ fn function_info(interner: &NodeInterner, function_body_id: &ExprId) -> (noirc_e (expr_span, empty_function) } +/// Checks that the type of a function in a trait impl matches the type +/// of the corresponding function declaration in the trait itself. +/// +/// To do this, given a trait such as: +/// `trait Foo { fn foo(...); }` +/// +/// And an impl such as: +/// `impl Foo for Bar { fn foo(...); } ` +/// +/// We have to substitute: +/// - Self for Bar +/// - A for D +/// - B for F +/// +/// Before we can type check. Finally, we must also check that the unification +/// result does not introduce any new bindings. This can happen if the impl +/// function's type is more general than that of the trait function. E.g. +/// `fn baz(a: A, b: B)` when the impl required `fn baz(a: A, b: A)`. +/// +/// This does not type check the body of the impl function. +pub(crate) fn check_trait_impl_method_matches_declaration( + interner: &mut NodeInterner, + function: FuncId, +) -> Vec { + let meta = interner.function_meta(&function); + let method_name = interner.function_name(&function); + let mut errors = Vec::new(); + + let definition_type = meta.typ.as_monotype(); + + let impl_ = + meta.trait_impl.expect("Trait impl function should have a corresponding trait impl"); + let impl_ = interner.get_trait_implementation(impl_); + let impl_ = impl_.borrow(); + let trait_info = interner.get_trait(impl_.trait_id); + + let mut bindings = TypeBindings::new(); + bindings.insert( + trait_info.self_type_typevar_id, + (trait_info.self_type_typevar.clone(), impl_.typ.clone()), + ); + + if trait_info.generics.len() != impl_.trait_generics.len() { + let expected = trait_info.generics.len(); + let found = impl_.trait_generics.len(); + let span = impl_.ident.span(); + let item = trait_info.name.to_string(); + errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); + } + + // Substitute each generic on the trait with the corresponding generic on the impl + for (generic, arg) in trait_info.generics.iter().zip(&impl_.trait_generics) { + bindings.insert(generic.id(), (generic.clone(), arg.clone())); + } + + // If this is None, the trait does not have the corresponding function. + // This error should have been caught in name resolution already so we don't + // issue an error for it here. + if let Some(trait_fn_id) = trait_info.method_ids.get(method_name) { + let trait_fn_meta = interner.function_meta(trait_fn_id); + + if trait_fn_meta.direct_generics.len() != meta.direct_generics.len() { + let expected = trait_fn_meta.direct_generics.len(); + let found = meta.direct_generics.len(); + let span = meta.name.location.span; + let item = method_name.to_string(); + errors.push(TypeCheckError::GenericCountMismatch { item, expected, found, span }); + } + + // Substitute each generic on the trait function with the corresponding generic on the impl function + for ((_, trait_fn_generic), (name, impl_fn_generic)) in + trait_fn_meta.direct_generics.iter().zip(&meta.direct_generics) + { + let arg = Type::NamedGeneric(impl_fn_generic.clone(), name.clone()); + bindings.insert(trait_fn_generic.id(), (trait_fn_generic.clone(), arg)); + } + + let (declaration_type, _) = trait_fn_meta.typ.instantiate_with_bindings(bindings, interner); + + check_function_type_matches_expected_type( + &declaration_type, + definition_type, + method_name, + &meta.parameters, + meta.name.location.span, + &trait_info.name.0.contents, + &mut errors, + ); + } + + errors +} + +fn check_function_type_matches_expected_type( + expected: &Type, + actual: &Type, + method_name: &str, + actual_parameters: &Parameters, + span: Span, + trait_name: &str, + errors: &mut Vec, +) { + let mut bindings = TypeBindings::new(); + // Shouldn't need to unify envs, they should always be equal since they're both free functions + if let (Type::Function(params_a, ret_a, _env_a), Type::Function(params_b, ret_b, _env_b)) = + (expected, actual) + { + if params_a.len() == params_b.len() { + for (i, (a, b)) in params_a.iter().zip(params_b.iter()).enumerate() { + if a.try_unify(b, &mut bindings).is_err() { + errors.push(TypeCheckError::TraitMethodParameterTypeMismatch { + method_name: method_name.to_string(), + expected_typ: a.to_string(), + actual_typ: b.to_string(), + parameter_span: actual_parameters.0[i].0.span(), + parameter_index: i + 1, + }); + } + } + + if ret_b.try_unify(ret_a, &mut bindings).is_err() { + errors.push(TypeCheckError::TypeMismatch { + expected_typ: ret_a.to_string(), + expr_typ: ret_b.to_string(), + expr_span: span, + }); + } + } else { + errors.push(TypeCheckError::MismatchTraitImplNumParameters { + actual_num_parameters: params_b.len(), + expected_num_parameters: params_a.len(), + trait_name: trait_name.to_string(), + method_name: method_name.to_string(), + span, + }); + } + } + + // If result bindings is not empty, a type variable was bound which means the two + // signatures were not a perfect match. Note that this relies on us already binding + // all the expected generics to each other prior to this check. + if !bindings.is_empty() { + let expected_typ = expected.to_string(); + let expr_typ = actual.to_string(); + errors.push(TypeCheckError::TypeMismatch { expected_typ, expr_typ, expr_span: span }); + } +} + impl<'interner> TypeChecker<'interner> { fn new(interner: &'interner mut NodeInterner) -> Self { Self { interner, errors: Vec::new(), trait_constraints: Vec::new(), current_function: None } @@ -329,6 +500,8 @@ mod test { trait_impl: None, return_type: FunctionReturnType::Default(Span::default()), trait_constraints: Vec::new(), + direct_generics: Vec::new(), + is_entry_point: true, }; interner.push_fn_meta(func_meta, func_id); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs index d3ab2a9393b..56543e8185c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/function.rs @@ -1,12 +1,14 @@ use iter_extended::vecmap; use noirc_errors::{Location, Span}; +use std::rc::Rc; + use super::expr::{HirBlockExpression, HirExpression, HirIdent}; use super::stmt::HirPattern; use super::traits::TraitConstraint; use crate::node_interner::{ExprId, NodeInterner, TraitImplId}; use crate::FunctionKind; -use crate::{Distinctness, FunctionReturnType, Type, Visibility}; +use crate::{Distinctness, FunctionReturnType, Type, TypeVariable, Visibility}; /// A Hir function is a block expression /// with a list of statements @@ -103,6 +105,12 @@ pub struct FuncMeta { /// or a Type::Forall for generic functions. pub typ: Type, + /// The set of generics that are declared directly on this function in the source code. + /// This does not include generics from an outer scope, like those introduced by + /// an `impl` block. This also does not include implicit generics added by the compiler + /// such as a trait's `Self` type variable. + pub direct_generics: Vec<(Rc, TypeVariable)>, + pub location: Location, // This flag is needed for the attribute check pass @@ -112,6 +120,10 @@ pub struct FuncMeta { /// The trait impl this function belongs to, if any pub trait_impl: Option, + + /// True if this function is an entry point to the program. + /// For non-contracts, this means the function is `main`. + pub is_entry_point: bool, } impl FuncMeta { 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 b70aa43701c..5ab036eef5b 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 @@ -703,10 +703,10 @@ impl Type { | Type::TraitAsType(..) | Type::NotConstant => false, - // This function is called during name resolution before we've verified aliases - // are not cyclic. As a result, it wouldn't be safe to check this alias' definition - // to see if the aliased type is valid. - Type::Alias(..) => false, + Type::Alias(alias, generics) => { + let alias = alias.borrow(); + alias.get_type(generics).is_valid_for_program_input() + } Type::Array(length, element) => { length.is_valid_for_program_input() && element.is_valid_for_program_input() @@ -1768,9 +1768,11 @@ impl From<&Type> for PrintableType { Type::TypeVariable(_, _) => unreachable!(), Type::NamedGeneric(..) => unreachable!(), Type::Forall(..) => unreachable!(), - Type::Function(_, _, env) => { - PrintableType::Function { env: Box::new(env.as_ref().into()) } - } + Type::Function(arguments, return_type, env) => PrintableType::Function { + arguments: arguments.iter().map(|arg| arg.into()).collect(), + return_type: Box::new(return_type.as_ref().into()), + env: Box::new(env.as_ref().into()), + }, Type::MutableReference(typ) => { PrintableType::MutableReference { typ: Box::new(typ.as_ref().into()) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs index e4e619d5d92..7fcf8e87792 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -1,7 +1,7 @@ use acvm::FieldElement; use iter_extended::vecmap; use noirc_errors::{ - debug_info::{DebugTypes, DebugVariables}, + debug_info::{DebugFunctions, DebugTypes, DebugVariables}, Location, }; @@ -253,6 +253,7 @@ pub struct Program { /// Indicates to a backend whether a SNARK-friendly prover should be used. pub recursive: bool, pub debug_variables: DebugVariables, + pub debug_functions: DebugFunctions, pub debug_types: DebugTypes, } @@ -266,6 +267,7 @@ impl Program { return_visibility: Visibility, recursive: bool, debug_variables: DebugVariables, + debug_functions: DebugFunctions, debug_types: DebugTypes, ) -> Program { Program { @@ -276,6 +278,7 @@ impl Program { return_visibility, recursive, debug_variables, + debug_functions, debug_types, } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug.rs index a8ff4399f99..cf4e0ab792e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug.rs @@ -76,7 +76,7 @@ impl<'interner> Monomorphizer<'interner> { let var_type = self.interner.id_type(call.arguments[DEBUG_VALUE_ARG_SLOT]); let source_var_id = source_var_id.to_u128().into(); // then update the ID used for tracking at runtime - let var_id = self.debug_type_tracker.insert_var(source_var_id, var_type); + let var_id = self.debug_type_tracker.insert_var(source_var_id, &var_type); let interned_var_id = self.intern_var_id(var_id, &call.location); arguments[DEBUG_VAR_ID_ARG_SLOT] = self.expr(interned_var_id)?; Ok(()) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug_types.rs b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug_types.rs index fea073d394f..16b82d1e7b9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug_types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/debug_types.rs @@ -3,7 +3,8 @@ use crate::{ hir_def::types::Type, }; use noirc_errors::debug_info::{ - DebugTypeId, DebugTypes, DebugVarId, DebugVariable, DebugVariables, + DebugFnId, DebugFunction, DebugFunctions, DebugTypeId, DebugTypes, DebugVarId, DebugVariable, + DebugVariables, }; use noirc_printable_type::PrintableType; use std::collections::HashMap; @@ -30,7 +31,10 @@ pub struct DebugTypeTracker { // All instances of tracked variables variables: HashMap, - // Types of tracked variables + // Function metadata collected during instrumentation injection + functions: HashMap, + + // Types of tracked variables and functions types: HashMap, types_reverse: HashMap, @@ -43,34 +47,29 @@ impl DebugTypeTracker { DebugTypeTracker { source_variables: instrumenter.variables.clone(), source_field_names: instrumenter.field_names.clone(), + functions: instrumenter.functions.clone(), ..DebugTypeTracker::default() } } - pub fn extract_vars_and_types(&self) -> (DebugVariables, DebugTypes) { + pub fn extract_vars_and_types(&self) -> (DebugVariables, DebugFunctions, DebugTypes) { let debug_variables = self .variables .clone() .into_iter() .map(|(var_id, (source_var_id, type_id))| { - ( - var_id, - DebugVariable { - name: self.source_variables.get(&source_var_id).cloned().unwrap_or_else( - || { - unreachable!( - "failed to retrieve variable name for {source_var_id:?}" - ); - }, - ), - debug_type_id: type_id, - }, - ) + let var_name = + self.source_variables.get(&source_var_id).cloned().unwrap_or_else(|| { + unreachable!("failed to retrieve variable name for {source_var_id:?}"); + }); + (var_id, DebugVariable { name: var_name, debug_type_id: type_id }) }) .collect(); + + let debug_functions = self.functions.clone().into_iter().collect(); let debug_types = self.types.clone().into_iter().collect(); - (debug_variables, debug_types) + (debug_variables, debug_functions, debug_types) } pub fn resolve_field_index( @@ -83,19 +82,24 @@ impl DebugTypeTracker { .and_then(|field_name| get_field(cursor_type, field_name)) } - pub fn insert_var(&mut self, source_var_id: SourceVarId, var_type: Type) -> DebugVarId { - if !self.source_variables.contains_key(&source_var_id) { - unreachable!("cannot find source debug variable {source_var_id:?}"); - } - - let ptype: PrintableType = var_type.follow_bindings().into(); - let type_id = self.types_reverse.get(&ptype).copied().unwrap_or_else(|| { + fn insert_type(&mut self, the_type: &Type) -> DebugTypeId { + let ptype: PrintableType = the_type.follow_bindings().into(); + self.types_reverse.get(&ptype).copied().unwrap_or_else(|| { let type_id = DebugTypeId(self.next_type_id); self.next_type_id += 1; self.types_reverse.insert(ptype.clone(), type_id); self.types.insert(type_id, ptype); type_id - }); + }) + } + + pub fn insert_var(&mut self, source_var_id: SourceVarId, var_type: &Type) -> DebugVarId { + if !self.source_variables.contains_key(&source_var_id) { + unreachable!("cannot find source debug variable {source_var_id:?}"); + } + + let type_id = self.insert_type(var_type); + // check if we need to instantiate the var with a new type let existing_var_id = self.source_to_debug_vars.get(&source_var_id).and_then(|var_id| { let (_, existing_type_id) = self.variables.get(var_id).unwrap(); 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 ce880401d77..cfd9a61d13f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -165,7 +165,8 @@ pub fn monomorphize_debug( let FuncMeta { return_distinctness, return_visibility, kind, .. } = monomorphizer.interner.function_meta(&main); - let (debug_variables, debug_types) = monomorphizer.debug_type_tracker.extract_vars_and_types(); + let (debug_variables, debug_functions, debug_types) = + monomorphizer.debug_type_tracker.extract_vars_and_types(); let program = Program::new( functions, function_sig, @@ -174,6 +175,7 @@ pub fn monomorphize_debug( *return_visibility, *kind == FunctionKind::Recursive, debug_variables, + debug_functions, debug_types, ); Ok(program) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index 0ff7819c00f..ea96dee8a47 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -14,8 +14,8 @@ mod parser; use crate::token::{Keyword, Token}; use crate::{ast::ImportStatement, Expression, NoirStruct}; use crate::{ - Ident, LetStatement, NoirFunction, NoirTrait, NoirTraitImpl, NoirTypeAlias, Recoverable, - StatementKind, TypeImpl, UseTree, + Ident, LetStatement, ModuleDeclaration, NoirFunction, NoirTrait, NoirTraitImpl, NoirTypeAlias, + Recoverable, StatementKind, TypeImpl, UseTree, }; use chumsky::prelude::*; @@ -28,7 +28,7 @@ pub use parser::parse_program; #[derive(Debug, Clone)] pub(crate) enum TopLevelStatement { Function(NoirFunction), - Module(Ident), + Module(ModuleDeclaration), Import(UseTree), Struct(NoirStruct), Trait(NoirTrait), @@ -220,7 +220,7 @@ pub struct SortedModule { pub globals: Vec, /// Module declarations like `mod foo;` - pub module_decls: Vec, + pub module_decls: Vec, /// Full submodules as in `mod foo { ... definitions ... }` pub submodules: Vec, @@ -229,7 +229,7 @@ pub struct SortedModule { impl std::fmt::Display for SortedModule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { for decl in &self.module_decls { - writeln!(f, "mod {decl};")?; + writeln!(f, "{decl};")?; } for import in &self.imports { @@ -309,7 +309,7 @@ pub enum ItemKind { Impl(TypeImpl), TypeAlias(NoirTypeAlias), Global(LetStatement), - ModuleDecl(Ident), + ModuleDecl(ModuleDeclaration), Submodules(ParsedSubModule), } @@ -380,8 +380,8 @@ impl SortedModule { self.imports.extend(import_stmt.desugar(None)); } - fn push_module_decl(&mut self, mod_name: Ident) { - self.module_decls.push(mod_name); + fn push_module_decl(&mut self, mod_decl: ModuleDeclaration) { + self.module_decls.push(mod_decl); } fn push_submodule(&mut self, submodule: SortedSubModule) { @@ -474,7 +474,7 @@ impl std::fmt::Display for TopLevelStatement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TopLevelStatement::Function(fun) => fun.fmt(f), - TopLevelStatement::Module(m) => write!(f, "mod {m}"), + TopLevelStatement::Module(m) => m.fmt(f), TopLevelStatement::Import(tree) => write!(f, "use {tree}"), TopLevelStatement::Trait(t) => t.fmt(f), TopLevelStatement::TraitImpl(i) => i.fmt(f), 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 75f4a6359bf..383a1ffafc9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -40,9 +40,9 @@ use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Keyword, Token, TokenKind}; use crate::{ BinaryOp, BinaryOpKind, BlockExpression, Distinctness, ForLoopStatement, ForRange, - FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Literal, NoirTypeAlias, - Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, UnresolvedTraitConstraint, - UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, + FunctionReturnType, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, + NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TraitBound, TypeImpl, + UnresolvedTraitConstraint, UnresolvedTypeExpression, UseTree, UseTreeKind, Visibility, }; use chumsky::prelude::*; @@ -370,7 +370,9 @@ fn optional_type_annotation<'a>() -> impl NoirParser + 'a { } fn module_declaration() -> impl NoirParser { - keyword(Keyword::Mod).ignore_then(ident()).map(TopLevelStatement::Module) + keyword(Keyword::Mod) + .ignore_then(ident()) + .map(|ident| TopLevelStatement::Module(ModuleDeclaration { ident })) } fn use_statement() -> impl NoirParser { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index c661cc92eef..3f78bd43ba9 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -81,7 +81,7 @@ mod test { &mut context, program.clone().into_sorted(), root_file_id, - Vec::new(), // No macro processors + &[], // No macro processors )); } (program, context, errors) @@ -535,15 +535,13 @@ mod test { assert!(errors.len() == 1, "Expected 1 error, got: {:?}", errors); for (err, _file_id) in errors { match &err { - CompilationError::DefinitionError( - DefCollectorErrorKind::MismatchTraitImplementationNumParameters { - actual_num_parameters, - expected_num_parameters, - trait_name, - method_name, - .. - }, - ) => { + CompilationError::TypeError(TypeCheckError::MismatchTraitImplNumParameters { + actual_num_parameters, + expected_num_parameters, + trait_name, + method_name, + .. + }) => { assert_eq!(actual_num_parameters, &1_usize); assert_eq!(expected_num_parameters, &2_usize); assert_eq!(method_name, "default"); @@ -1206,4 +1204,13 @@ fn lambda$f1(mut env$l1: (Field)) -> Field { "#; assert_eq!(get_program_errors(src).len(), 1); } + + #[test] + fn type_aliases_in_entry_point() { + let src = r#" + type Foo = u8; + fn main(_x: Foo) {} + "#; + assert_eq!(get_program_errors(src).len(), 0); + } } diff --git a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs index 24f4f275a14..60f233cd86d 100644 --- a/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_printable_type/src/lib.rs @@ -33,6 +33,8 @@ pub enum PrintableType { length: u64, }, Function { + arguments: Vec, + return_type: Box, env: Box, }, MutableReference { @@ -176,8 +178,8 @@ fn to_string(value: &PrintableValue, typ: &PrintableType) -> Option { output.push_str("false"); } } - (PrintableValue::Field(_), PrintableType::Function { .. }) => { - output.push_str("<>"); + (PrintableValue::Field(_), PrintableType::Function { arguments, return_type, .. }) => { + output.push_str(&format!("< {:?}>>", arguments, return_type,)); } (_, PrintableType::MutableReference { .. }) => { output.push_str("<>"); @@ -350,7 +352,7 @@ pub fn decode_value( PrintableValue::Struct(struct_map) } - PrintableType::Function { env } => { + PrintableType::Function { env, .. } => { let field_element = field_iterator.next().unwrap(); let func_ref = PrintableValue::Field(field_element); // we want to consume the fields from the environment, but for now they are not actually printed diff --git a/noir/noir-repo/compiler/wasm/src/noir/package.ts b/noir/noir-repo/compiler/wasm/src/noir/package.ts index 81178e6ae96..2856798273a 100644 --- a/noir/noir-repo/compiler/wasm/src/noir/package.ts +++ b/noir/noir-repo/compiler/wasm/src/noir/package.ts @@ -105,7 +105,12 @@ export class Package { handles .filter((handle) => SOURCE_EXTENSIONS.find((ext) => handle.endsWith(ext))) .map(async (file) => { - const suffix = file.replace(this.#srcPath, ''); + // Github deps are directly added to the file manager, which causes them to be missing the absolute path to the source file + // and only include the extraction directory relative to the fm root directory + // This regexp ensures we remove the "real" source path for all dependencies, providing the compiler with what it expects for each source file: + // -> for bin/contract packages + // -> for libs + const suffix = file.replace(new RegExp(`.*${this.#srcPath}`), ''); return { path: this.getType() === 'lib' ? `${alias ? alias : this.#config.package.name}${suffix}` : file, source: (await fm.readFile(file, 'utf-8')).toString(), diff --git a/noir/noir-repo/deny.toml b/noir/noir-repo/deny.toml index 72150f08a3c..578f8427263 100644 --- a/noir/noir-repo/deny.toml +++ b/noir/noir-repo/deny.toml @@ -2,11 +2,13 @@ # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] -vulnerability = "deny" -unmaintained = "warn" -unsound = "warn" +version = 2 yanked = "warn" -notice = "warn" + +ignore = [ + "RUSTSEC-2020-0168", # mach unmaintained + "RUSTSEC-2020-0016" # net2 unmaintained +] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: @@ -32,9 +34,8 @@ skip = [] skip-tree = [] [licenses] -unlicensed = "deny" +version = 2 confidence-threshold = 0.9 -# copyleft = "deny" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses diff --git a/noir/noir-repo/docs/docs/noir/standard_library/containers/hashmap.md b/noir/noir-repo/docs/docs/noir/standard_library/containers/hashmap.md new file mode 100644 index 00000000000..093b6d38d11 --- /dev/null +++ b/noir/noir-repo/docs/docs/noir/standard_library/containers/hashmap.md @@ -0,0 +1,270 @@ +--- +title: HashMap +keywords: [noir, map, hash, hashmap] +sidebar_position: 1 +--- + +`HashMap` is used to efficiently store and look up key-value pairs. + +`HashMap` is a bounded type which can store anywhere from zero to `MaxLen` total elements. +Note that due to hash collisions, the actual maximum number of elements stored by any particular +hashmap is likely lower than `MaxLen`. This is true even with cryptographic hash functions since +every hash value will be performed modulo `MaxLen`. + +When creating `HashMap`s, the `MaxLen` generic should always be specified if it is not already +known. Otherwise, the compiler may infer a different value for `MaxLen` (such as zero), which +will likely change the result of the program. This behavior is set to become an error in future +versions instead. + +Example: + +```rust +// Create a mapping from Fields to u32s with a maximum length of 12 +// using a pedersen hash +let mut map: HashMap> = HashMap::default(); + +map.insert(1, 2); +map.insert(3, 4); + +let two = map.get(1).unwrap(); +``` + +## Methods + +### default + +#include_code default noir_stdlib/src/collections/map.nr rust + +Creates a fresh, empty HashMap. + +When using this function, always make sure to specify the maximum size of the hash map. + +This is the same `default` from the `Default` implementation given further below. It is +repeated here for convenience since it is the recommended way to create a hashmap. + +Example: + +#include_code default_example test_programs/execution_success/hashmap/src/main.nr rust + +Because `HashMap` has so many generic arguments that are likely to be the same throughout +your program, it may be helpful to create a type alias: + +#include_code type_alias test_programs/execution_success/hashmap/src/main.nr rust + +### with_hasher + +#include_code with_hasher noir_stdlib/src/collections/map.nr rust + +Creates a hashmap with an existing `BuildHasher`. This can be used to ensure multiple +hashmaps are created with the same hasher instance. + +Example: + +#include_code with_hasher_example test_programs/execution_success/hashmap/src/main.nr rust + +### get + +#include_code get noir_stdlib/src/collections/map.nr rust + +Retrieves a value from the hashmap, returning `Option::none()` if it was not found. + +Example: + +#include_code get_example test_programs/execution_success/hashmap/src/main.nr rust + +### insert + +#include_code insert noir_stdlib/src/collections/map.nr rust + +Inserts a new key-value pair into the map. If the key was already in the map, its +previous value will be overridden with the newly provided one. + +Example: + +#include_code insert_example test_programs/execution_success/hashmap/src/main.nr rust + +### remove + +#include_code remove noir_stdlib/src/collections/map.nr rust + +Removes the given key-value pair from the map. If the key was not already present +in the map, this does nothing. + +Example: + +#include_code remove_example test_programs/execution_success/hashmap/src/main.nr rust + +### is_empty + +#include_code is_empty noir_stdlib/src/collections/map.nr rust + +True if the length of the hash map is empty. + +Example: + +#include_code is_empty_example test_programs/execution_success/hashmap/src/main.nr rust + +### len + +#include_code len noir_stdlib/src/collections/map.nr rust + +Returns the current length of this hash map. + +Example: + +#include_code len_example test_programs/execution_success/hashmap/src/main.nr rust + +### capacity + +#include_code capacity noir_stdlib/src/collections/map.nr rust + +Returns the maximum capacity of this hashmap. This is always equal to the capacity +specified in the hashmap's type. + +Unlike hashmaps in general purpose programming languages, hashmaps in Noir have a +static capacity that does not increase as the map grows larger. Thus, this capacity +is also the maximum possible element count that can be inserted into the hashmap. +Due to hash collisions (modulo the hashmap length), it is likely the actual maximum +element count will be lower than the full capacity. + +Example: + +#include_code capacity_example test_programs/execution_success/hashmap/src/main.nr rust + +### clear + +#include_code clear noir_stdlib/src/collections/map.nr rust + +Clears the hashmap, removing all key-value pairs from it. + +Example: + +#include_code clear_example test_programs/execution_success/hashmap/src/main.nr rust + +### contains_key + +#include_code contains_key noir_stdlib/src/collections/map.nr rust + +True if the hashmap contains the given key. Unlike `get`, this will not also return +the value associated with the key. + +Example: + +#include_code contains_key_example test_programs/execution_success/hashmap/src/main.nr rust + +### entries + +#include_code entries noir_stdlib/src/collections/map.nr rust + +Returns a vector of each key-value pair present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +#include_code entries_example test_programs/execution_success/hashmap/src/main.nr rust + +### keys + +#include_code keys noir_stdlib/src/collections/map.nr rust + +Returns a vector of each key present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +#include_code keys_example test_programs/execution_success/hashmap/src/main.nr rust + +### values + +#include_code values noir_stdlib/src/collections/map.nr rust + +Returns a vector of each value present in the hashmap. + +The length of the returned vector is always equal to the length of the hashmap. + +Example: + +#include_code values_example test_programs/execution_success/hashmap/src/main.nr rust + +### iter_mut + +#include_code iter_mut noir_stdlib/src/collections/map.nr rust + +Iterates through each key-value pair of the HashMap, setting each key-value pair to the +result returned from the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If this is not desired, use `iter_values_mut` if only values need to be mutated, +or `entries` if neither keys nor values need to be mutated. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +#include_code iter_mut_example test_programs/execution_success/hashmap/src/main.nr rust + +### iter_keys_mut + +#include_code iter_keys_mut noir_stdlib/src/collections/map.nr rust + +Iterates through the HashMap, mutating each key to the result returned from +the given function. + +Note that since keys can be mutated, the HashMap needs to be rebuilt as it is iterated +through. If only iteration is desired and the keys are not intended to be mutated, +prefer using `entries` instead. + +The iteration order is left unspecified. As a result, if two keys are mutated to become +equal, which of the two values that will be present for the key in the resulting map is also unspecified. + +Example: + +#include_code iter_keys_mut_example test_programs/execution_success/hashmap/src/main.nr rust + +### iter_values_mut + +#include_code iter_values_mut noir_stdlib/src/collections/map.nr rust + +Iterates through the HashMap, applying the given function to each value and mutating the +value to equal the result. This function is more efficient than `iter_mut` and `iter_keys_mut` +because the keys are untouched and the underlying hashmap thus does not need to be reordered. + +Example: + +#include_code iter_values_mut_example test_programs/execution_success/hashmap/src/main.nr rust + +### retain + +#include_code retain noir_stdlib/src/collections/map.nr rust + +Retains only the key-value pairs for which the given function returns true. +Any key-value pairs for which the function returns false will be removed from the map. + +Example: + +#include_code retain_example test_programs/execution_success/hashmap/src/main.nr rust + +## Trait Implementations + +### default + +#include_code default noir_stdlib/src/collections/map.nr rust + +Constructs an empty HashMap. + +Example: + +#include_code default_example test_programs/execution_success/hashmap/src/main.nr rust + +### eq + +#include_code eq noir_stdlib/src/collections/map.nr rust + +Checks if two HashMaps are equal. + +Example: + +#include_code eq_example test_programs/execution_success/hashmap/src/main.nr rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx index 99b7f830a20..c2c0624dfad 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx +++ b/noir/noir-repo/docs/docs/noir/standard_library/cryptographic_primitives/eddsa.mdx @@ -15,6 +15,14 @@ Verifier for EdDSA signatures fn eddsa_poseidon_verify(public_key_x : Field, public_key_y : Field, signature_s: Field, signature_r8_x: Field, signature_r8_y: Field, message: Field) -> bool ``` +It is also possible to specify the hash algorithm used for the signature by using the `eddsa_verify_with_hasher` function with a parameter implementing the Hasher trait. For instance, if you want to use Poseidon2 instead, you can do the following: +```rust +use dep::std::hash::poseidon2::Poseidon2Hasher; + +let mut hasher = Poseidon2Hasher::default(); +eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher); +``` + ## eddsa::eddsa_to_pub diff --git a/noir/noir-repo/noir_stdlib/src/collections/map.nr b/noir/noir-repo/noir_stdlib/src/collections/map.nr index 056299b4238..2d76acf1f3a 100644 --- a/noir/noir-repo/noir_stdlib/src/collections/map.nr +++ b/noir/noir-repo/noir_stdlib/src/collections/map.nr @@ -3,6 +3,7 @@ use crate::collections::vec::Vec; use crate::option::Option; use crate::default::Default; use crate::hash::{Hash, Hasher, BuildHasher}; +use crate::collections::bounded_vec::BoundedVec; // We use load factor α_max = 0.75. // Upon exceeding it, assert will fail in order to inform the user @@ -78,21 +79,26 @@ impl Slot { // it is very unlikely to be after - performance will be heavily degraded. impl HashMap { // Creates a new instance of HashMap with specified BuildHasher. + // docs:start:with_hasher pub fn with_hasher(_build_hasher: B) -> Self where B: BuildHasher { + // docs:end:with_hasher let _table = [Slot::default(); N]; let _len = 0; Self { _table, _len, _build_hasher } } // Clears the map, removing all key-value entries. + // docs:start:clear pub fn clear(&mut self) { + // docs:end:clear self._table = [Slot::default(); N]; self._len = 0; } // Returns true if the map contains a value for the specified key. + // docs:start:contains_key pub fn contains_key( self, key: K @@ -101,89 +107,80 @@ impl HashMap { K: Hash + Eq, B: BuildHasher, H: Hasher { + // docs:end:contains_key self.get(key).is_some() } // Returns true if the map contains no elements. + // docs:start:is_empty pub fn is_empty(self) -> bool { + // docs:end:is_empty self._len == 0 } - // Get the Option<(K, V) array of valid entries - // with a length of map capacity. First len() elements - // are safe to unwrap_unchecked(), whilst remaining - // are guaranteed to be Option::none(). - // - // This design is reasoned by compile-time limitations and - // temporary nested slices ban. - pub fn entries(self) -> [Option<(K, V)>; N] { - let mut entries = [Option::none(); N]; - let mut valid_amount = 0; + // Returns a BoundedVec of all valid entries in this HashMap. + // The length of the returned vector will always match the length of this HashMap. + // docs:start:entries + pub fn entries(self) -> BoundedVec<(K, V), N> { + // docs:end:entries + let mut entries = BoundedVec::new(); for slot in self._table { if slot.is_valid() { - entries[valid_amount] = slot.key_value(); - valid_amount += 1; + // SAFETY: slot.is_valid() should ensure there is a valid key-value pairing here + let key_value = slot.key_value().unwrap_unchecked(); + entries.push(key_value); } } - let msg = f"Amount of valid elements should have been {self._len} times, but got {valid_amount}."; - assert(valid_amount == self._len, msg); + let msg = f"Amount of valid elements should have been {self._len} times, but got {entries.len()}."; + assert(entries.len() == self._len, msg); entries } - // Get the Option array of valid keys - // with a length of map capacity. First len() elements - // are safe to unwrap_unchecked(), whilst remaining - // are guaranteed to be Option::none(). - // - // This design is reasoned by compile-time limitations and - // temporary nested slices ban. - pub fn keys(self) -> [Option; N] { - let mut keys = [Option::none(); N]; - let mut valid_amount = 0; + // Returns a BoundedVec containing all the keys within this HashMap. + // The length of the returned vector will always match the length of this HashMap. + // docs:start:keys + pub fn keys(self) -> BoundedVec { + // docs:end:keys + let mut keys = BoundedVec::new(); for slot in self._table { if slot.is_valid() { let (key, _) = slot.key_value_unchecked(); - keys[valid_amount] = Option::some(key); - valid_amount += 1; + keys.push(key); } } - let msg = f"Amount of valid elements should have been {self._len} times, but got {valid_amount}."; - assert(valid_amount == self._len, msg); + let msg = f"Amount of valid elements should have been {self._len} times, but got {keys.len()}."; + assert(keys.len() == self._len, msg); keys } - // Get the Option array of valid values - // with a length of map capacity. First len() elements - // are safe to unwrap_unchecked(), whilst remaining - // are guaranteed to be Option::none(). - // - // This design is reasoned by compile-time limitations and - // temporary nested slices ban. - pub fn values(self) -> [Option; N] { - let mut values = [Option::none(); N]; - let mut valid_amount = 0; + // Returns a BoundedVec containing all the values within this HashMap. + // The length of the returned vector will always match the length of this HashMap. + // docs:start:values + pub fn values(self) -> BoundedVec { + // docs:end:values + let mut values = BoundedVec::new(); for slot in self._table { if slot.is_valid() { let (_, value) = slot.key_value_unchecked(); - values[valid_amount] = Option::some(value); - valid_amount += 1; + values.push(value); } } - let msg = f"Amount of valid elements should have been {self._len} times, but got {valid_amount}."; - assert(valid_amount == self._len, msg); + let msg = f"Amount of valid elements should have been {self._len} times, but got {values.len()}."; + assert(values.len() == self._len, msg); values } // For each key-value entry applies mutator function. + // docs:start:iter_mut pub fn iter_mut( &mut self, f: fn(K, V) -> (K, V) @@ -192,12 +189,13 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:iter_mut let mut entries = self.entries(); let mut new_map = HashMap::with_hasher(self._build_hasher); for i in 0..N { if i < self._len { - let entry = entries[i].unwrap_unchecked(); + let entry = entries.get_unchecked(i); let (key, value) = f(entry.0, entry.1); new_map.insert(key, value); } @@ -207,6 +205,7 @@ impl HashMap { } // For each key applies mutator function. + // docs:start:iter_keys_mut pub fn iter_keys_mut( &mut self, f: fn(K) -> K @@ -215,12 +214,13 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:iter_keys_mut let mut entries = self.entries(); let mut new_map = HashMap::with_hasher(self._build_hasher); for i in 0..N { if i < self._len { - let entry = entries[i].unwrap_unchecked(); + let entry = entries.get_unchecked(i); let (key, value) = (f(entry.0), entry.1); new_map.insert(key, value); } @@ -230,7 +230,9 @@ impl HashMap { } // For each value applies mutator function. + // docs:start:iter_values_mut pub fn iter_values_mut(&mut self, f: fn(V) -> V) { + // docs:end:iter_values_mut for i in 0..N { let mut slot = self._table[i]; if slot.is_valid() { @@ -242,7 +244,9 @@ impl HashMap { } // Retains only the elements specified by the predicate. + // docs:start:retain pub fn retain(&mut self, f: fn(K, V) -> bool) { + // docs:end:retain for index in 0..N { let mut slot = self._table[index]; if slot.is_valid() { @@ -257,16 +261,21 @@ impl HashMap { } // Amount of active key-value entries. + // docs:start:len pub fn len(self) -> u64 { + // docs:end:len self._len } // Get the compile-time map capacity. + // docs:start:capacity pub fn capacity(_self: Self) -> u64 { + // docs:end:capacity N } // Get the value by key. If it does not exist, returns none(). + // docs:start:get pub fn get( self, key: K @@ -275,6 +284,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:get let mut result = Option::none(); let hash = self.hash(key); @@ -300,6 +310,7 @@ impl HashMap { } // Insert key-value entry. In case key was already present, value is overridden. + // docs:start:insert pub fn insert( &mut self, key: K, @@ -309,6 +320,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:insert self.assert_load_factor(); let hash = self.hash(key); @@ -340,7 +352,8 @@ impl HashMap { } } - // Remove key-value entry. If key is not present, HashMap remains unchanged. + // Removes a key-value entry. If key is not present, HashMap remains unchanged. + // docs:start:remove pub fn remove( &mut self, key: K @@ -349,6 +362,7 @@ impl HashMap { K: Eq + Hash, B: BuildHasher, H: Hasher { + // docs:end:remove let hash = self.hash(key); let mut break = false; @@ -409,6 +423,7 @@ impl HashMap { // Equality class on HashMap has to test that they have // equal sets of key-value entries, // thus one is a subset of the other and vice versa. +// docs:start:eq impl Eq for HashMap where K: Eq + Hash, @@ -416,7 +431,8 @@ where B: BuildHasher, H: Hasher { - fn eq(self, other: HashMap) -> bool{ + fn eq(self, other: HashMap) -> bool { +// docs:end:eq let mut equal = false; if self.len() == other.len(){ @@ -443,12 +459,14 @@ where } } +// docs:start:default impl Default for HashMap where B: BuildHasher + Default, H: Hasher + Default { - fn default() -> Self{ + fn default() -> Self { +// docs:end:default let _build_hasher = B::default(); let map: HashMap = HashMap::with_hasher(_build_hasher); map diff --git a/noir/noir-repo/noir_stdlib/src/eddsa.nr b/noir/noir-repo/noir_stdlib/src/eddsa.nr index 966bc1da2a1..3aff6043ffd 100644 --- a/noir/noir-repo/noir_stdlib/src/eddsa.nr +++ b/noir/noir-repo/noir_stdlib/src/eddsa.nr @@ -1,6 +1,8 @@ use crate::hash::poseidon; use crate::ec::consts::te::baby_jubjub; use crate::ec::tecurve::affine::Point as TEPoint; +use crate::hash::{Hash, Hasher, BuildHasher, BuildHasherDefault}; +use crate::hash::poseidon::PoseidonHasher; // Returns true if signature is valid pub fn eddsa_poseidon_verify( @@ -11,6 +13,28 @@ pub fn eddsa_poseidon_verify( signature_r8_y: Field, message: Field ) -> bool { + let mut hasher = PoseidonHasher::default(); + eddsa_verify_with_hasher( + pub_key_x, + pub_key_y, + signature_s, + signature_r8_x, + signature_r8_y, + message, + &mut hasher + ) +} + +pub fn eddsa_verify_with_hasher( + pub_key_x: Field, + pub_key_y: Field, + signature_s: Field, + signature_r8_x: Field, + signature_r8_y: Field, + message: Field, + hasher: &mut H +) -> bool +where H: Hasher { // Verifies by testing: // S * B8 = R8 + H(R8, A, m) * A8 let bjj = baby_jubjub(); @@ -23,7 +47,12 @@ pub fn eddsa_poseidon_verify( // Ensure S < Subgroup Order assert(signature_s.lt(bjj.suborder)); // Calculate the h = H(R, A, msg) - let hash: Field = poseidon::bn254::hash_5([signature_r8_x, signature_r8_y, pub_key_x, pub_key_y, message]); + signature_r8_x.hash(hasher); + signature_r8_y.hash(hasher); + pub_key_x.hash(hasher); + pub_key_y.hash(hasher); + message.hash(hasher); + let hash: Field = (*hasher).finish(); // Calculate second part of the right side: right2 = h*8*A // Multiply by 8 by doubling 3 times. This also ensures that the result is in the subgroup. let pub_key_mul_2 = bjj.curve.add(pub_key, pub_key); diff --git a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr index 10c0a48917c..db8a32d7909 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/mimc.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/mimc.nr @@ -1,3 +1,6 @@ +use crate::hash::Hasher; +use crate::default::Default; + // mimc-p/p implementation // constants are (publicly generated) random numbers, for instance using keccak as a ROM. // You must use constants generated for the native field @@ -16,13 +19,8 @@ fn mimc(x: Field, k: Field, constants: [Field; N], exp: Field) -> Field { } global MIMC_BN254_ROUNDS = 91; -//mimc implementation with hardcoded parameters for BN254 curve. -#[field(bn254)] -pub fn mimc_bn254(array: [Field; N]) -> Field { - //mimc parameters - let exponent = 7; - //generated from seed "mimc" using keccak256 - let constants: [Field; MIMC_BN254_ROUNDS] = [ +//generated from seed "mimc" using keccak256 +global MIMC_BN254_CONSTANTS: [Field; MIMC_BN254_ROUNDS] = [ 0, 20888961410941983456478427210666206549300505294776164667214940546594746570981, 15265126113435022738560151911929040668591755459209400716467504685752745317193, @@ -116,10 +114,46 @@ pub fn mimc_bn254(array: [Field; N]) -> Field { 13602139229813231349386885113156901793661719180900395818909719758150455500533 ]; +//mimc implementation with hardcoded parameters for BN254 curve. +#[field(bn254)] +pub fn mimc_bn254(array: [Field; N]) -> Field { + let exponent = 7; let mut r = 0; for elem in array { - let h = mimc(elem, r, constants, exponent); + let h = mimc(elem, r, MIMC_BN254_CONSTANTS, exponent); r = r + elem + h; } r } + +struct MimcHasher{ + _state: [Field], + _len: u64, +} + +impl Hasher for MimcHasher { + #[field(bn254)] + fn finish(self) -> Field { + let exponent = 7; + let mut r = 0; + for i in 0..self._len { + let h = mimc(self._state[i], r, MIMC_BN254_CONSTANTS, exponent); + r = r + self._state[i] + h; + } + r + } + + fn write(&mut self, input: [Field]){ + self._state = self._state.append(input); + self._len += input.len(); + } +} + +impl Default for MimcHasher{ + fn default() -> Self{ + MimcHasher{ + _state: [], + _len: 0, + } + } +} diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr index b1a7c4a2367..7f99ad36316 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon.nr @@ -1,5 +1,7 @@ mod bn254; // Instantiations of Poseidon for prime field of the same order as BN254 use crate::field::modulus_num_bits; +use crate::hash::Hasher; +use crate::default::Default; struct PoseidonConfig { t: Field, // Width, i.e. state size @@ -100,3 +102,77 @@ fn apply_matrix(a: [Field; M], x: [Field; N]) -> [Field; N] { y } + +struct PoseidonHasher{ + _state: [Field], + _len: u64, +} + +impl Hasher for PoseidonHasher { + #[field(bn254)] + fn finish(self) -> Field { + let mut result = 0; + assert(self._len < 16); + if self._len == 1 { + result = bn254::hash_1([self._state[0]]); + } + if self._len == 2 { + result = bn254::hash_2([self._state[0],self._state[1]]); + } + if self._len == 3 { + result = bn254::hash_3([self._state[0],self._state[1],self._state[2]]); + } + if self._len == 4 { + result = bn254::hash_4([self._state[0],self._state[1],self._state[2],self._state[3]]); + } + if self._len == 5 { + result = bn254::hash_5([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4]]); + } + if self._len == 6 { + result = bn254::hash_6([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5]]); + } + if self._len == 7 { + result = bn254::hash_7([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6]]); + } + if self._len == 8 { + result = bn254::hash_8([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7]]); + } + if self._len == 9 { + result = bn254::hash_9([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8]]); + } + if self._len == 10 { + result = bn254::hash_10([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9]]); + } + if self._len == 11 { + result = bn254::hash_11([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10]]); + } + if self._len == 12 { + result = bn254::hash_12([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11]]); + } + if self._len == 13 { + result = bn254::hash_13([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12]]); + } + if self._len == 14 { + result = bn254::hash_14([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12], self._state[13]]); + } + if self._len == 15 { + result = bn254::hash_15([self._state[0],self._state[1],self._state[2],self._state[3],self._state[4], self._state[5], self._state[6], self._state[7], self._state[8], self._state[9], self._state[10], self._state[11], self._state[12], self._state[13], self._state[14]]); + } + + result + } + + fn write(&mut self, input: [Field]){ + self._state = self._state.append(input); + self._len += input.len(); + } +} + +impl Default for PoseidonHasher{ + fn default() -> Self{ + PoseidonHasher{ + _state: [], + _len: 0, + } + } +} diff --git a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr index 40eea029e82..52229f18dbd 100644 --- a/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr +++ b/noir/noir-repo/noir_stdlib/src/hash/poseidon2.nr @@ -1,3 +1,6 @@ +use crate::hash::Hasher; +use crate::default::Default; + global RATE = 3; struct Poseidon2 { @@ -9,7 +12,7 @@ struct Poseidon2 { impl Poseidon2 { - pub fn hash(input: [Field; N], message_size: u32) -> Field { + pub fn hash(input: [Field; N], message_size: u64) -> Field { if message_size == N { Poseidon2::hash_internal(input, N, false) } else { @@ -92,12 +95,12 @@ impl Poseidon2 { result } - fn hash_internal(input: [Field; N], in_len: u32, is_variable_length: bool) -> Field { + fn hash_internal(input: [Field; N], in_len: u64, is_variable_length: bool) -> Field { let two_pow_64 = 18446744073709551616; let iv : Field = (in_len as Field) * two_pow_64; let mut sponge = Poseidon2::new(iv); for i in 0..input.len() { - if i as u32 < in_len { + if i < in_len { sponge.absorb(input[i]); } } @@ -111,3 +114,33 @@ impl Poseidon2 { sponge.squeeze() } } + +struct Poseidon2Hasher{ + _state: [Field], + _len: u64, +} + +impl Hasher for Poseidon2Hasher { + fn finish(self) -> Field { + let iv : Field = (self._state.len() as Field)*18446744073709551616; // iv = (self._state.len() << 64) + let mut sponge = Poseidon2::new(iv); + for i in 0..self._len { + sponge.absorb(self._state[i]); + } + sponge.squeeze() + } + + fn write(&mut self, input: [Field]){ + self._state = self._state.append(input); + self._len += input.len(); + } +} + +impl Default for Poseidon2Hasher{ + fn default() -> Self{ + Poseidon2Hasher{ + _state: [], + _len: 0, + } + } +} diff --git a/noir/noir-repo/noir_stdlib/src/internal.nr b/noir/noir-repo/noir_stdlib/src/internal.nr new file mode 100644 index 00000000000..8d5c01dda7f --- /dev/null +++ b/noir/noir-repo/noir_stdlib/src/internal.nr @@ -0,0 +1,12 @@ +// This file contains functions which should only be used in calls injected by the Noir compiler. +// These functions should not be called manually in user code. +// +// Changes to this file will not be considered breaking. + +#[oracle(assert_message)] +unconstrained fn assert_message_oracle(_input: T) {} +unconstrained pub fn resolve_assert_message(input: T, condition: bool) { + if !condition { + assert_message_oracle(input); + } +} diff --git a/noir/noir-repo/noir_stdlib/src/lib.nr b/noir/noir-repo/noir_stdlib/src/lib.nr index ebde4b88858..90c04472066 100644 --- a/noir/noir-repo/noir_stdlib/src/lib.nr +++ b/noir/noir-repo/noir_stdlib/src/lib.nr @@ -26,6 +26,7 @@ mod default; mod prelude; mod uint128; mod bigint; +mod internal; // Oracle calls are required to be wrapped in an unconstrained function // Thus, the only argument to the `println` oracle is expected to always be an ident diff --git a/noir/noir-repo/noirc_macros/Cargo.toml b/noir/noir-repo/noirc_macros/Cargo.toml deleted file mode 100644 index 699e6b01cae..00000000000 --- a/noir/noir-repo/noirc_macros/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "noirc_macros" -version.workspace = true -authors.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -noirc_frontend.workspace = true -iter-extended.workspace = true \ No newline at end of file diff --git a/noir/noir-repo/noirc_macros/src/lib.rs b/noir/noir-repo/noirc_macros/src/lib.rs deleted file mode 100644 index 9a916843200..00000000000 --- a/noir/noir-repo/noirc_macros/src/lib.rs +++ /dev/null @@ -1,73 +0,0 @@ -use noirc_frontend::hir::def_collector::dc_crate::UnresolvedFunctions; -use noirc_frontend::hir::def_collector::dc_crate::UnresolvedTraitImpl; -use noirc_frontend::macros_api::parse_program; -use noirc_frontend::macros_api::HirContext; -use noirc_frontend::macros_api::SortedModule; -use noirc_frontend::macros_api::{CrateId, FileId}; -use noirc_frontend::macros_api::{MacroError, MacroProcessor}; - -pub struct AssertMessageMacro; - -impl MacroProcessor for AssertMessageMacro { - fn process_untyped_ast( - &self, - ast: SortedModule, - crate_id: &CrateId, - _context: &HirContext, - ) -> Result { - transform(ast, crate_id) - } - - fn process_unresolved_traits_impls( - &self, - _crate_id: &CrateId, - _context: &mut HirContext, - _unresolved_traits_impls: &[UnresolvedTraitImpl], - _collected_functions: &mut Vec, - ) -> Result<(), (MacroError, FileId)> { - Ok(()) - } - - // This macro does not need to process any information after name resolution - fn process_typed_ast( - &self, - _crate_id: &CrateId, - _context: &mut HirContext, - ) -> Result<(), (MacroError, FileId)> { - Ok(()) - } -} - -fn transform(ast: SortedModule, crate_id: &CrateId) -> Result { - let ast = add_resolve_assert_message_funcs(ast, crate_id)?; - - Ok(ast) -} - -fn add_resolve_assert_message_funcs( - mut ast: SortedModule, - crate_id: &CrateId, -) -> Result { - if !crate_id.is_stdlib() { - return Ok(ast); - } - let assert_message_oracles = " - #[oracle(assert_message)] - unconstrained fn assert_message_oracle(_input: T) {} - unconstrained pub fn resolve_assert_message(input: T, condition: bool) { - if !condition { - assert_message_oracle(input); - } - }"; - - let (assert_msg_funcs_ast, errors) = parse_program(assert_message_oracles); - assert_eq!(errors.len(), 0, "Failed to parse Noir macro code. This is either a bug in the compiler or the Noir macro code"); - - let assert_msg_funcs_ast = assert_msg_funcs_ast.into_sorted(); - - for func in assert_msg_funcs_ast.functions { - ast.functions.push(func) - } - - Ok(ast) -} diff --git a/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/Nargo.toml b/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/Nargo.toml new file mode 100644 index 00000000000..a878566a372 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "brillig_cow_assign" +type = "bin" +authors = [""] +compiler_version = ">=0.23.0" + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/Prover.toml b/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/Prover.toml new file mode 100644 index 00000000000..882c73b83f8 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/Prover.toml @@ -0,0 +1,2 @@ +items_to_update = 10 +index = 6 diff --git a/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/src/main.nr b/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/src/main.nr new file mode 100644 index 00000000000..e5c3e2bd2f5 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/brillig_cow_assign/src/main.nr @@ -0,0 +1,23 @@ +global N = 10; + +unconstrained fn main() { + let mut arr = [0; N]; + let mut mid_change = arr; + + for i in 0..N { + if i == N / 2 { + mid_change = arr; + } + arr[i] = 27; + } + + // Expect: + // arr = [27, 27, 27, 27, 27, 27, 27, 27, 27, 27] + // mid_change = [27, 27, 27, 27, 27, 0, 0, 0, 0, 0] + + let modified_i = N / 2 + 1; + assert_eq(arr[modified_i], 27); + + // Fail here! + assert(mid_change[modified_i] != 27); +} diff --git a/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr b/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr index 4404ffe75f7..fd1a95ee5fb 100644 --- a/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/eddsa/src/main.nr @@ -2,7 +2,9 @@ use dep::std::compat; use dep::std::ec::consts::te::baby_jubjub; use dep::std::ec::tecurve::affine::Point as TEPoint; use dep::std::hash; -use dep::std::eddsa::{eddsa_to_pub, eddsa_poseidon_verify}; +use dep::std::eddsa::{eddsa_to_pub, eddsa_poseidon_verify, eddsa_verify_with_hasher}; +use dep::std::hash::poseidon2::Poseidon2Hasher; +use dep::std::hash::pedersen::PedersenHasher; fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) { // Skip this test for non-bn254 backends @@ -48,5 +50,11 @@ fn main(msg: pub Field, _priv_key_a: Field, _priv_key_b: Field) { assert(!eddsa_poseidon_verify(pub_key_a.x, pub_key_a.y, s_b, r8_b.x, r8_b.y, msg)); // User A's signature over the message can't be used with another message assert(!eddsa_poseidon_verify(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg + 1)); + // Using a different hash should fail + let mut hasher = Poseidon2Hasher::default(); + assert(!eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher)); + // Using a different hash should fail + let mut hasher = PedersenHasher::default(); + assert(!eddsa_verify_with_hasher(pub_key_a.x, pub_key_a.y, s_a, r8_a.x, r8_a.y, msg, &mut hasher)); } } diff --git a/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr b/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr index 597a5c0b7de..4d2cbd45993 100644 --- a/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/hashmap/src/main.nr @@ -37,6 +37,8 @@ fn main(input: [Entry; HASHMAP_LEN]) { test_retain(); test_iterators(); test_mut_iterators(); + + doc_tests(); } // Insert, get, remove. @@ -154,9 +156,9 @@ fn test_iterators() { hashmap.insert(5, 7); hashmap.insert(11, 13); - let keys: [K; 3] = cut(hashmap.keys()).map(|k: Option| k.unwrap_unchecked()).sort_via(K_CMP); - let values: [V; 3] = cut(hashmap.values()).map(|v: Option| v.unwrap_unchecked()).sort_via(V_CMP); - let entries: [(K, V); 3] = cut(hashmap.entries()).map(|e: Option<(K, V)>| e.unwrap_unchecked()).sort_via(KV_CMP); + let keys: [K; 3] = cut(hashmap.keys()).sort_via(K_CMP); + let values: [V; 3] = cut(hashmap.values()).sort_via(V_CMP); + let entries: [(K, V); 3] = cut(hashmap.entries()).sort_via(KV_CMP); assert(keys == [2, 5, 11], "Got incorrect iteration of keys."); assert(values == [3, 7, 13], "Got incorrect iteration of values."); @@ -177,8 +179,8 @@ fn test_mut_iterators() { let f = |v: V| -> V{ v * 5}; hashmap.iter_values_mut(f); - let keys: [K; 3] = cut(hashmap.keys()).map(|k: Option| k.unwrap_unchecked()).sort_via(K_CMP); - let values: [V; 3] = cut(hashmap.values()).map(|v: Option| v.unwrap_unchecked()).sort_via(V_CMP); + let keys: [K; 3] = cut(hashmap.keys()).sort_via(K_CMP); + let values: [V; 3] = cut(hashmap.values()).sort_via(V_CMP); assert(keys == [6, 15, 33], f"Got incorrect iteration of keys: {keys}"); assert(values == [15, 35, 65], "Got incorrect iteration of values."); @@ -186,7 +188,175 @@ fn test_mut_iterators() { let f = |k: K, v: V| -> (K, V){(k * 2, v * 2)}; hashmap.iter_mut(f); - let entries: [(K, V); 3] = cut(hashmap.entries()).map(|e: Option<(K, V)>| e.unwrap_unchecked()).sort_via(KV_CMP); + let entries: [(K, V); 3] = cut(hashmap.entries()).sort_via(KV_CMP); assert(entries == [(12, 30), (30, 70), (66, 130)], "Got incorrect iteration of entries."); } + +// docs:start:type_alias +type MyMap = HashMap>; +// docs:end:type_alias + +/// Tests examples from the stdlib hashmap documentation +fn doc_tests() { + // docs:start:default_example + let hashmap: HashMap> = HashMap::default(); + assert(hashmap.is_empty()); + // docs:end:default_example + + // docs:start:with_hasher_example + let my_hasher: BuildHasherDefault = Default::default(); + let hashmap: HashMap> = HashMap::with_hasher(my_hasher); + assert(hashmap.is_empty()); + // docs:end:with_hasher_example + + // docs:start:insert_example + let mut map: HashMap> = HashMap::default(); + map.insert(12, 42); + assert(map.len() == 1); + // docs:end:insert_example + + get_example(map); + + // docs:start:remove_example + map.remove(12); + assert(map.is_empty()); + + // If a key was not present in the map, remove does nothing + map.remove(12); + assert(map.is_empty()); + // docs:end:remove_example + + // docs:start:is_empty_example + assert(map.is_empty()); + + map.insert(1, 2); + assert(!map.is_empty()); + + map.remove(1); + assert(map.is_empty()); + // docs:end:is_empty_example + + // docs:start:len_example + // This is equivalent to checking map.is_empty() + assert(map.len() == 0); + + map.insert(1, 2); + map.insert(3, 4); + map.insert(5, 6); + assert(map.len() == 3); + + // 3 was already present as a key in the hash map, so the length is unchanged + map.insert(3, 7); + assert(map.len() == 3); + + map.remove(1); + assert(map.len() == 2); + // docs:end:len_example + + // docs:start:capacity_example + let empty_map: HashMap> = HashMap::default(); + assert(empty_map.len() == 0); + assert(empty_map.capacity() == 42); + // docs:end:capacity_example + + // docs:start:clear_example + assert(!map.is_empty()); + map.clear(); + assert(map.is_empty()); + // docs:end:clear_example + + // docs:start:contains_key_example + if map.contains_key(7) { + let value = map.get(7); + assert(value.is_some()); + } else { + println("No value for key 7!"); + } + // docs:end:contains_key_example + + entries_examples(map); + iter_examples(map); + + // docs:start:retain_example + map.retain(|k, v| (k != 0) & (v != 0)); + // docs:end:retain_example + + // docs:start:eq_example + let mut map1: HashMap> = HashMap::default(); + let mut map2: HashMap> = HashMap::default(); + + map1.insert(1, 2); + map1.insert(3, 4); + + map2.insert(3, 4); + map2.insert(1, 2); + + assert(map1 == map2); + // docs:end:eq_example +} + +// docs:start:get_example +fn get_example(map: HashMap>) { + let x = map.get(12); + + if x.is_some() { + assert(x.unwrap() == 42); + } +} +// docs:end:get_example + +fn entries_examples(map: HashMap>) { + // docs:start:entries_example + let entries = map.entries(); + + // The length of a hashmap may not be compile-time known, so we + // need to loop over its capacity instead + for i in 0..map.capacity() { + if i < entries.len() { + let (key, value) = entries.get(i); + println(f"{key} -> {value}"); + } + } + // docs:end:entries_example + + // docs:start:keys_example + let keys = map.keys(); + + for i in 0..keys.max_len() { + if i < keys.len() { + let key = keys.get_unchecked(i); + let value = map.get(key).unwrap_unchecked(); + println(f"{key} -> {value}"); + } + } + // docs:end:keys_example + + // docs:start:values_example + let values = map.values(); + + for i in 0..values.max_len() { + if i < values.len() { + let value = values.get_unchecked(i); + println(f"Found value {value}"); + } + } + // docs:end:values_example +} + +fn iter_examples(mut map: HashMap>) { + // docs:start:iter_mut_example + // Add 1 to each key in the map, and double the value associated with that key. + map.iter_mut(|k, v| (k + 1, v * 2)); + // docs:end:iter_mut_example + + // docs:start:iter_keys_mut_example + // Double each key, leaving the value associated with that key untouched + map.iter_keys_mut(|k| k * 2); + // docs:end:iter_keys_mut_example + + // docs:start:iter_values_mut_example + // Halve each value + map.iter_values_mut(|v| v / 2); + // docs:end:iter_values_mut_example +} diff --git a/noir/noir-repo/test_programs/execution_success/hashmap/src/utils.nr b/noir/noir-repo/test_programs/execution_success/hashmap/src/utils.nr index 45c9ca9bbf7..ee73245a902 100644 --- a/noir/noir-repo/test_programs/execution_success/hashmap/src/utils.nr +++ b/noir/noir-repo/test_programs/execution_success/hashmap/src/utils.nr @@ -1,10 +1,10 @@ -// Compile-time: cuts the M first elements from the [T; N] array. -pub(crate) fn cut(input: [T; N]) -> [T; M] { - assert(M as u64 < N as u64, "M should be less than N."); +// Compile-time: cuts the M first elements from the BoundedVec. +pub(crate) fn cut(input: BoundedVec) -> [T; M] { + assert(M < N, "M should be less than N."); - let mut new = [dep::std::unsafe::zeroed(); M]; + let mut new = BoundedVec::new(); for i in 0..M { - new[i] = input[i]; + new.push(input.get(i)); } - new + new.storage() } diff --git a/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash/src/main.nr b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash/src/main.nr index 939b99595c7..a1607956190 100644 --- a/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/poseidon_bn254_hash/src/main.nr @@ -9,7 +9,7 @@ fn main(x1: [Field; 2], y1: pub Field, x2: [Field; 4], y2: pub Field, x3: [Field let hash2 = poseidon::bn254::hash_4(x2); assert(hash2 == y2); - let hash3 = poseidon2::Poseidon2::hash(x3, x3.len() as u32); + let hash3 = poseidon2::Poseidon2::hash(x3, x3.len()); assert(hash3 == y3); } // docs:end:poseidon diff --git a/noir/noir-repo/test_programs/execution_success/regression_4449/Nargo.toml b/noir/noir-repo/test_programs/execution_success/regression_4449/Nargo.toml new file mode 100644 index 00000000000..925420a03a8 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_4449/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "regression_4449" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/regression_4449/Prover.toml b/noir/noir-repo/test_programs/execution_success/regression_4449/Prover.toml new file mode 100644 index 00000000000..81af476bcc9 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_4449/Prover.toml @@ -0,0 +1,3 @@ + +x = 0xbd +result = [204, 59, 83, 197, 18, 1, 128, 43, 247, 28, 104, 225, 106, 13, 20, 187, 42, 26, 67, 150, 48, 75, 238, 168, 121, 247, 142, 160, 71, 222, 97, 188] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/regression_4449/src/main.nr b/noir/noir-repo/test_programs/execution_success/regression_4449/src/main.nr new file mode 100644 index 00000000000..454a93f5d1a --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/regression_4449/src/main.nr @@ -0,0 +1,14 @@ +// Regression test for issue #4449 +use dep::std; + +fn main(x: u8, result: [u8; 32]) { + let x = x % 31; + let mut digest = [0; 32]; + for i in 0..70 { + let y = x + i; + let a = [y, x, 32, 0, y + 1, y - 1, y - 2, 5]; + digest = std::sha256::digest(a); + } + + assert(digest == result); +} diff --git a/noir/noir-repo/test_programs/execution_success/slices/src/main.nr b/noir/noir-repo/test_programs/execution_success/slices/src/main.nr index eca42a660c4..e44edf872a4 100644 --- a/noir/noir-repo/test_programs/execution_success/slices/src/main.nr +++ b/noir/noir-repo/test_programs/execution_success/slices/src/main.nr @@ -50,6 +50,10 @@ fn main(x: Field, y: pub Field) { // The parameters to this function must come from witness values (inputs to main) regression_merge_slices(x, y); regression_2370(); + + regression_4418(x); + regression_slice_call_result(x, y); + regression_4506(); } // Ensure that slices of struct/tuple values work. fn regression_2083() { @@ -297,3 +301,32 @@ fn regression_2370() { let mut slice = []; slice = [1, 2, 3]; } + +fn regression_4418(x: Field) { + let mut crash = x.to_be_bytes(32); + + if x != 0 { + crash[0] = 10; + } +} + +fn regression_slice_call_result(x: Field, y: Field) { + let mut slice = merge_slices_return(x, y); + if x != 0 { + slice = slice.push_back(5); + slice = slice.push_back(10); + } else { + slice = slice.push_back(5); + } + assert(slice.len() == 5); + assert(slice[0] == 0); + assert(slice[1] == 0); + assert(slice[2] == 10); + assert(slice[3] == 5); + assert(slice[4] == 10); +} + +fn regression_4506() { + let slice: [Field] = [1, 2, 3]; + assert(slice == slice); +} diff --git a/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs b/noir/noir-repo/tooling/bb_abstraction_leaks/build.rs index f9effd5d991..0bd2b1c076a 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.24.0"; +const VERSION: &str = "0.26.3"; const TAG: &str = formatcp!("aztec-packages-v{}", VERSION); const API_URL: &str = diff --git a/noir/noir-repo/tooling/debugger/ignored-tests.txt b/noir/noir-repo/tooling/debugger/ignored-tests.txt index c472e828739..231d4d897a9 100644 --- a/noir/noir-repo/tooling/debugger/ignored-tests.txt +++ b/noir/noir-repo/tooling/debugger/ignored-tests.txt @@ -1,21 +1,13 @@ array_dynamic_blackbox_input -array_sort -assign_ex +bigint bit_shifts_comptime -brillig_cow -brillig_nested_arrays brillig_references brillig_to_bytes_integration debug_logs double_verify_nested_proof double_verify_proof modulus -nested_array_dynamic -nested_array_in_slice -nested_arrays_from_brillig references scalar_mul signed_comparison -simple_2d_array to_bytes_integration -bigint diff --git a/noir/noir-repo/tooling/debugger/src/context.rs b/noir/noir-repo/tooling/debugger/src/context.rs index 515edf0bb06..a3ee89263a4 100644 --- a/noir/noir-repo/tooling/debugger/src/context.rs +++ b/noir/noir-repo/tooling/debugger/src/context.rs @@ -8,11 +8,14 @@ use acvm::pwg::{ }; use acvm::{BlackBoxFunctionSolver, FieldElement}; -use nargo::artifacts::debug::DebugArtifact; +use codespan_reporting::files::{Files, SimpleFile}; +use fm::FileId; +use nargo::artifacts::debug::{DebugArtifact, StackFrame}; use nargo::errors::{ExecutionError, Location}; use nargo::NargoError; -use noirc_printable_type::{PrintableType, PrintableValue}; +use noirc_driver::DebugFile; +use std::collections::BTreeMap; use std::collections::{hash_set::Iter, HashSet}; #[derive(Debug)] @@ -29,6 +32,7 @@ pub(super) struct DebugContext<'a, B: BlackBoxFunctionSolver> { foreign_call_executor: Box, debug_artifact: &'a DebugArtifact, breakpoints: HashSet, + source_to_opcodes: BTreeMap>, } impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { @@ -39,12 +43,14 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { initial_witness: WitnessMap, foreign_call_executor: Box, ) -> Self { + let source_to_opcodes = build_source_to_opcode_debug_mappings(debug_artifact); Self { acvm: ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness), brillig_solver: None, foreign_call_executor, debug_artifact, breakpoints: HashSet::new(), + source_to_opcodes, } } @@ -100,10 +106,50 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.debug_artifact .file_map .get(&location.file) - .map(|file| file.path.starts_with("__debug/")) + .map(is_debug_file_in_debug_crate) .unwrap_or(false) } + /// Find an opcode location matching a source code location + // We apply some heuristics here, and there are four possibilities for the + // return value of this function: + // 1. the source location is not found -> None + // 2. an exact unique location is found (very rare) -> Some(opcode_location) + // 3. an exact but not unique location is found, ie. a source location may + // be mapped to multiple opcodes, and those may be disjoint, for example for + // functions called multiple times throughout the program + // -> return the first opcode in program order that matches the source location + // 4. exact location is not found, so an opcode for a nearby source location + // is returned (this again could actually be more than one opcodes) + // -> return the opcode for the next source line that is mapped + pub(super) fn find_opcode_for_source_location( + &self, + file_id: &FileId, + line: i64, + ) -> Option { + let line = line as usize; + let Some(line_to_opcodes) = self.source_to_opcodes.get(file_id) else { + return None; + }; + 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 + let mut index = index; + while index > 0 && line_to_opcodes[index - 1].0 == line { + index -= 1; + } + line_to_opcodes[index].1 + } + Err(index) => { + if index >= line_to_opcodes.len() { + return None; + } + line_to_opcodes[index].1 + } + }; + Some(found_index) + } + /// Returns the callstack in source code locations for the currently /// executing opcode. This can be `None` if the execution finished (and /// `get_current_opcode_location()` returns `None`) or if the opcode is not @@ -128,6 +174,9 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { &self, opcode_location: &OpcodeLocation, ) -> Vec { + // TODO: this assumes we're debugging a program (ie. the DebugArtifact + // will contain a single DebugInfo), but this assumption doesn't hold + // for contracts self.debug_artifact.debug_symbols[0] .opcode_location(opcode_location) .map(|source_locations| { @@ -467,10 +516,14 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } - pub(super) fn get_variables(&self) -> Vec<(&str, &PrintableValue, &PrintableType)> { + pub(super) fn get_variables(&self) -> Vec { return self.foreign_call_executor.get_variables(); } + pub(super) fn current_stack_frame(&self) -> Option { + return self.foreign_call_executor.current_stack_frame(); + } + fn breakpoint_reached(&self) -> bool { if let Some(location) = self.get_current_opcode_location() { self.breakpoints.contains(&location) @@ -526,6 +579,52 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } +fn is_debug_file_in_debug_crate(debug_file: &DebugFile) -> bool { + debug_file.path.starts_with("__debug/") +} + +/// Builds a map from FileId to an ordered vector of tuples with line +/// numbers and opcode locations corresponding to those line numbers +fn build_source_to_opcode_debug_mappings( + debug_artifact: &DebugArtifact, +) -> BTreeMap> { + if debug_artifact.debug_symbols.is_empty() { + return BTreeMap::new(); + } + let locations = &debug_artifact.debug_symbols[0].locations; + let simple_files: BTreeMap<_, _> = debug_artifact + .file_map + .iter() + .filter(|(_, debug_file)| !is_debug_file_in_debug_crate(debug_file)) + .map(|(file_id, debug_file)| { + ( + file_id, + SimpleFile::new(debug_file.path.to_str().unwrap(), debug_file.source.as_str()), + ) + }) + .collect(); + + let mut result: BTreeMap> = BTreeMap::new(); + locations.iter().for_each(|(opcode_location, source_locations)| { + source_locations.iter().for_each(|source_location| { + let span = source_location.span; + let file_id = source_location.file; + let Some(file) = simple_files.get(&file_id) else { + return; + }; + let Ok(line_index) = file.line_index((), span.start() as usize) else { + return; + }; + let line_number = line_index + 1; + + result.entry(file_id).or_default().push((line_number, *opcode_location)); + }); + }); + result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| (x.0, x.1))); + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/noir/noir-repo/tooling/debugger/src/dap.rs b/noir/noir-repo/tooling/debugger/src/dap.rs index 7e67a26b257..7c722ed0a61 100644 --- a/noir/noir-repo/tooling/debugger/src/dap.rs +++ b/noir/noir-repo/tooling/debugger/src/dap.rs @@ -5,7 +5,6 @@ use std::str::FromStr; use acvm::acir::circuit::{Circuit, OpcodeLocation}; use acvm::acir::native_types::WitnessMap; use acvm::BlackBoxFunctionSolver; -use codespan_reporting::files::{Files, SimpleFile}; use crate::context::DebugCommandResult; use crate::context::DebugContext; @@ -30,15 +29,16 @@ use nargo::artifacts::debug::DebugArtifact; use fm::FileId; use noirc_driver::CompiledProgram; +type BreakpointId = i64; + pub struct DapSession<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> { server: Server, context: DebugContext<'a, B>, debug_artifact: &'a DebugArtifact, running: bool, - source_to_opcodes: BTreeMap>, - next_breakpoint_id: i64, - instruction_breakpoints: Vec<(OpcodeLocation, i64)>, - source_breakpoints: BTreeMap>, + next_breakpoint_id: BreakpointId, + instruction_breakpoints: Vec<(OpcodeLocation, BreakpointId)>, + source_breakpoints: BTreeMap>, } enum ScopeReferences { @@ -57,8 +57,6 @@ impl From for ScopeReferences { } } -// BTreeMap - impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { pub fn new( server: Server, @@ -67,7 +65,6 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, ) -> Self { - let source_to_opcodes = Self::build_source_to_opcode_debug_mappings(debug_artifact); let context = DebugContext::new( solver, circuit, @@ -79,7 +76,6 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { server, context, debug_artifact, - source_to_opcodes, running: false, next_breakpoint_id: 1, instruction_breakpoints: vec![], @@ -87,46 +83,6 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { } } - /// Builds a map from FileId to an ordered vector of tuples with line - /// numbers and opcode locations corresponding to those line numbers - fn build_source_to_opcode_debug_mappings( - debug_artifact: &'a DebugArtifact, - ) -> BTreeMap> { - if debug_artifact.debug_symbols.is_empty() { - return BTreeMap::new(); - } - let locations = &debug_artifact.debug_symbols[0].locations; - let simple_files: BTreeMap<_, _> = debug_artifact - .file_map - .iter() - .map(|(file_id, debug_file)| { - ( - file_id, - SimpleFile::new(debug_file.path.to_str().unwrap(), debug_file.source.as_str()), - ) - }) - .collect(); - - let mut result: BTreeMap> = BTreeMap::new(); - locations.iter().for_each(|(opcode_location, source_locations)| { - if source_locations.is_empty() { - return; - } - let source_location = source_locations[0]; - let span = source_location.span; - let file_id = source_location.file; - let Ok(line_index) = &simple_files[&file_id].line_index((), span.start() as usize) - else { - return; - }; - let line_number = line_index + 1; - - result.entry(file_id).or_default().push((line_number, *opcode_location)); - }); - result.iter_mut().for_each(|(_, file_locations)| file_locations.sort_by_key(|x| x.0)); - result - } - fn send_stopped_event(&mut self, reason: StoppedEventReason) -> Result<(), ServerError> { let description = format!("{:?}", &reason); self.server.send_event(Event::Stopped(StoppedEventBody { @@ -230,6 +186,8 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { } fn build_stack_trace(&self) -> Vec { + let stack_frames = self.context.get_variables(); + self.context .get_source_call_stack() .iter() @@ -239,9 +197,15 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { self.debug_artifact.location_line_number(*source_location).unwrap(); let column_number = self.debug_artifact.location_column_number(*source_location).unwrap(); + + let name = match stack_frames.get(index) { + Some(frame) => format!("{} {}", frame.function_name, index), + None => format!("frame #{index}"), + }; + StackFrame { id: index as i64, - name: format!("frame #{index}"), + name, source: Some(Source { path: self.debug_artifact.file_map[&source_location.file] .path @@ -422,7 +386,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { Ok(()) } - fn get_next_breakpoint_id(&mut self) -> i64 { + fn get_next_breakpoint_id(&mut self) -> BreakpointId { let id = self.next_breakpoint_id; self.next_breakpoint_id += 1; id @@ -493,36 +457,6 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { found.map(|iter| *iter.0) } - // TODO: there are four possibilities for the return value of this function: - // 1. the source location is not found -> None - // 2. an exact unique location is found -> Some(opcode_location) - // 3. an exact but not unique location is found (ie. a source location may - // be mapped to multiple opcodes, and those may be disjoint, for example for - // functions called multiple times throughout the program) - // 4. exact location is not found, so an opcode for a nearby source location - // is returned (this again could actually be more than one opcodes) - // Case 3 is not supported yet, and 4 is not correctly handled. - fn find_opcode_for_source_location( - &self, - file_id: &FileId, - line: i64, - ) -> Option { - let line = line as usize; - let Some(line_to_opcodes) = self.source_to_opcodes.get(file_id) else { - return None; - }; - let found_index = match line_to_opcodes.binary_search_by(|x| x.0.cmp(&line)) { - Ok(index) => line_to_opcodes[index].1, - Err(index) => { - if index >= line_to_opcodes.len() { - return None; - } - line_to_opcodes[index].1 - } - }; - Some(found_index) - } - fn map_source_breakpoints(&mut self, args: &SetBreakpointsArguments) -> Vec { let Some(ref source) = &args.source.path else { return vec![]; @@ -539,7 +473,8 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { .iter() .map(|breakpoint| { let line = breakpoint.line; - let Some(location) = self.find_opcode_for_source_location(&file_id, line) else { + let Some(location) = self.context.find_opcode_for_source_location(&file_id, line) + else { return Breakpoint { verified: false, message: Some(String::from( @@ -608,16 +543,20 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { } fn build_local_variables(&self) -> Vec { - let mut variables: Vec<_> = self - .context - .get_variables() + let Some(current_stack_frame) = self.context.current_stack_frame() else { + return vec![]; + }; + + let mut variables = current_stack_frame + .variables .iter() .map(|(name, value, _var_type)| Variable { name: String::from(*name), value: format!("{:?}", *value), ..Variable::default() }) - .collect(); + .collect::>(); + variables.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap()); variables } diff --git a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs index 68c4d3947b0..25f126ff490 100644 --- a/noir/noir-repo/tooling/debugger/src/foreign_calls.rs +++ b/noir/noir-repo/tooling/debugger/src/foreign_calls.rs @@ -3,17 +3,19 @@ use acvm::{ pwg::ForeignCallWaitInfo, }; use nargo::{ - artifacts::debug::{DebugArtifact, DebugVars}, + artifacts::debug::{DebugArtifact, DebugVars, StackFrame}, ops::{DefaultForeignCallExecutor, ForeignCallExecutor, NargoForeignCallResult}, }; -use noirc_errors::debug_info::DebugVarId; -use noirc_printable_type::{ForeignCallError, PrintableType, PrintableValue}; +use noirc_errors::debug_info::{DebugFnId, DebugVarId}; +use noirc_printable_type::ForeignCallError; pub(crate) enum DebugForeignCall { VarAssign, VarDrop, MemberAssign(u32), DerefAssign, + FnEnter, + FnExit, } impl DebugForeignCall { @@ -28,13 +30,16 @@ impl DebugForeignCall { "__debug_var_assign" => Some(DebugForeignCall::VarAssign), "__debug_var_drop" => Some(DebugForeignCall::VarDrop), "__debug_deref_assign" => Some(DebugForeignCall::DerefAssign), + "__debug_fn_enter" => Some(DebugForeignCall::FnEnter), + "__debug_fn_exit" => Some(DebugForeignCall::FnExit), _ => None, } } } pub trait DebugForeignCallExecutor: ForeignCallExecutor { - fn get_variables(&self) -> Vec<(&str, &PrintableValue, &PrintableType)>; + fn get_variables(&self) -> Vec; + fn current_stack_frame(&self) -> Option; } pub struct DefaultDebugForeignCallExecutor { @@ -57,23 +62,33 @@ impl DefaultDebugForeignCallExecutor { } pub fn load_artifact(&mut self, artifact: &DebugArtifact) { - artifact.debug_symbols.iter().for_each(|info| { - self.debug_vars.insert_variables(&info.variables); - self.debug_vars.insert_types(&info.types); - }); + // TODO: handle loading from the correct DebugInfo when we support + // debugging contracts + let Some(info) = artifact.debug_symbols.get(0) else { + return; + }; + self.debug_vars.insert_debug_info(info); } } impl DebugForeignCallExecutor for DefaultDebugForeignCallExecutor { - fn get_variables(&self) -> Vec<(&str, &PrintableValue, &PrintableType)> { + fn get_variables(&self) -> Vec { self.debug_vars.get_variables() } + + fn current_stack_frame(&self) -> Option { + self.debug_vars.current_stack_frame() + } } fn debug_var_id(value: &Value) -> DebugVarId { DebugVarId(value.to_u128() as u32) } +fn debug_fn_id(value: &Value) -> DebugFnId { + DebugFnId(value.to_u128() as u32) +} + impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { fn execute( &mut self, @@ -136,6 +151,19 @@ impl ForeignCallExecutor for DefaultDebugForeignCallExecutor { } Ok(ForeignCallResult::default().into()) } + Some(DebugForeignCall::FnEnter) => { + let fcp_fn_id = &foreign_call.inputs[0]; + let ForeignCallParam::Single(fn_id_value) = fcp_fn_id else { + panic!("unexpected foreign call parameter in fn enter: {fcp_fn_id:?}") + }; + let fn_id = debug_fn_id(fn_id_value); + self.debug_vars.push_fn(fn_id); + Ok(ForeignCallResult::default().into()) + } + Some(DebugForeignCall::FnExit) => { + self.debug_vars.pop_fn(); + Ok(ForeignCallResult::default().into()) + } None => self.executor.execute(foreign_call), } } diff --git a/noir/noir-repo/tooling/debugger/src/repl.rs b/noir/noir-repo/tooling/debugger/src/repl.rs index 8441dbde9be..41dbf604f99 100644 --- a/noir/noir-repo/tooling/debugger/src/repl.rs +++ b/noir/noir-repo/tooling/debugger/src/repl.rs @@ -337,11 +337,13 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { } pub fn show_vars(&self) { - let vars = self.context.get_variables(); - for (var_name, value, var_type) in vars.iter() { - let printable_value = - PrintableValueDisplay::Plain((*value).clone(), (*var_type).clone()); - println!("{var_name}:{var_type:?} = {}", printable_value); + for frame in self.context.get_variables() { + println!("{}({})", frame.function_name, frame.function_params.join(", ")); + for (var_name, value, var_type) in frame.variables.iter() { + let printable_value = + PrintableValueDisplay::Plain((*value).clone(), (*var_type).clone()); + println!(" {var_name}:{var_type:?} = {}", printable_value); + } } } @@ -530,7 +532,7 @@ pub fn run( .add( "vars", command! { - "show variable values available at this point in execution", + "show variables for each function scope available at this point in execution", () => || { ref_context.borrow_mut().show_vars(); Ok(CommandStatus::Done) diff --git a/noir/noir-repo/tooling/debugger/src/source_code_printer.rs b/noir/noir-repo/tooling/debugger/src/source_code_printer.rs index b5ffdb12d01..e298eb8aadd 100644 --- a/noir/noir-repo/tooling/debugger/src/source_code_printer.rs +++ b/noir/noir-repo/tooling/debugger/src/source_code_printer.rs @@ -30,7 +30,7 @@ struct LocationPrintContext { // Given a DebugArtifact and an OpcodeLocation, prints all the source code // locations the OpcodeLocation maps to, with some surrounding context and // visual aids to highlight the location itself. -pub(crate) fn print_source_code_location(debug_artifact: &DebugArtifact, locations: &[Location]) { +pub(super) fn print_source_code_location(debug_artifact: &DebugArtifact, locations: &[Location]) { let locations = locations.iter(); for loc in locations { @@ -269,8 +269,12 @@ mod tests { let mut opcode_locations = BTreeMap::>::new(); opcode_locations.insert(OpcodeLocation::Acir(42), vec![loc]); - let debug_symbols = - vec![DebugInfo::new(opcode_locations, BTreeMap::default(), BTreeMap::default())]; + let debug_symbols = vec![DebugInfo::new( + opcode_locations, + BTreeMap::default(), + BTreeMap::default(), + BTreeMap::default(), + )]; let debug_artifact = DebugArtifact::new(debug_symbols, &fm); let location_rendered: Vec<_> = render_location(&debug_artifact, &loc).collect(); diff --git a/noir/noir-repo/tooling/debugger/tests/debug.rs b/noir/noir-repo/tooling/debugger/tests/debug.rs index 4cb678192b8..143ee7987f8 100644 --- a/noir/noir-repo/tooling/debugger/tests/debug.rs +++ b/noir/noir-repo/tooling/debugger/tests/debug.rs @@ -12,7 +12,7 @@ mod tests { let nargo_bin = cargo_bin("nargo").into_os_string().into_string().expect("Cannot parse nargo path"); - let mut dbg_session = spawn_bash(Some(10000)).expect("Could not start bash session"); + let mut dbg_session = spawn_bash(Some(15000)).expect("Could not start bash session"); // Set backend to `/dev/null` to force an error if nargo tries to speak to a backend. dbg_session diff --git a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs index 0b88d814265..1844a3d9bf0 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/test_run.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/test_run.rs @@ -84,7 +84,7 @@ fn on_test_run_request_inner( let test_result = run_test( &state.solver, &mut context, - test_function, + &test_function, false, None, &CompileOptions::default(), diff --git a/noir/noir-repo/tooling/nargo/src/artifacts/debug.rs b/noir/noir-repo/tooling/nargo/src/artifacts/debug.rs index a249ecb03ad..fbdf59805c9 100644 --- a/noir/noir-repo/tooling/nargo/src/artifacts/debug.rs +++ b/noir/noir-repo/tooling/nargo/src/artifacts/debug.rs @@ -8,7 +8,7 @@ use std::{ ops::Range, }; -pub use super::debug_vars::DebugVars; +pub use super::debug_vars::{DebugVars, StackFrame}; use fm::{FileId, FileManager, PathString}; /// A Debug Artifact stores, for a given program, the debug info for every function @@ -231,8 +231,12 @@ mod tests { let mut opcode_locations = BTreeMap::>::new(); opcode_locations.insert(OpcodeLocation::Acir(42), vec![loc]); - let debug_symbols = - vec![DebugInfo::new(opcode_locations, BTreeMap::default(), BTreeMap::default())]; + let debug_symbols = vec![DebugInfo::new( + opcode_locations, + BTreeMap::default(), + BTreeMap::default(), + BTreeMap::default(), + )]; let debug_artifact = DebugArtifact::new(debug_symbols, &fm); let location_in_line = debug_artifact.location_in_line(loc).expect("Expected a range"); 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 20f2637f7d6..66568bec833 100644 --- a/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs +++ b/noir/noir-repo/tooling/nargo/src/artifacts/debug_vars.rs @@ -1,54 +1,85 @@ use acvm::brillig_vm::brillig::Value; use noirc_errors::debug_info::{ - DebugTypeId, DebugTypes, DebugVarId, DebugVariable, DebugVariables, + DebugFnId, DebugFunction, DebugInfo, DebugTypeId, DebugVarId, DebugVariable, }; use noirc_printable_type::{decode_value, PrintableType, PrintableValue}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; #[derive(Debug, Default, Clone)] pub struct DebugVars { variables: HashMap, + functions: HashMap, types: HashMap, - active: HashSet, - values: HashMap, + frames: Vec<(DebugFnId, HashMap)>, +} + +pub struct StackFrame<'a> { + pub function_name: &'a str, + pub function_params: Vec<&'a str>, + pub variables: Vec<(&'a str, &'a PrintableValue, &'a PrintableType)>, } impl DebugVars { - pub fn get_variables(&self) -> Vec<(&str, &PrintableValue, &PrintableType)> { - self.active - .iter() - .filter_map(|var_id| { - self.variables.get(var_id).and_then(|debug_var| { - let Some(value) = self.values.get(var_id) else { - return None; - }; - let Some(ptype) = self.types.get(&debug_var.debug_type_id) else { - return None; - }; - Some((debug_var.name.as_str(), value, ptype)) - }) - }) - .collect() + pub fn insert_debug_info(&mut self, info: &DebugInfo) { + self.variables.extend(info.variables.clone()); + self.types.extend(info.types.clone()); + self.functions.extend(info.functions.clone()); + } + + pub fn get_variables(&self) -> Vec { + self.frames.iter().map(|(fn_id, frame)| self.build_stack_frame(fn_id, frame)).collect() } - pub fn insert_variables(&mut self, vars: &DebugVariables) { - self.variables.extend(vars.clone()); + pub fn current_stack_frame(&self) -> Option { + self.frames.last().map(|(fn_id, frame)| self.build_stack_frame(fn_id, frame)) } - pub fn insert_types(&mut self, types: &DebugTypes) { - self.types.extend(types.clone()); + 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; + }; + Some((debug_var.name.as_str(), ptype)) + }) + } + + fn build_stack_frame<'a>( + &'a self, + fn_id: &DebugFnId, + frame: &'a HashMap, + ) -> StackFrame { + let debug_fn = &self.functions.get(fn_id).expect("failed to find function metadata"); + + let params: Vec<&str> = + debug_fn.arg_names.iter().map(|arg_name| arg_name.as_str()).collect(); + let vars: Vec<(&str, &PrintableValue, &PrintableType)> = frame + .iter() + .filter_map(|(var_id, var_value)| { + self.lookup_var(*var_id).map(|(name, typ)| (name, var_value, typ)) + }) + .collect(); + + StackFrame { + function_name: debug_fn.name.as_str(), + function_params: params, + variables: vars, + } } pub fn assign_var(&mut self, var_id: DebugVarId, values: &[Value]) { - self.active.insert(var_id); let type_id = &self.variables.get(&var_id).unwrap().debug_type_id; let ptype = self.types.get(type_id).unwrap(); - self.values.insert(var_id, decode_value(&mut values.iter().map(|v| v.to_field()), ptype)); + + self.frames + .last_mut() + .expect("unexpected empty stack frames") + .1 + .insert(var_id, decode_value(&mut values.iter().map(|v| v.to_field()), ptype)); } pub fn assign_field(&mut self, var_id: DebugVarId, indexes: Vec, values: &[Value]) { - let mut cursor: &mut PrintableValue = self - .values + let current_frame = &mut self.frames.last_mut().expect("unexpected empty stack frames").1; + let mut cursor: &mut PrintableValue = current_frame .get_mut(&var_id) .unwrap_or_else(|| panic!("value unavailable for var_id {var_id:?}")); let cursor_type_id = &self @@ -102,7 +133,6 @@ impl DebugVars { }; } *cursor = decode_value(&mut values.iter().map(|v| v.to_field()), cursor_type); - self.active.insert(var_id); } pub fn assign_deref(&mut self, _var_id: DebugVarId, _values: &[Value]) { @@ -114,6 +144,14 @@ impl DebugVars { } pub fn drop_var(&mut self, var_id: DebugVarId) { - self.active.remove(&var_id); + self.frames.last_mut().expect("unexpected empty stack frames").1.remove(&var_id); + } + + pub fn push_fn(&mut self, fn_id: DebugFnId) { + self.frames.push((fn_id, HashMap::default())); + } + + pub fn pop_fn(&mut self) { + self.frames.pop(); } } diff --git a/noir/noir-repo/tooling/nargo/src/ops/test.rs b/noir/noir-repo/tooling/nargo/src/ops/test.rs index 92c09ec889e..8ddcb5cf8d2 100644 --- a/noir/noir-repo/tooling/nargo/src/ops/test.rs +++ b/noir/noir-repo/tooling/nargo/src/ops/test.rs @@ -14,10 +14,16 @@ pub enum TestStatus { CompileError(FileDiagnostic), } +impl TestStatus { + pub fn failed(&self) -> bool { + !matches!(self, TestStatus::Pass) + } +} + pub fn run_test( blackbox_solver: &B, context: &mut Context, - test_function: TestFunction, + test_function: &TestFunction, show_output: bool, foreign_call_resolver_url: Option<&str>, config: &CompileOptions, @@ -45,7 +51,7 @@ pub fn run_test( /// that a constraint was never satisfiable. /// An example of this is the program `assert(false)` /// In that case, we check if the test function should fail, and if so, we return `TestStatus::Pass`. -fn test_status_program_compile_fail(err: CompileError, test_function: TestFunction) -> TestStatus { +fn test_status_program_compile_fail(err: CompileError, test_function: &TestFunction) -> TestStatus { // The test has failed compilation, but it should never fail. Report error. if !test_function.should_fail() { return TestStatus::CompileError(err.into()); @@ -70,7 +76,7 @@ fn test_status_program_compile_fail(err: CompileError, test_function: TestFuncti /// We now check whether execution passed/failed and whether it should have /// passed/failed to determine the test status. fn test_status_program_compile_pass( - test_function: TestFunction, + test_function: &TestFunction, debug: DebugInfo, circuit_execution: Result, ) -> TestStatus { @@ -109,7 +115,7 @@ fn test_status_program_compile_pass( } fn check_expected_failure_message( - test_function: TestFunction, + test_function: &TestFunction, failed_assertion: Option, error_diagnostic: Option, ) -> TestStatus { diff --git a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs index 68660d62d23..2828aaf01eb 100644 --- a/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/noir/noir-repo/tooling/nargo_cli/src/cli/test_cmd.rs @@ -5,17 +5,18 @@ use bn254_blackbox_solver::Bn254BlackBoxSolver; use clap::Args; use fm::FileManager; use nargo::{ - insert_all_files_for_workspace_into_file_manager, - ops::{run_test, TestStatus}, - package::Package, - parse_all, prepare_package, + insert_all_files_for_workspace_into_file_manager, ops::TestStatus, package::Package, parse_all, + prepare_package, }; use nargo_toml::{get_package_manifest, resolve_workspace_from_toml, PackageSelection}; -use noirc_driver::{file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING}; +use noirc_driver::{ + check_crate, file_manager_with_stdlib, CompileOptions, NOIR_ARTIFACT_VERSION_STRING, +}; use noirc_frontend::{ graph::CrateName, hir::{FunctionNameMatch, ParsedFiles}, }; +use rayon::prelude::{IntoParallelIterator, ParallelBridge, ParallelIterator}; use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use crate::{backends::Backend, cli::check_cmd::check_crate_and_report_errors, errors::CliError}; @@ -82,15 +83,13 @@ pub(crate) fn run( None => FunctionNameMatch::Anything, }; - let blackbox_solver = Bn254BlackBoxSolver::new(); - let test_reports: Vec> = workspace .into_iter() + .par_bridge() .map(|package| { - run_tests( + run_tests::( &workspace_file_manager, &parsed_files, - &blackbox_solver, package, pattern, args.show_output, @@ -116,24 +115,95 @@ pub(crate) fn run( }; } - if test_report.iter().any(|(_, status)| matches!(status, TestStatus::Fail { .. })) { + if test_report.iter().any(|(_, status)| status.failed()) { Err(CliError::Generic(String::new())) } else { Ok(()) } } -#[allow(clippy::too_many_arguments)] -fn run_tests( +fn run_tests( file_manager: &FileManager, parsed_files: &ParsedFiles, - blackbox_solver: &S, package: &Package, fn_name: FunctionNameMatch, show_output: bool, foreign_call_resolver_url: Option<&str>, compile_options: &CompileOptions, ) -> Result, CliError> { + let test_functions = + get_tests_in_package(file_manager, parsed_files, package, fn_name, compile_options)?; + + let count_all = test_functions.len(); + + let plural = if count_all == 1 { "" } else { "s" }; + println!("[{}] Running {count_all} test function{plural}", package.name); + + let test_report: Vec<(String, TestStatus)> = test_functions + .into_par_iter() + .map(|test_name| { + let status = run_test::( + file_manager, + parsed_files, + package, + &test_name, + show_output, + foreign_call_resolver_url, + compile_options, + ); + + (test_name, status) + }) + .collect(); + + display_test_report(file_manager, package, compile_options, &test_report)?; + Ok(test_report) +} + +fn run_test( + file_manager: &FileManager, + parsed_files: &ParsedFiles, + package: &Package, + fn_name: &str, + show_output: bool, + foreign_call_resolver_url: Option<&str>, + compile_options: &CompileOptions, +) -> TestStatus { + // This is really hacky but we can't share `Context` or `S` across threads. + // We then need to construct a separate copy for each test. + + let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); + check_crate( + &mut context, + crate_id, + compile_options.deny_warnings, + compile_options.disable_macros, + ) + .expect("Any errors should have occurred when collecting test functions"); + + let test_functions = context + .get_all_test_functions_in_crate_matching(&crate_id, FunctionNameMatch::Exact(fn_name)); + let (_, test_function) = test_functions.first().expect("Test function should exist"); + + let blackbox_solver = S::default(); + + nargo::ops::run_test( + &blackbox_solver, + &mut context, + test_function, + show_output, + foreign_call_resolver_url, + compile_options, + ) +} + +fn get_tests_in_package( + file_manager: &FileManager, + parsed_files: &ParsedFiles, + package: &Package, + fn_name: FunctionNameMatch, + compile_options: &CompileOptions, +) -> Result, CliError> { let (mut context, crate_id) = prepare_package(file_manager, parsed_files, package); check_crate_and_report_errors( &mut context, @@ -143,30 +213,27 @@ fn run_tests( compile_options.silence_warnings, )?; - let test_functions = context.get_all_test_functions_in_crate_matching(&crate_id, fn_name); - let count_all = test_functions.len(); - - let plural = if count_all == 1 { "" } else { "s" }; - println!("[{}] Running {count_all} test function{plural}", package.name); + Ok(context + .get_all_test_functions_in_crate_matching(&crate_id, fn_name) + .into_iter() + .map(|(test_name, _)| test_name) + .collect()) +} +fn display_test_report( + file_manager: &FileManager, + package: &Package, + compile_options: &CompileOptions, + test_report: &[(String, TestStatus)], +) -> Result<(), CliError> { let writer = StandardStream::stderr(ColorChoice::Always); let mut writer = writer.lock(); - let mut test_report: Vec<(String, TestStatus)> = Vec::new(); - for (test_name, test_function) in test_functions { + for (test_name, test_status) in test_report { write!(writer, "[{}] Testing {test_name}... ", package.name) .expect("Failed to write to stderr"); writer.flush().expect("Failed to flush writer"); - let test_status = run_test( - blackbox_solver, - &mut context, - test_function, - show_output, - foreign_call_resolver_url, - compile_options, - ); - match &test_status { TestStatus::Pass { .. } => { writer @@ -181,7 +248,7 @@ fn run_tests( writeln!(writer, "FAIL\n{message}\n").expect("Failed to write to stderr"); if let Some(diag) = error_diagnostic { noirc_errors::reporter::report_all( - context.file_manager.as_file_map(), + file_manager.as_file_map(), &[diag.clone()], compile_options.deny_warnings, compile_options.silence_warnings, @@ -190,23 +257,21 @@ fn run_tests( } TestStatus::CompileError(err) => { noirc_errors::reporter::report_all( - context.file_manager.as_file_map(), + file_manager.as_file_map(), &[err.clone()], compile_options.deny_warnings, compile_options.silence_warnings, ); } } - - test_report.push((test_name, test_status)); - writer.reset().expect("Failed to reset writer"); } write!(writer, "[{}] ", package.name).expect("Failed to write to stderr"); - let count_failed = - test_report.iter().filter(|(_, status)| !matches!(status, TestStatus::Pass)).count(); + let count_all = test_report.len(); + let count_failed = test_report.iter().filter(|(_, status)| status.failed()).count(); + let plural = if count_all == 1 { "" } else { "s" }; if count_failed == 0 { writer.set_color(ColorSpec::new().set_fg(Some(Color::Green))).expect("Failed to set color"); write!(writer, "{count_all} test{plural} passed").expect("Failed to write to stderr"); @@ -231,5 +296,5 @@ fn run_tests( writer.reset().expect("Failed to reset writer"); } - Ok(test_report) + Ok(()) } 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 28c3609fd14..06c034725e3 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": "0.24.0", + "@aztec/bb.js": "0.26.3", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/noir/noir-repo/yarn.lock b/noir/noir-repo/yarn.lock index f5f3a29f08a..49485193532 100644 --- a/noir/noir-repo/yarn.lock +++ b/noir/noir-repo/yarn.lock @@ -221,9 +221,9 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.24.0": - version: 0.24.0 - resolution: "@aztec/bb.js@npm:0.24.0" +"@aztec/bb.js@npm:0.26.3": + version: 0.26.3 + resolution: "@aztec/bb.js@npm:0.26.3" dependencies: comlink: ^4.4.1 commander: ^10.0.1 @@ -231,7 +231,7 @@ __metadata: tslib: ^2.4.0 bin: bb.js: dest/node/main.js - checksum: a086dabf30084cfa526e512148b9c02f0a0770dcc19b7dca4af9a3e98612b716acc7eaac6b52c0f12d985932e866d1cb9e534ded6ac9d747f3dd021afe25de27 + checksum: 74c2b7ef5405f56472cf7c41d1c13261df07b1d5019e3ede9b63d218378e0fb73ccf5c52f1cc524505efad5799b347b497349d7c9b6fe82286014b1574f12309 languageName: node linkType: hard @@ -4396,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": 0.24.0 + "@aztec/bb.js": 0.26.3 "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3