Skip to content

fix(ssa): do not fold ArraySet to MakeArray in simplifier#11659

Merged
TomAFrench merged 37 commits intomasterfrom
tf/fix-array-set-predicate-simplification
Mar 6, 2026
Merged

fix(ssa): do not fold ArraySet to MakeArray in simplifier#11659
TomAFrench merged 37 commits intomasterfrom
tf/fix-array-set-predicate-simplification

Conversation

@TomAFrench
Copy link
Copy Markdown
Member

Summary

  • The DFG simplifier was rewriting predicate-dependent ArraySet instructions into predicate-independent MakeArray when the base array and index were constants. This erased the EnableSideEffectsIf guard that ACIR relies on, making conditional writes unconditional.
  • Remove the ArraySetMakeArray constant fold since the simplifier has no access to the current side-effects predicate and cannot determine whether the fold is safe.
  • Add defensive test confirming array_get through a predicated array_set is also not incorrectly simplified (already handled correctly).

Test plan

  • Regression test array_set_constant_folding_must_respect_side_effects_predicate verifies simplification preserves semantics under both enabled and disabled side effects
  • Defensive test array_get_not_simplified_through_predicated_array_set confirms existing conservative behavior
  • All 1167 noirc_evaluator tests pass
  • All 868 array-related integration tests pass
  • Updated snapshots in simplifies_array_get_from_previous_array_set_with_make_array and revisit_block_which_dominates_cache

The DFG simplifier was rewriting predicate-dependent ArraySet
instructions into predicate-independent MakeArray when the base array
and index were constants. This erased the EnableSideEffectsIf guard
that ACIR relies on, making conditional writes unconditional and
allowing incorrect witness values to satisfy constraints.

Remove the ArraySet→MakeArray constant fold since the simplifier has
no access to the current side-effects predicate and cannot determine
whether the fold is safe.
…_set

NOIR-17 was a false positive — the simplifier already correctly refuses
to optimize array_get through a same-index predicated array_set when
it lacks predicate context (side_effects = None). Add a test to ensure
this behavior is preserved.
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Execution Time'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 75f893b Previous: 001f104 Ratio
rollup-checkpoint-merge 0.003 s 0.002 s 1.50

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Artifact Size'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 396508f Previous: 28c3b5f Ratio
rollup-tx-base-public 5696.8 KB 4693.9 KB 1.21

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Compilation Time'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 29af7df Previous: 128c6eb Ratio
rollup-block-root-single-tx 2.24 s 1.74 s 1.29

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Opcode count'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.10.

Benchmark suite Current: 396508f Previous: 28c3b5f Ratio
rollup-checkpoint-root-single-block 1560093 opcodes 1387956 opcodes 1.12
rollup-checkpoint-root 1561286 opcodes 1389150 opcodes 1.12
rollup-tx-base-private 358504 opcodes 299181 opcodes 1.20
rollup-tx-base-public 314113 opcodes 254789 opcodes 1.23

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Compilation Memory'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 9081cf0 Previous: 824f568 Ratio
rollup-tx-base-private 1700 MB 1070 MB 1.59

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Execution Memory'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 9081cf0 Previous: 824f568 Ratio
rollup-tx-base-private 688.35 MB 527.34 MB 1.31
rollup-tx-base-public 556.46 MB 462.99 MB 1.20

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Test Suite Duration'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 75f893b Previous: 001f104 Ratio
test_report_noir-lang_sha512_ 14 s 11 s 1.27
test_report_zkpassport_noir_rsa_ 2 s 1 s 2

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Brillig functions do not use side-effects predicates, so the
ArraySet-to-MakeArray simplification is safe there. Guard the
optimization with a runtime check instead of disabling it entirely.
@TomAFrench TomAFrench requested a review from a team February 23, 2026 16:51
@TomAFrench TomAFrench added the bench-show Display benchmark results on PR label Feb 23, 2026
Copy link
Copy Markdown
Collaborator

@asterite asterite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!
Should we capture a follow-up issue to perform this optimization as a pass, if we find that the predicate of the MakeArray instruction is the same as the one for the ArraySet? I guess it could go with the existing SSA pass that does this for ArrayGet, and it could maybe revert these regressions.
When I implemented this for ArrayGet I didn't realize this was only an issue in ACIR, so maybe we could also change that code to take that into account (though I don't think it'll further optimize things).

@TomAFrench
Copy link
Copy Markdown
Member Author

Ah, lemme take another look at #11586 to see if I can tie this into your changes a bit more.

…dedicated pass

Move the constant-array ArraySet folding out of the simplifier (which
lacks predicate context) into the array_set_optimization pass. Extract
mutable_array_set as a separate module and fix test expectations.
@TomAFrench TomAFrench marked this pull request as draft February 24, 2026 12:32
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACVM Benchmarks

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
purely_sequential_opcodes 251600 ns/iter (± 615) 251446 ns/iter (± 484) 1.00
perfectly_parallel_opcodes 220410 ns/iter (± 4690) 221480 ns/iter (± 6603) 1.00
perfectly_parallel_batch_inversion_opcodes 2741913 ns/iter (± 2640) 2745617 ns/iter (± 6703) 1.00

This comment was automatically generated by workflow using github-action-benchmark.

@TomAFrench
Copy link
Copy Markdown
Member Author

I've pulled the renaming of the old array_set_optimization into #11673 so simplify the diff

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Elaboration Time

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 0.993 s 0.932 s 1.07
private-kernel-reset 0.92 s 0.984 s 0.93
private-kernel-tail 0.994 s 0.929 s 1.07
rollup-block-root-first-empty-tx 1.464 s 1.48 s 0.99
rollup-block-root-single-tx 1.4 s 1.36 s 1.03
rollup-block-root 1.38 s 1.42 s 0.97
rollup-checkpoint-merge 1.468 s 1.396 s 1.05
rollup-checkpoint-root-single-block 1.43 s 1.36 s 1.05
rollup-checkpoint-root 1.4 s 1.35 s 1.04
rollup-root 1.414 s 1.392 s 1.02
rollup-tx-base-private 1.394 s 1.388 s 1.00
rollup-tx-base-public 1.388 s 1.396 s 0.99
rollup-tx-merge 1.398 s 1.398 s 1
semaphore-depth-10 0.157 s 0.147 s 1.07
sha512-100-bytes 0.133 s 0.136 s 0.98

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opcode count

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 19357 opcodes 19357 opcodes 1
private-kernel-reset 85604 opcodes 85604 opcodes 1
private-kernel-tail 8739 opcodes 8739 opcodes 1
rollup-block-root-first-empty-tx 1088 opcodes 1082 opcodes 1.01
rollup-block-root-single-tx 975 opcodes 969 opcodes 1.01
rollup-block-root 2177 opcodes 2171 opcodes 1.00
rollup-checkpoint-merge 1271 opcodes 1271 opcodes 1
rollup-checkpoint-root-single-block 1412521 opcodes 1387956 opcodes 1.02
rollup-checkpoint-root 1413715 opcodes 1389150 opcodes 1.02
rollup-root 1525 opcodes 1525 opcodes 1
rollup-tx-base-private 307656 opcodes 299181 opcodes 1.03
rollup-tx-base-public 263264 opcodes 254789 opcodes 1.03
rollup-tx-merge 1302 opcodes 1302 opcodes 1
semaphore-depth-10 5699 opcodes 5699 opcodes 1
sha512-100-bytes 13173 opcodes 13173 opcodes 1

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Artifact Size

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 993.8 KB 993.8 KB 1
private-kernel-reset 2200.5 KB 2200.5 KB 1
private-kernel-tail 489.9 KB 490 KB 1.00
rollup-block-root-first-empty-tx 230.9 KB 230.7 KB 1.00
rollup-block-root-single-tx 234.3 KB 234.2 KB 1.00
rollup-block-root 306.4 KB 306.3 KB 1.00
rollup-checkpoint-merge 365.2 KB 365.2 KB 1
rollup-checkpoint-root-single-block 31154.9 KB 30782.4 KB 1.01
rollup-checkpoint-root 31207.3 KB 30834.5 KB 1.01
rollup-root 389.8 KB 389.8 KB 1
rollup-tx-base-private 5515.8 KB 5384.3 KB 1.02
rollup-tx-base-public 4813.9 KB 4689.9 KB 1.03
rollup-tx-merge 178.5 KB 178.5 KB 1
semaphore-depth-10 488.6 KB 488.6 KB 1
sha512-100-bytes 473.7 KB 473.7 KB 1

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brillig Compilation Time

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 1.54 s 1.5 s 1.03
private-kernel-reset 1.522 s 1.578 s 0.96
private-kernel-tail 1.378 s 1.294 s 1.06
rollup-block-root-first-empty-tx 1.782 s 1.892 s 0.94
rollup-block-root-single-tx 1.7 s 1.69 s 1.01
rollup-block-root 1.74 s 1.77 s 0.98
rollup-checkpoint-merge 1.796 s 1.7 s 1.06
rollup-checkpoint-root-single-block 2.29 s 2.24 s 1.02
rollup-checkpoint-root 2.33 s 2.3 s 1.01
rollup-root 1.85 s 1.756 s 1.05
rollup-tx-base-private 2.078 s 1.872 s 1.11
rollup-tx-base-public 1.962 s 2.04 s 0.96
rollup-tx-merge 1.702 s 1.702 s 1
semaphore-depth-10 0.297 s 0.282 s 1.05
sha512-100-bytes 0.244 s 0.243 s 1.00

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compilation Time

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 2.97 s 2.638 s 1.13
private-kernel-reset 7.948 s 8.434 s 0.94
private-kernel-tail 2.57 s 2.622 s 0.98
rollup-block-root-first-empty-tx 1.854 s 1.86 s 1.00
rollup-block-root-single-tx 1.75 s 1.7 s 1.03
rollup-block-root 1.8 s 1.83 s 0.98
rollup-checkpoint-merge 1.856 s 1.778 s 1.04
rollup-checkpoint-root-single-block 332 s 299 s 1.11
rollup-checkpoint-root 316 s 307 s 1.03
rollup-root 1.86 s 1.806 s 1.03
rollup-tx-base-private 20.64 s 19.06 s 1.08
rollup-tx-base-public 110 s 102.8 s 1.07
rollup-tx-merge 1.756 s 1.75 s 1.00
semaphore-depth-10 1.193 s 1.118 s 1.07
sha512-100-bytes 1.719 s 2.051 s 0.84

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Execution Time

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 0.04 s 0.041 s 0.98
private-kernel-reset 0.174 s 0.176 s 0.99
private-kernel-tail 0.009 s 0.008 s 1.13
rollup-block-root-first-empty-tx 0.003 s 0.003 s 1
rollup-block-root-single-tx 0.003 s 0.003 s 1
rollup-block-root 0.003 s 0.004 s 0.75
rollup-checkpoint-merge 0.003 s 0.002 s 1.50
rollup-checkpoint-root-single-block 10.8 s 10.7 s 1.01
rollup-checkpoint-root 9.85 s 10.4 s 0.95
rollup-root 0.003 s 0.003 s 1
rollup-tx-base-private 0.336 s 0.332 s 1.01
rollup-tx-base-public 0.251 s 0.252 s 1.00
rollup-tx-merge 0.002 s 0.002 s 1
semaphore-depth-10 0.009 s 0.009 s 1
sha512-100-bytes 0.058 s 0.083 s 0.70

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brillig Artifact Size

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 766 KB 766 KB 1
private-kernel-reset 706.3 KB 706.3 KB 1
private-kernel-tail 338.6 KB 338.6 KB 1
rollup-block-root-first-empty-tx 272 KB 272 KB 1
rollup-block-root-single-tx 276.3 KB 276.3 KB 1
rollup-block-root 337.3 KB 337.3 KB 1
rollup-checkpoint-merge 268.4 KB 268.4 KB 1
rollup-checkpoint-root-single-block 539.1 KB 539.1 KB 1
rollup-checkpoint-root 581.6 KB 581.6 KB 1
rollup-root 405.8 KB 405.8 KB 1
rollup-tx-base-private 636.4 KB 636.4 KB 1
rollup-tx-base-public 802.4 KB 802.4 KB 1
rollup-tx-merge 202.8 KB 202.8 KB 1
semaphore-depth-10 2067.4 KB 2067.4 KB 1
sha512-100-bytes 163.5 KB 163.5 KB 1

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brillig Execution Time

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
private-kernel-inner 0.031 s 0.032 s 0.97
private-kernel-reset 0.05 s 0.052 s 0.96
private-kernel-tail 0.004 s 0.005 s 0.80
rollup-block-root-first-empty-tx 0.003 s 0.003 s 1
rollup-block-root-single-tx 0.002 s 0.002 s 1
rollup-block-root 0.002 s 0.003 s 0.67
rollup-checkpoint-merge 0.001 s 0.001 s 1
rollup-root 0.002 s 0.002 s 1
rollup-tx-base-private 0.028 s 0.028 s 1
rollup-tx-base-public 0.031 s 0.032 s 0.97
rollup-tx-merge 0.001 s 0.001 s 1
semaphore-depth-10 0.023 s 0.023 s 1
sha512-100-bytes 0.016 s 0.014 s 1.14

This comment was automatically generated by workflow using github-action-benchmark.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Suite Duration

Details
Benchmark suite Current: 75f893b Previous: 001f104 Ratio
test_report_AztecProtocol_aztec-packages_noir-projects_aztec-nr 246 s 233 s 1.06
test_report_AztecProtocol_aztec-packages_noir-projects_noir-contracts 183 s 178 s 1.03
test_report_AztecProtocol_aztec-packages_noir-projects_noir-protocol-circuits_crates_blob 239 s 269 s 0.89
test_report_AztecProtocol_aztec-packages_noir-projects_noir-protocol-circuits_crates_private-kernel-lib 492 s 464 s 1.06
test_report_AztecProtocol_aztec-packages_noir-projects_noir-protocol-circuits_crates_types 162 s 156 s 1.04
test_report_noir-lang_noir-bignum_ 207 s 186 s 1.11
test_report_noir-lang_noir_bigcurve_ 374 s 335 s 1.12
test_report_noir-lang_sha256_ 18 s 18 s 1
test_report_noir-lang_sha512_ 14 s 11 s 1.27
test_report_zkpassport_noir-ecdsa_ 1 s 1 s 1
test_report_zkpassport_noir_rsa_ 2 s 1 s 2

This comment was automatically generated by workflow using github-action-benchmark.

@aakoshh
Copy link
Copy Markdown
Contributor

aakoshh commented Mar 4, 2026

My experiment with merging is in #11760

At the same time we confirmed with @guipublic that assigning incorrect values to the witnesses representing the inputs of poseidon2_permutation, when we will zero out the outputs of the call, should not matter from the circuit correctness perspective.

We were wondering with @asterite whether it is even possible to construct a program that would make this unwanted side effect of unconditionally replacing the result of a disabled array_set even possible, or will the value merger always mask the results.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'ACVM Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: a012f4a Previous: 28c3b5f Ratio
perfectly_parallel_batch_inversion_opcodes 2746089 ns/iter (± 2902) 2209309 ns/iter (± 4639) 1.24

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'ACVM Benchmarks'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.20.

Benchmark suite Current: 396508f Previous: 28c3b5f Ratio
perfectly_parallel_batch_inversion_opcodes 2743531 ns/iter (± 2148) 2209309 ns/iter (± 4639) 1.24

This comment was automatically generated by workflow using github-action-benchmark.

CC: @TomAFrench

@asterite
Copy link
Copy Markdown
Collaborator

asterite commented Mar 5, 2026

One thing I'm noticing is that when we compile hashmap in master, we just get a warning:

warning: unused type alias MyMap
    ┌─ src/main.nr:227:6
    │
227 │ type MyMap = HashMap<u8, u32, 10, BuildHasherDefault<Poseidon2Hasher>>;
    │      ----- unused type alias

But when we compile it in this branch we get bugs (in addition to the warning above):

bug: Input to Brillig function is in a separate subgraph to output
    ┌─ std/array/mod.nr:284:31
    │
284 │         let sorted = unsafe { quicksort::quicksort(self, ordering) };
    │                               ------------------------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
    │
    = Call stack:
      1. src/main.nr:38:5
      2. src/main.nr:198:32
      3. std/array/mod.nr:284:31

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:106:17
     8. std/field/bn254.nr:82:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:106:17
     8. std/field/bn254.nr:83:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:109:17
     8. std/field/bn254.nr:82:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:109:17
     8. std/field/bn254.nr:83:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:106:17
     8. std/field/bn254.nr:82:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:106:17
     8. std/field/bn254.nr:83:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:109:17
     8. std/field/bn254.nr:82:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/field/bn254.nr:40:22
   │
40 │         let borrow = lte_hint(alo, blo);
   │                      ------------------ There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:289:21
     4. src/main.nr:25:68
     5. std/field/mod.nr:199:13
     6. std/field/bn254.nr:117:5
     7. std/field/bn254.nr:109:17
     8. std/field/bn254.nr:83:23
     9. std/field/bn254.nr:68:13
     10. std/field/bn254.nr:40:22

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/array/check_shuffle.nr:49:23
   │
49 │             let idx = __get_index(shuffle_indices, i);
   │                       ------------------------------- There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:293:13
     4. std/array/check_shuffle.nr:49:23

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/array/check_shuffle.nr:49:23
   │
49 │             let idx = __get_index(shuffle_indices, i);
   │                       ------------------------------- There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:293:13
     4. std/array/check_shuffle.nr:49:23

bug: Input to Brillig function is in a separate subgraph to output
   ┌─ std/array/check_shuffle.nr:49:23
   │
49 │             let idx = __get_index(shuffle_indices, i);
   │                       ------------------------------- There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability
   │
   = Call stack:
     1. src/main.nr:38:5
     2. src/main.nr:198:32
     3. std/array/mod.nr:293:13
     4. std/array/check_shuffle.nr:49:23

I wonder if those bugs are legitimate or this PR introduces a bug...

Then it seems #11773 doesn't fix the regression in hashmap, thought it does seem to fix the regression that was happening in Aztec-Packages, so maybe there was a bug in hashmap after all. I'm going to check now why those bugs happen.

@aakoshh
Copy link
Copy Markdown
Contributor

aakoshh commented Mar 5, 2026

There is no path from the output of this Brillig call to either return values or inputs of the circuit, which creates an independent subgraph. This is quite likely a soundness vulnerability

It is true that the results of the call it points at are not constrained against any input, nor are they returned:

fn assert_gt_limbs(a: (Field, Field), b: (Field, Field)) {
let (alo, ahi) = a;
let (blo, bhi) = b;
// Safety: borrow is enforced to be boolean due to its type.
// if borrow is 0, it asserts that (alo > blo && ahi >= bhi)
// if borrow is 1, it asserts that (alo <= blo && ahi > bhi)
unsafe {
let borrow = lte_hint(alo, blo);
let rlo = alo - blo - 1 + (borrow as Field) * TWO_POW_128;
let rhi = ahi - bhi - (borrow as Field);
rlo.assert_max_bit_size::<128>();
rhi.assert_max_bit_size::<128>();
}
}

@TomAFrench
Copy link
Copy Markdown
Member Author

It is true that the results of the call it points at are not constrained against any input, nor are they returned:

This is only half true as the range constraints are expected to constrain the value of borrow.

@aakoshh
Copy link
Copy Markdown
Contributor

aakoshh commented Mar 5, 2026

It is also true that hashmap sorts an array that is a constant defined in the test:

let mut hashmap = ALLOCATE_HASHMAP();
hashmap.insert(2, 3);
hashmap.insert(5, 7);
hashmap.insert(11, 13);
let keys: [K; 3] = cut(hashmap.keys()).sort_via(K_CMP);

Maybe this is whey the vectors test has this kind of stuff:

// The parameters to this function must come from witness values (inputs to main)

@aakoshh
Copy link
Copy Markdown
Contributor

aakoshh commented Mar 5, 2026

This is only half true as the range constraints are expected to constrain the value of borrow.

It looks like the method that considers things connected only looks for return values and inputs:

/// Find sets that contain input or output value of the function
///
/// Goes through each set of connected ValueIds and see if function arguments or return values are in the set
fn find_sets_connected_to_function_inputs_or_outputs(
&self,
function: &Function,
) -> BTreeSet<usize> {
let returns = function.returns().unwrap_or_default();
let variable_parameters_and_return_values = function
.parameters()
.iter()
.chain(returns)
.filter(|id| !is_numeric_constant(function, **id));

Where it connects range constraints, I only see them as things that need to be connected:

self.value_sets.push(instruction_arguments_and_results);

Ah no, they are actually not connected 🙈 :

Value::Intrinsic(intrinsic) => match intrinsic {
Intrinsic::ApplyRangeConstraint
| Intrinsic::AssertConstant
| Intrinsic::AsWitness
| Intrinsic::IsUnconstrained => {}

@asterite
Copy link
Copy Markdown
Collaborator

asterite commented Mar 5, 2026

It seems the hashmap regression is gone! 🎉 And then #11773 optimized the case that Aztec-Packages was bumping into.

@aakoshh
Copy link
Copy Markdown
Contributor

aakoshh commented Mar 6, 2026

It seems the hashmap regression is gone!

Before it was gone, the SSA could not eliminate the Brillig calls as constants, the array_sets where not turned into make_array, and then the call to Brillig stayed in. After some further changes, they were all make_array, a constant input to the unconstrained quick sort related calls, which were then constant folded, and with no Brillig calls left, no disconnected subgraphs either.

@aakoshh aakoshh marked this pull request as ready for review March 6, 2026 11:04
@TomAFrench TomAFrench added this pull request to the merge queue Mar 6, 2026
@TomAFrench
Copy link
Copy Markdown
Member Author

Great work on this @aakoshh and @asterite, thank you!

Merged via the queue into master with commit ebb5ff1 Mar 6, 2026
135 checks passed
@TomAFrench TomAFrench deleted the tf/fix-array-set-predicate-simplification branch March 6, 2026 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bench-show Display benchmark results on PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants