fix: handle unconditional break in SSA gen in all cases#8849
fix: handle unconditional break in SSA gen in all cases#8849
Conversation
Could we instead introduce a |
Oh, I mean, It would still be nice to eventually introduce |
Ah you're right I think it would not affect that code gen still would not return any value. Perhaps we can avoid the Option threading though? In the other PR we implemented an open/closed block state, which I would prefer to use here as well. Why was using this state insufficient in this case? I'm assuming it was insufficient because we are returning a unit value which we call Also, instead of |
|
I'll try the enum approach.
The problem with that approach is that we had to explicitly check whether a block was closed before continuing generating code in some cases. For example the codegen for array generates the code for each expression, then calls But I'll try the enum approach to see if it makes the code a bit better. |
Makes sense. Having to check the open/closed state in many places was part of the motivation of returning the control flow state explicitly. I think in this case it would be better to move an enum that tracks the control flow state and whether we have a divergence. |
|
I also got the issue snippet compiling and executing with this change: fn codegen_non_tuple_expression(&mut self, expr: &Expression) -> Result<ValueId, RuntimeError> {
let expression = self.codegen_expression(expr)?;
if self.builder.current_block_is_closed() {
let zero = self.builder.numeric_constant(0_u128, NumericType::NativeField);
return Ok(zero);
}
Ok(expression.into_leaf().eval(self))
} |
Maybe we should change it to not return an Option then. I don't know about the enum approach - I think that will end up being more code as well. I haven't gotten to read the PR yet but why do we return |
We could just error, however, in Rust the code in the issue snippet does compile and execute (although it does nothing aside break out of a loop). |
|
Right, in Rust it's a bit strange because the type of |
|
Maybe it'd be best to try to filter break/continue in statements before SSA then? We already have a check for the warning. Maybe we should filter out statements after that point. E.g. still elaborate them but don't insert them to check for type errors but don't actually insert them into the block. Then we should be done since our parser should only parse break/continue in statements and not arbitrary expressions. Edit: I suppose we'd still need to handle |
Yeah, it makes sense. It doesn't break any typing rules and composes better since |
Good point. We could define a variant like The first approach with |
Right, I was about to reply that I'll try the filtering approach (which was there some time ago before I made the typing like I commented above) but then in code like I'll still try to think of a way to avoid all of this |
I'd recommend an error variant like RuntimeError::Break. It's a bit of a misuse of Result but I think it works and isn't too bad in practice. At least while |
Ah, that was how I initially tried to implement it 😅 I got confused because I wasn't sure where I'd need to handle breaks (I initially thought it was in codegen_block) so I abandoned that approach. After implementing the current approach now I understand why it didn't work (or at least why I think it wouldn't easily work). The problem is that right now, in this PR's code, whenever we call
1 is the most common scenario. An example of 2 is when we generate an if's "then" branch: if the "then" had a break, we don't try to jump after the body because it already jumped, but we still continue switching to the "else" branch and generating that. If that retuned Another example is when generating a loop's body: it's fine if the body breaks, but we still want to call |
We'd have to handle it wherever there is branching. We can think of break as terminating the current line of execution so we either percolate up to the current loop, or we hit an expression like |
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: 39bdeb9 | Previous: bc6b68b | Ratio |
|---|---|---|---|
rollup-merge |
0.004 s |
0.003 s |
1.33 |
This comment was automatically generated by workflow using github-action-benchmark.
CC: @TomAFrench
|
I'll open another PR with that approach. |
Description
Problem
Resolves #8836
Summary
In the end I ended up implementing this in the way that I initially wanted to avoid, mainly because it involved a large change: changing
codegen_expressionto return an optional value instead of a value. No value is returned when a break or continue were found.Because a code like
{ break; 1 }can happen in any expression, there's a change that any expression doesn't return a value. In the compiler that translates tocodegen_expressionreturning an optional value too. Then we are forced to handle this missing value everywhere. Before this it was only handled in a few places but there was no way the compiler would force this handling like that.Additional Context
Let me know if there's a Rust pattern to avoid writing so many
let Some(...) = ... else { return Ok(None); }. The problem here is that it's an Option nested in a Result.Documentation
Check one:
PR Checklist
cargo fmton default settings.