-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[MIR] Reintroduce the unit temporary #30945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
// Even if we assign unconditionally, if the loop turns out to be diverging, | ||
// the loop’s “return” value will not appear in the MIR (removed along with the | ||
// exit_block by cfg simplification passes later). | ||
this.cfg.push_assign_unit(exit_block, expr_span, destination); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some doubts about suitability of this here. It relies on CFG passes removing the exit_block
in order to not introduce ill-typed MIR (see e.g. #30876) in case of loops with diverging types. I’m not sure if having that ill-typedness for a little while in an unreachable block is fine. Any thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually a little bit surprised by the behavior of the type checker here. It seems like loop { }
has type unit -- but, if you never break
out, we consider the loop non-terminating, and hence we allow it to be used in any context. This is probably not how I would want it to work in an ideal world, but it may not be worth trying to change anything here. :)
Therefore, to match the compiler as today, I suspect it should work like this:
- if you break out of the loop, then we will
push_assign_unit
- if you do not, we will not push any assignment at all
Of course we don't know whether you break out of the loop in advance. We could either analyze this, or maybe come back after the fact and check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trivial way to check would be to simply check if desination type is ()
or not. Provided typechecking/inference run before lowering into MIR, like they do now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It occurs to me we could also add this assignment as an "exiting the scope" action, much like drops. That is, we could record it in the LoopScope
, and when we are translating a break, we can insert the store.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not sure if having that ill-typedness for a little while in an unreachable block is fine.
I never answered this directly -- I'd prefer to avoid it, since I plan to be running various type-checking steps on the MIR before we do optimizations and cleanup.
6a704a1
to
04895a0
Compare
@nikomatsakis I updated the PR with the approach of storing a flag inside |
self.exit_scope(span, loop_scope.extent, block, exit_block); | ||
let (exit_block, extent) = { | ||
let loop_scope = self.find_loop_scope(span, label); | ||
loop_scope.might_terminate = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is only true for break
, not continue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something like
'a: while 1 != 2 {
loop { continue 'a }
}
is a valid code and essentially terminates the loop inside of which the expression resides?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nagisa yes, you're right, it depends on where you continue to I suppose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nagisa seems like we should toggle this flag in exit_scope
, not here, and then return the result (as I wrote elsewhere)
@nagisa left some nit-like comments. :) |
04895a0
to
132b263
Compare
|
||
// We merge iterators of regular scopes and loop scopes based on their extents. This code | ||
// relies on the assumption that every loop scope will have an extent of some non-popped | ||
// scope. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope this comment is a good enough explanation for the overly-smart code below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is some awfully clever code, I hope it's correct :) (I get nervous about iterators being off by one)
I used to have each scope have an Option<LoopScope>
embedded within, but I removed it at some point. Wonder if it'd be better that way. It was nice to be able to separate them, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option would be to allow a kind of marker to be pushed into the scopes array, so that as we iterate backwards we'd encounter a "track_break" marker and set the flag to true.
@nikomatsakis nit-like comments addressed. |
|
||
// start the loop | ||
this.cfg.terminate(block, Terminator::Goto { target: loop_block }); | ||
|
||
this.in_loop_scope(loop_block, exit_block, |this| { | ||
let may_term = unpack!(exit_block = this.in_loop_scope(loop_block, | ||
exit_block, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry to keep adding nits, but it seems like exit_block
never changes, it just flows around and around. Maybe we should just change the closure's return type to ()
, and then we can make in_loop_scope
return bool
and drop this awkward unpack
. The code will also be clearer in that it's clear that exit_block
never changes (and I can't see why you would want it to?)
132b263
to
57b6ace
Compare
An attempt to make loop body destination be optional, author implemented a pretty self contained change and deemed it to be (much) uglier than the alternative of just keeping the unit temporary. Having the temporary created lazily also has a nice property of not figuring in the MIR of functions which do not use loops of any sort.
57b6ace
to
f9f6e3a
Compare
if let Some(cond_expr) = opt_cond_expr { | ||
// This loop has a condition, ergo its exit_block is reachable. | ||
this.find_loop_scope(expr_span, None).might_break = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems fine; we could also check in the if
below and say if may_break or opt_cond_expr.is_some()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me it sounds like it is better to keep state consistent in case somebody else wants to use it later for whatever reasons.
This version looks good. I wonder if it's worth adding some tests (basically, the various cases we discussed), just to cement that this is the expected result of the compiler. Such tests may of course exist, I don't know. :( e.g.:
(also, I know that these tests wouldn't test mir, at least not until we port borrowck etc to mir.) |
well, r=me, I'll leave it to your discretion to consider whether adding tests seems to make sense, since I cannot decide :) |
@bors r=nikomatsakis 28638ed |
⌛ Testing commit 28638ed with merge 6271092... |
💔 Test failed - auto-linux-32-opt |
@bors: retry On Tuesday, January 19, 2016, bors notifications@github.com wrote:
|
Mostly testing borrowck and typeck behaviour in presence of `loop` expression
28638ed
to
8877cca
Compare
⌛ Testing commit 8877cca with merge d8c2360... |
💔 Test failed - auto-linux-64-nopt-t |
@bors retry |
As an attempt to make loop body destination be optional, author implemented a pretty self contained change and deemed it to be (much) uglier than the alternative of just keeping the unit temporary. Having the temporary created lazily also has a nice property of not figuring in the MIR of functions which do not use loops of any sort. r? @nikomatsakis
As an attempt to make loop body destination be optional, author implemented a pretty self contained
change and deemed it to be (much) uglier than the alternative of just keeping the unit temporary.
Having the temporary created lazily also has a nice property of not figuring in the MIR of
functions which do not use loops of any sort.
r? @nikomatsakis