Skip to content

feat: push overflow checks inside of signed binary ops#9074

Merged
aakoshh merged 36 commits intomasterfrom
tf/checked-signed-ops
Jul 10, 2025
Merged

feat: push overflow checks inside of signed binary ops#9074
aakoshh merged 36 commits intomasterfrom
tf/checked-signed-ops

Conversation

@TomAFrench
Copy link
Member

@TomAFrench TomAFrench commented Jul 1, 2025

Description

Problem*

Supercedes #9059
Resolves #9047

Summary*

This PR aims to put signed integer instruction in SSA on a similar footing to unsigned integer operations, i.e. unless explicitly marked as unchecked then it internally contains an appropriate overflow check in isolation.

I've basically just moved the relevant code from the ssa_codegen step and moved it into the new expand_signed_checks ssa pass. There's now a good deal of code duplication between this pass and remove_bit_shifts which implies we should push some of these methods into the simple_optimization module to be shared.

I've removed the SSA validation around signed overflow checks (cc @vezenovm) as this is no longer necessary as we can rely on the testing of this new SSA pass to provide the same guarantees.

Additional Context

I've looked into the changes in higher_order_functions a little as I initially expected that this would not affect any of the build artifacts. It seems like the cause is that because the signed operation is marked as unchecked now, the LICM pass picks it up now which unlocks some extra optimization.

Documentation*

Check one:

  • No documentation needed.
  • Documentation included in this PR.
  • [For Experimental Features] Documentation to be submitted in a separate PR.

PR Checklist*

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

@TomAFrench
Copy link
Member Author

Code is relatively messy atm, I'll be doing a little bit of cleaning before marking as ready for review.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 1, 2025

Changes to Brillig bytecode sizes

Generated at commit: c55d16e5be0e5a6e9607579ac1a1eb4b1a730f68, compared to commit: 409c8386abbe83c133dfde1f4732d1b9834bb7cf

🧾 Summary (10% most significant diffs)

Program Brillig opcodes (+/-) %
higher_order_functions_inliner_zero -1 ✅ -0.15%

Full diff report 👇
Program Brillig opcodes (+/-) %
higher_order_functions_inliner_zero 651 (-1) -0.15%

@github-actions
Copy link
Contributor

github-actions bot commented Jul 1, 2025

Changes to number of Brillig opcodes executed

Generated at commit: c55d16e5be0e5a6e9607579ac1a1eb4b1a730f68, compared to commit: 409c8386abbe83c133dfde1f4732d1b9834bb7cf

🧾 Summary (10% most significant diffs)

Program Brillig opcodes (+/-) %
higher_order_functions_inliner_zero +6 ❌ +0.51%

Full diff report 👇
Program Brillig opcodes (+/-) %
higher_order_functions_inliner_zero 1,192 (+6) +0.51%

Copy link
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: 4063332 Previous: cf71daf Ratio
test_report_noir-lang_noir_bigcurve_ 392 s 263 s 1.49
test_report_zkpassport_noir_rsa_ 1 s 0 s +∞

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

CC: @TomAFrench

Copy link
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: 65ad060 Previous: cf71daf Ratio
rollup-block-root 289 s 197 s 1.47

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

CC: @TomAFrench

Copy link
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: 8302641 Previous: d31ebb9 Ratio
rollup-root 0.005 s 0.004 s 1.25

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

CC: @TomAFrench

@TomAFrench TomAFrench marked this pull request as ready for review July 2, 2025 12:28
@TomAFrench TomAFrench requested a review from a team July 2, 2025 12:28
/// something we take can advantage of in the [secondary_passes].
pub fn primary_passes(options: &SsaEvaluatorOptions) -> Vec<SsaPass> {
vec![
SsaPass::new(Ssa::expand_signed_checks, "expand signed checks"),
Copy link
Member Author

Choose a reason for hiding this comment

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

Having this at the very beginning of the SSA pipeline is intentional as I want to ensure that we replicate the current behaviour. In a followup PR we can push this further down so that we can ideally mark more signed operations as unchecked.

Comment on lines +384 to +437
/// Insert a numeric constant into the current function
fn numeric_constant(&mut self, value: impl Into<FieldElement>, typ: NumericType) -> ValueId {
self.context.dfg.make_constant(value.into(), typ)
}

/// Insert a binary instruction at the end of the current block.
/// Returns the result of the binary instruction.
fn insert_binary(&mut self, lhs: ValueId, operator: BinaryOp, rhs: ValueId) -> ValueId {
let instruction = Instruction::Binary(Binary { lhs, rhs, operator });
self.context.insert_instruction(instruction, None).first()
}

/// Insert a not instruction at the end of the current block.
/// Returns the result of the instruction.
fn insert_not(&mut self, rhs: ValueId) -> ValueId {
self.context.insert_instruction(Instruction::Not(rhs), None).first()
}

/// Insert a truncate instruction at the end of the current block.
/// Returns the result of the truncate instruction.
fn insert_truncate(&mut self, value: ValueId, bit_size: u32, max_bit_size: u32) -> ValueId {
self.context
.insert_instruction(Instruction::Truncate { value, bit_size, max_bit_size }, None)
.first()
}

/// Insert a cast instruction at the end of the current block.
/// Returns the result of the cast instruction.
fn insert_cast(&mut self, value: ValueId, typ: NumericType) -> ValueId {
self.context.insert_instruction(Instruction::Cast(value, typ), None).first()
}

/// Insert a [`Instruction::RangeCheck`] instruction at the end of the current block.
fn insert_range_check(
&mut self,
value: ValueId,
max_bit_size: u32,
assert_message: Option<String>,
) {
self.context.insert_instruction(
Instruction::RangeCheck { value, max_bit_size, assert_message },
None,
);
}

/// Insert a constrain instruction at the end of the current block.
fn insert_constrain(
&mut self,
lhs: ValueId,
rhs: ValueId,
assert_message: Option<ConstrainError>,
) {
self.context.insert_instruction(Instruction::Constrain(lhs, rhs, assert_message), None);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

These should perhaps be pushed onto the inner context as remove_bit_shifts has a lot of the same methods. Something for a followup however.

@TomAFrench
Copy link
Member Author

Yeah, I can (and probably should) add these but it's gonna basically require a complete revamp of these tests.

I'm going to punt this as it's forcing basically a complete rewrite of this test suite.

Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

🤞

I had some timeouts with the rollup-block-base contracts recently. Managed to reel it back in the stack overflow PR I have, but there I had a sense of why it might have slowed down. Is there anything here that could explain a longer compilation time?

@TomAFrench TomAFrench enabled auto-merge July 9, 2025 20:45
@TomAFrench TomAFrench added this pull request to the merge queue Jul 10, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jul 10, 2025
@aakoshh aakoshh added this pull request to the merge queue Jul 10, 2025
Merged via the queue into master with commit 2684469 Jul 10, 2025
118 checks passed
@aakoshh aakoshh deleted the tf/checked-signed-ops branch July 10, 2025 11:09
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.

Min != Full: Should never execute, yet degenerates into always-fail

4 participants