Skip to content

fix(brillig): emit a trap for unreachable terminators - #13448

Open
asterite wants to merge 1 commit into
masterfrom
ab/brillig-unreachable-trap
Open

fix(brillig): emit a trap for unreachable terminators#13448
asterite wants to merge 1 commit into
masterfrom
ab/brillig-unreachable-trap

Conversation

@asterite

@asterite asterite commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

In reality the unreachable SSA instruction can never be triggered. That's why we didn't produce any brillig opcode in this case. However, it's probably safer to do a trap in this case. It slightly increases the number of opcodes in a program in this case, but it's always a small constant factor, and not for every program.

Description

Problem

Resolves https://github.com/noir-lang/noir-claude/issues/1541

Brillig codegen emitted no opcode for an unreachable terminator. When the block's last instruction is a constrain, its "assertion passed" section label is registered at the position of the next opcode to be emitted — which, with no terminator opcode following, is one past the end of the function's own code. The linker resolves that dangling label like any other, so the shipped JumpIf targets whatever bytecode happened to be linked next: another function's body, a shared procedure such as RevertWithString, or an index past the end of the whole program. Five programs in test_programs ship such a jump today; execution_success/regression_5435 ships a jump target equal to its opcode count.

The edge is currently provably dead (every source-reachable unreachable sits behind a constrain over unequal constants), so no execution behavior changes — but the malformed address ships in real artifacts, and correctness rests on an unenforced upstream invariant.

Summary

Emit a message-less trap for the unreachable terminator instead of nothing:

  • Every section label stays in range by construction, so linked jump targets always point into the code they belong to.
  • If an unreachability proof is ever wrong, execution now aborts loudly at the trap instead of silently transferring control into unrelated code.

The trap emission is extracted into a codegen_trap helper, reused by the existing empty-error-data constrain path (identical bytecode as before).

Cost: two opcodes (const + trap) per unreachable terminator, which by construction only appears on paths believed unreachable.

Additional Context

Regression test: unreachable_terminator_keeps_jump_targets_in_range compiles the minimal reproduction (constrain v0 == u32 7 followed by unreachable) and asserts every Jump/JumpIf/Call target in the linked program is in range. It fails on master with JumpIf targeting 17 in a 17-opcode program.

The updated brillig_global_array_not_coalesced_with_block_param snapshot shows the fix directly: the constrain's passed-edge label f0/b1/1 previously resolved to the first opcode of the next block (f0/b2); it now lands on the trap inside its own block.

Verified locally: full noirc_evaluator suite (1921 tests) and the nargo_cli execution integration suite (9130 tests) pass; regression_5435 now compiles with all jump targets in range and still executes successfully.

PR Checklist

  • I have tested the changes locally.
  • I have formatted the changes with Prettier and/or cargo fmt on default settings.

🤖 Generated with Claude Code

Brillig codegen emitted no opcode for an `unreachable` terminator. When
the block's last instruction is a `constrain`, its "assertion passed"
section label is registered at the next opcode's position, which ended
up one past the end of the function's own code. The linker then
resolved that label into whatever bytecode was placed next, so the
shipped `JumpIf` targeted another function, a shared procedure, or an
index past the end of the program (e.g. `regression_5435` shipped a
jump target equal to its opcode count).

Emit a message-less trap for `unreachable` instead. This keeps every
section label in range by construction and turns a wrong unreachability
proof into a loud abort instead of silent wrong control flow.

Fixes noir-lang/noir-claude#1541

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Changes to Brillig bytecode sizes

Generated at commit: 41a3a8fdd3ff3b126e3885e384df8b58dbea7f33, compared to commit: 54386dfcd6449707ec737e416d90825c0872e96b

🧾 Summary (10% most significant diffs)

Program Brillig opcodes (+/-) %
regression_5435_inliner_max +2 ❌ +9.09%
regression_5435_inliner_min +2 ❌ +9.09%
regression_5435_inliner_zero +2 ❌ +9.09%
side_effects_constrain_array_inliner_max +2 ❌ +8.70%
side_effects_constrain_array_inliner_min +2 ❌ +8.70%
side_effects_constrain_array_inliner_zero +2 ❌ +8.70%
regression_9758_inliner_zero +2 ❌ +8.33%
regression_8874_inliner_max +2 ❌ +8.00%
regression_9193_inliner_max +2 ❌ +8.00%
regression_9193_inliner_min +2 ❌ +8.00%
regression_8874_inliner_min +2 ❌ +8.00%
regression_8874_inliner_zero +2 ❌ +8.00%
regression_9193_inliner_zero +2 ❌ +8.00%

Full diff report 👇
Program Brillig opcodes (+/-) %
regression_5435_inliner_max 24 (+2) +9.09%
regression_5435_inliner_min 24 (+2) +9.09%
regression_5435_inliner_zero 24 (+2) +9.09%
side_effects_constrain_array_inliner_max 25 (+2) +8.70%
side_effects_constrain_array_inliner_min 25 (+2) +8.70%
side_effects_constrain_array_inliner_zero 25 (+2) +8.70%
regression_9758_inliner_zero 26 (+2) +8.33%
regression_8874_inliner_max 27 (+2) +8.00%
regression_9193_inliner_max 27 (+2) +8.00%
regression_9193_inliner_min 27 (+2) +8.00%
regression_8874_inliner_min 27 (+2) +8.00%
regression_8874_inliner_zero 27 (+2) +8.00%
regression_9193_inliner_zero 27 (+2) +8.00%
regression_8235_inliner_max 28 (+2) +7.69%
regression_8235_inliner_min 28 (+2) +7.69%
regression_8235_inliner_zero 28 (+2) +7.69%
regression_10180_inliner_max 31 (+2) +6.90%
regression_oob_constant_tuple_array_get_inliner_max 31 (+2) +6.90%
regression_10180_inliner_min 31 (+2) +6.90%
regression_oob_constant_tuple_array_get_inliner_min 31 (+2) +6.90%
regression_10180_inliner_zero 31 (+2) +6.90%
regression_oob_constant_tuple_array_get_inliner_zero 31 (+2) +6.90%
regression_9764_inliner_max 32 (+2) +6.67%
regression_9764_inliner_min 32 (+2) +6.67%
regression_9764_inliner_zero 32 (+2) +6.67%
regression_9594_inliner_max 33 (+2) +6.45%
regression_9594_inliner_min 33 (+2) +6.45%
regression_9594_inliner_zero 33 (+2) +6.45%
regression_9546_inliner_max 36 (+2) +5.88%
regression_9546_inliner_min 36 (+2) +5.88%
regression_9546_inliner_zero 36 (+2) +5.88%
regression_9888_inliner_max 38 (+2) +5.56%
regression_9888_inliner_min 38 (+2) +5.56%
regression_9888_inliner_zero 38 (+2) +5.56%
regression_9208_inliner_max 40 (+2) +5.26%
regression_9208_inliner_min 40 (+2) +5.26%
regression_9208_inliner_zero 40 (+2) +5.26%
conditional_regression_short_circuit_inliner_max 188 (+4) +2.17%
conditional_regression_short_circuit_inliner_min 188 (+4) +2.17%
conditional_regression_short_circuit_inliner_zero 188 (+4) +2.17%
regression_6285_inliner_max 122 (+2) +1.67%
regression_6285_inliner_min 122 (+2) +1.67%
regression_6285_inliner_zero 122 (+2) +1.67%
regression_10917_inliner_max 124 (+2) +1.64%
regression_10917_inliner_min 124 (+2) +1.64%
regression_10917_inliner_zero 124 (+2) +1.64%
regression_9467_inliner_max 137 (+2) +1.48%
regression_9467_inliner_min 137 (+2) +1.48%
regression_9467_inliner_zero 137 (+2) +1.48%
loop_invariant_regression_inliner_max 159 (+2) +1.27%
loop_invariant_regression_inliner_min 159 (+2) +1.27%
loop_invariant_regression_inliner_zero 159 (+2) +1.27%
to_be_bytes_inliner_max 166 (+2) +1.22%
to_be_bytes_inliner_min 166 (+2) +1.22%
to_be_bytes_inliner_zero 166 (+2) +1.22%
regression_9758_inliner_min 173 (+2) +1.17%
vector_dynamic_index_inliner_max 1,651 (+10) +0.61%
vector_dynamic_index_inliner_min 1,651 (+10) +0.61%
vector_dynamic_index_inliner_zero 1,651 (+10) +0.61%
lambda_from_dynamic_if_inliner_min 472 (+2) +0.43%
lambda_from_dynamic_if_inliner_zero 472 (+2) +0.43%
conditional_1_inliner_max 500 (+2) +0.40%
conditional_1_inliner_min 538 (+2) +0.37%
conditional_1_inliner_zero 538 (+2) +0.37%
lambda_from_dynamic_if_inliner_max 573 (+2) +0.35%

@asterite
asterite marked this pull request as ready for review August 3, 2026 22:34
@asterite
asterite requested a review from TomAFrench August 3, 2026 22:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant