fix: don't check for overflow when binary operation is simplified to constant#8843
fix: don't check for overflow when binary operation is simplified to constant#8843
Conversation
There was a problem hiding this comment.
⚠️ 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: 6df513b | Previous: 6343a30 | Ratio |
|---|---|---|---|
rollup-merge |
0.004 s |
0.003 s |
1.33 |
This comment was automatically generated by workflow using github-action-benchmark.
CC: @TomAFrench
There was a problem hiding this comment.
I'm not sure this is the best solution, as I would expect our simplifications to handle this case. If the operation can be simplified I would expect the overflow checks to be simplified as well.
This is our final SSA:
After Dead Instruction Elimination (3) (step 43):
g0 = i8 135
brillig(inline) predicate_pure fn main f0 {
b0():
v2 = cast u8 135 as i8
v3 = truncate v2 to 8 bits, max_bit_size: 9
v4 = cast v3 as u8
v6 = lt v4, u8 128
constrain v6 == u1 0, "attempt to subtract with overflow"
return v3
}
We know that we should be able to simplify this SSA. I think our signed cast simplification is incorrect. We are only simplifying if the signed cast is below 2^(bit_size - 1), however, this does not encompass fully how we represent signed integers. I think we could convert our unsigned type using IntegerConstant, and then we would also remove another place where we are duplicating field -> integer conversions.
This reverts commit 9ede0bb.
|
For this program: unconstrained fn main(x: i8) -> pub i8 {
x - 0
}we get this initial SSA: There are no constants anymore so I don't think the cast can be simplified. It still does the overflow check for the binary operation, but there's no need to do it because it's subtracting zero from it. Maybe zero is a special case, not sure... What I mean is, I'm not sure this can be solved by further simplifying the "check_overflow" function. |
|
Also this likely doesn't matter much because this only happens in the initial SSA codegen, so you'd really have to have |
Yes, that one cannot be simplified as it is dynamic, and thus why the instructions generated by The snippet in the issue #8833 still can be simplified, and points me to that the cast simplification is incorrect. I would also expect these programs to produce the same SSA: global a: i8 = -121;
unconstrained fn main() -> pub i8 {
let mut b: u32 = (25 - 0);
((-(-a)) - 1)
}No subtraction: global a: i8 = -122;
unconstrained fn main() -> pub i8 {
let mut b: u32 = 25;
(-(-a))
}With the current PR we would get the same SSA we get in the |
|
I now changed it to now to an overflow check if the binary operation was simplified to a constant. I personally think this makes sense, as if we were able to simplify the operation to a constant it means we were able to do it and there's no chance of an overflow, and I understand that there might be some things to fix or improve in signed cast operations, but I think we'd still want to avoid doing overflow checks when they aren't needed, right? |
Changes to number of Brillig opcodes executed
🧾 Summary (10% most significant diffs)
Full diff report 👇
|
Changes to Brillig bytecode sizes
🧾 Summary (10% most significant diffs)
Full diff report 👇
|
|
Apparently this also fixed #8305. I don't know if we'll merge, but I'll add a regression test for that. |
Yes if we can simplify to a constant there is no chance of an overflow. But that is why I would expect the overflow instructions to also be simplified out upon insertion. If overflow checks are not needed they should be simplified out.
The main benefit I see from this vs fixing signed simplifications is that we do not spend any time generating and simplifying instructions for This does not fix the issue if later SSA passes simplify a signed binary operation to a constant that does not overflow. Fixing simplification does handle that though. Now that we have moved away from checking for a constant zero and just a constant result I am ok with this change as it could provide a smaller IR earlier on (although I am not sure about this with fixed signed simplification), it is very little actual extra logic in code gen, and could provide some minor performance benefits. Although keeping this logic contained to the simplifier still provides a better separation of concerns in my opinion as the performance benefit from this change should still be minor and rare. I have pushed the signed cast fix here #8862 and I contend that this PR is an optimization rather than a fix. I'd like to get other team members thoughts on whether we want this optimization as I don't feel too strongly either way. |
|
Ah, I see now. I couldn't understand what the bug was, but now I understand that I agree, the other PR is the better way to solve this. I'm curious why the numbers aren't exactly the same in the "Changes to ..." sections, but the differences are minimal. |
I have not investigated but I imagine it is due to further simplifications being made possible by our SSA passes, while in this PR it is only for the initial SSA. |
Description
Problem
Resolves #8833
Resolves #8305
Summary
There's already a simplification that will turn
x - 0intox. However, when creating the initial SSA we insert an overflow check after some binary operations, even if those binary operations end up being simplified. I thought about not inserting the overflow check when the instruction is simplified, but we don't know what it was simplified to or, well, we could check if it's a binary, etc., but it would be a bit bug-prone to do that.Additional Context
Documentation
Check one:
PR Checklist
cargo fmton default settings.