-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Eager unreachable blocks for !
type
#47291
Conversation
src/test/run-pass/never-type-args.rs
Outdated
@@ -0,0 +1,40 @@ | |||
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT |
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.
There should be a MIR codegen test too. You can find some examples in src/test/mir-opt
.
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've added one — I wasn't 100% sure which MIR pass to check after, so I've gone for SimplifyCfg-initial
for now. I just tried to check that the right basic blocks were being marked as unreachable — let me know if this isn't quite stringent enough.
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.
SimplifyCfg-initial
is okay.
f2777f6
to
a441f9e
Compare
src/librustc/ty/mod.rs
Outdated
match self.sty { | ||
ty::TyNever => true, | ||
ty::TyRawPtr(ty_and_mut) | | ||
ty::TyRef(_, ty_and_mut) => ty_and_mut.ty.requires_never_value(), |
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 actually not as conservative as might be needed. =) For example, it is certainly wrong that *mut !
requires a never value -- it could legally be null. Similarly, there is some dispute about whether &!
is truly uninhabited, since in unsafe code such types can come to be (but never get dereferenced).
I would be happier if we just used TyNever
here -- but perhaps the real question is why we don't delegate to one of the existing "is uninhabitable" functions. Was this a deliberate decision, or simply a matter of not knowing about said functions? :)
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.
Ah, I shouldn't have forgotten about the weaker unsafe guarantees.
Not using an existing function was just an oversight! Would it be more appropriate to use conservative_is_uninhabited
or is_ty_uninhabited_from_all_modules
?
Edit: I've used conservative_is_uninhabited
(and moved it into a more convenient location) for now, but if that's not the most appropriate one, let me know!
src/librustc_const_eval/pattern.rs
Outdated
|
||
// Returns true if the pattern cannot bind, as it would require a value of type `!` to have | ||
// been constructed. This check is conservative. | ||
pub fn is_unreachable(&self) -> bool { |
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.
Similarly here, there is existing code that tries to make a similar judgement, I have to go digging around to find it, but I suspect we should be re-using it.
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 had a look around, and couldn't find anything that looked suitable, but I might have been looking in the wrong places. If you can give me a pointer, I'll replace this one!
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.
Let me look around.
@@ -38,6 +38,10 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { | |||
-> BlockAnd<()> { | |||
let discriminant_place = unpack!(block = self.as_place(block, discriminant)); | |||
|
|||
let arms: Vec<Arm<'tcx>> = arms.into_iter().filter(|arm| | |||
!arm.patterns.iter().any(|pat| pat.is_unreachable()) |
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.
Hmm, I'm not sure if this is quite right either. Consider a case where you have an arm with two patterns, and one of them is unreachable but the other isn't. e.g.
let x: Option<!> = None;
match x {
Some(_) | None => ...
}
woudn't this (single) arm be filtered out here? Or am I confused. Maybe we want .filter(|arm| arm.patterns.iter().any(|pat| !pat.is_unreachable()))
? i.e., keep the arm if any of its patterns are reachable? Or, probably, we want to filter out the patterns (and then filter out arms that have no patterns left)?
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.
Whoops, you're absolutely right — I put the !
in the wrong place.
Edit: Although filtering the patterns themselves is a better idea.
Looks like the change basically removed all the code from https://github.com/rust-lang/rust/blob/master/src/test/compile-fail/uninhabited-matches-feature-gated.rs, since it's using the I-thought-it-was-instant-UB line let x: Void = unsafe { std::mem::uninitialized() }; Failure from Travis: [00:59:51] ---- [compile-fail] compile-fail/uninhabited-matches-feature-gated.rs stdout ----
[00:59:51]
[00:59:51] error: /checkout/src/test/compile-fail/uninhabited-matches-feature-gated.rs:25: expected error not found: non-exhaustive
[00:59:51]
[00:59:51] error: 0 unexpected errors found, 1 expected errors not found
[00:59:51] status: exit code: 101
[00:59:51] command: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/src/test/compile-fail/uninhabited-matches-feature-gated.rs" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/compile-fail" "--target=x86_64-unknown-linux-gnu" "--error-format" "json" "-C" "prefer-dynamic" "-o" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/compile-fail/uninhabited-matches-feature-gated.stage2-x86_64-unknown-linux-gnu" "-Crpath" "-O" "-Zmiri" "-Zunstable-options" "-Lnative=/checkout/obj/build/x86_64-unknown-linux-gnu/native/rust-test-helpers" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/compile-fail/uninhabited-matches-feature-gated.stage2-x86_64-unknown-linux-gnu.aux" "-A" "unused"
[00:59:51] not found errors (from test file): [
[00:59:51] Error {
[00:59:51] line_num: 25,
[00:59:51] kind: Some(
[00:59:51] Error
[00:59:51] ),
[00:59:51] msg: "non-exhaustive"
[00:59:51] }
[00:59:51] ]
[00:59:51]
[00:59:51] thread '[compile-fail] compile-fail/uninhabited-matches-feature-gated.rs' panicked at 'explicit panic', tools/compiletest/src/runtest.rs:1170:13 |
The loss of diagnostics due to UB in code seems unfortunate, but it is true that the match cannot be reached in the first place, so it being exhaustive or not doesn’t really matter here. Should we try to also adjust the unreachable code lint to flag down cases like these? |
I'll remove the offending statements; the other ones look okay. I think ensuring the unreachable code lint flags all of the cases introduced here would definitely be helpful (though maybe in a follow-up), as the main problem this introduces is that this eliminates a lot of MIR that would otherwise be analysed for issues. Edit: #46164 covers extending the |
@@ -38,6 +38,10 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> { | |||
-> BlockAnd<()> { | |||
let discriminant_place = unpack!(block = self.as_place(block, discriminant)); | |||
|
|||
let arms: Vec<Arm<'tcx>> = arms.into_iter().filter(|arm| | |||
arm.patterns.iter().any(|pat| !pat.is_unreachable()) |
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.
given that we didn't notice that the condition here was wrong, can we make a test targeting that particular case?
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.
Yep, will do!
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've added a MIR test that differentiates between these two cases.
a082e14
to
9e50a1a
Compare
Hi @varkor -- so I was digging into this code the other day (just so you know I'm not ignoring it). Unfortunately, I don't seem to have kept many notes of my progress. :P In answer to your questions:
It seems like we need a helper for this. That is, I see that several other parts of the code make a decision based on the feature-gates that are enabled (in particular, That said, one thing that I was puzzling over was what role privacy ought to play here. The "uninhabited from all modules" helper will for example fail to find some types are uninhabited even if they are because the source code cannot observe the uninhabitedness (e.g., it's in a private field). It seems like we probably do want to respect privacy initially, though maybe once we start optimizing, we should ignore it (e.g., otherwise, the borrowck behavior might be affected by private fields, which seems bad). But for that matter we might want to use a more specific variant that just checks whether a type is uninhabited from the point of view of the code we are currently building.
I might have been wrong. There is definitely code that performs the same check -- notably the |
Still, I have to say that one other thing is bothering me. It feels like the original ICE, which occurred with this test:
is really arising because we are failing to do the "implicit coercion". That is, if you have the explicit I guess I feel a bit nervous about "doubling down" on the strategy of not generating code when we see e.g. local variables with type This is particularly true since I fear that any checks and code we write here will be conservative, and thus it may be that there are cases where indeed there are coercions-to- |
The main reason I didn't make use of the privacy-respecting uninhabited checks was because I was unsure of which situations one would not want the compiler to use all the information available to it for optimisation — intuitively, privacy is for the benefit of the user, and inhibiting optimisations because of it seems odd — though there could be some context there I'm missing. I suppose there could be transitive issues with dead code warnings for Yeah, if I remember correctly, the issue with the crash is failing to generate a To me the real issue seems to be "occurrences of values of If a fix solely for the missing That said, I do feel similarly in that it would be nice to find the missing |
Well, the point is that the MIR is used for more than optimization. It is also used for safety checks as well as (soon...) borrow checking. And borrow checking in particular takes flow information into account. This means that if you "observe" a private uninhabited field, then you may have code that borrow checks successfully, but if that private field later changes its type to be inhabited, then downstream consumers may be affected. I don't know how likely this scenario is, but it's the kind of thing that we are trying to avoid by making "inhabitedness checks" privacy respecting in the first place. We may well want another pass that runs after safety checks are done which ignores privacy. In any case, using the "from all modules" check is kind of strictly more conservative in any case.
Hmm. Yes, I see your argument. I am trying to figure out what I am worried about here, maybe it's ill-founded. |
OK, I've given this some more thought. I remain somewhat uncomfortable with this PR as is, although I think what it's doing makes sense. What makes me uncomfortable is that it feels just a bit ad-hoc -- that is, it's trying to do a kind of uniform "detect unreachable things" check, but somehow I don't feel "sure" that we're doing it in the right place or have covered all the cases. I am wondering if we can dig a bit more into the original ICE first -- I at least would like to better understand what goes wrong. Maybe I have to schedule a bit of time to dig into it myself, @vorner not sure if you can kind of explain what detective work you did? I guess it's also a good idea for me to offer an alternative: I think that I would feel mildly more comfortable if we implemented this via sort of "simple post pass" that detected "stores" to variables of type |
The main motivation for this change was reducing the amount of unnecessary work around unreachable code, rather than addressing #43061, which happened to be a happy side-effect, but I can see that by claiming to fix it, it somewhat seems like sweeping it under the rug. Considering this PR was intended to be behaviour-preserving, the fact that it isn't seems to draw more attention to the possibility it doesn't cover every case where unreachable could be emitted earlier. I do think separately fixing the ICE would make the PR seem like a more secure decision. Regarding locating the source of the original ICE: I think the most illustrative example so far is this one: fn foo(x: !) {
x; // ICEs
}
fn bar(x: !) {
{x}; // No problem!
} The type coercion seems relatively similar for the two: |
@varkor OK, so, digging a bit and leaving some notes in real time: When we type-check rust/src/librustc_typeck/check/mod.rs Lines 4238 to 4240 in 3a39b2a
As you can see, it calls rust/src/librustc_typeck/check/mod.rs Lines 2759 to 2761 in 3a39b2a
This call in turn invokes: rust/src/librustc_typeck/check/mod.rs Lines 2753 to 2757 in 3a39b2a
which bottoms out here: rust/src/librustc_typeck/check/mod.rs Lines 3471 to 3474 in 3a39b2a
Here, this an rust/src/librustc_typeck/check/mod.rs Lines 3645 to 3661 in 3a39b2a
and, on the way back up, we will notice that it is rust/src/librustc_typeck/check/mod.rs Lines 3498 to 3501 in 3a39b2a
In contrast, for rust/src/librustc_typeck/check/mod.rs Lines 3860 to 3862 in 3a39b2a
and so we invoke: rust/src/librustc_typeck/check/mod.rs Lines 4259 to 4261 in 3a39b2a
this will create a coercion. Since there are no rust/src/librustc_typeck/check/mod.rs Lines 4289 to 4293 in 3a39b2a
So, I guess this is the key difference. We insert a coercion always for blocks (because there is the potential of many paths), but we don't insert any coercion in the case of just Now one thing I'm a bit unclear on is just why it ICEs later -- @varkor did you leave notes as to precisely where the ICE occurs? |
I thought that the coercion in The ICE itself is from: rust/src/librustc_mir/borrow_check/nll/type_check/mod.rs Lines 720 to 730 in 4e3901d
! to a place with type () . I haven't really dug into it from that direction, though, because that seemed just to be a consequence of an earlier issue, probably some distance from the source.
|
@varkor thanks, that's interesting. I am trying to decide now what I think is the 'proper' fix. In some sense, I don't think that there is a "missing coercion". After all, the statement This implies that your initial instincts were right, and that MIR building ought to be handling rust/src/librustc_mir/build/expr/into.rs Lines 253 to 264 in 4e3901d
It falls out somewhat naturally there, since a Anyway, I think I would be happier with a more minimal version of this PR that introduces the "disconnect" whenever an expression of type I would have expected it to maybe be I think maybe it's the changes to matches and pattern that make me uncomfortable. That is, I'd rather cut off execution when we evaluate an expression to a value of type Does that make sense? |
I've investigated a little more, under the assumption the coercion wasn't the problem, and discovered some interesting points:
I'll also reply about this PR specifically then! |
@varkor nice, thanks for digging further in |
I've tracked down the ICE issue and have a fix here: #47746. To clarify my thoughts regarding this PR, which I think remains relevant (and in fact, is more so now that the ICE has been fixed separately). These optimisations are intended to decrease wasted time spent generating MIR for unreachable code, which is done to some extent, but to no significant degree currently.
Regarding the cases handled here: I agree that an ad-hoc approach to unreachability is a little unsatisfying. I'd like to be comprehensive, though. I think this is perhaps a case of "you've missed [this case], that could be handled similarly", rather than "these are just random constructs to optimise". These were the cases I thought of, but if there are more, I think they should be added too. Any time you guarantee a A post-pass (at least on the MIR) would be ineffective, because by that stage you've already spent all the time generating, etc. the code. And LLVM is going to do a decent-enough job of dead-code elimination afterwards. A post-pass at a higher level would be more effective, but I'm not sure if this is a plausible approach; this opportunistic "handle as we encounter it" approach seems to be somewhat sensible. |
Fix never-type rvalue ICE This fixes #43061. r? @nikomatsakis A small post-mortem as a follow-up to our investigations in #47291: The problem as I understand it is that when `NeverToAny` coercions are made, the expression/statement that is coerced may be enclosed in a block. In our case, the statement `x;` was being transformed to something like: `NeverToAny( {x;} )`. Then, `NeverToAny` is transformed into an expression: https://github.com/rust-lang/rust/blob/000fbbc9b8f88adc6a417f1caef41161f104250f/src/librustc_mir/build/expr/into.rs#L52-L59 Which ends up calling `ast_block_stmts` on the block `{x;}`, which triggers this condition: https://github.com/rust-lang/rust/blob/000fbbc9b8f88adc6a417f1caef41161f104250f/src/librustc_mir/build/block.rs#L141-L147 In our case, there is no return expression, so `push_assign_unit` is called. But the block has already been recorded as _diverging_, meaning the result of the block will be assigned to a location of type `!`, rather than `()`. This causes the MIR error. I'm assuming the `NeverToAny` coercion code is doing what it's supposed to (there don't seem to be any other problems), so fixing the issue simply consists of checking that the destination for the return value actually _is_ supposed to be a unit. (If no return value is given, the only other possible type for the return value is `!`, which can just be ignored, as it will be unreachable anyway.) I checked the other cases of `push_assign_unit`, and it didn't look like they could be affected by the divergence issue (blocks are kind of special-cased in this regard as far as I can tell), so this should be sufficient to fix the issue.
OK, so now that #47746 has landed, let's come back to this PR. I agree that there is probably more to be done here, though I'm still a bit torn on where and how it should be done (e.g., during MIR construction or as a later pass, etc). |
I am not sure if this time is really significant -- I don't think we expect a significant fraction of code to be stripped here. What worries me is more that we ensure we are consistent in terms of where we report borrowck errors or other sorts of errors, and things like that. I think the part of this PR that has made me the most nervous -- and I'm trying to put my finger on just why -- was the filtering of arms in a match. I'll have to re-read it. I think I would be happier if we could connect it to something more fundamental in MIR, e.g., if each time you assigned to a "place" of type
I agree that this is a danger. I am trying to decide if I'm making a reasonable objection, or if the right approach is to just keep fixing the edge cases until there are no more. |
@nikomatsakis and @rust-lang/compiler, what's the status on this? |
The status is that I haven't had time to look more closely at it yet. Sorry @varkor =) |
No problem at all — I've found other things to distract me for the time being! |
OK, I caught up on the conversation. I think I agree with basically everything that @arielb1 wrote. I'm trying to decide what that means precisely for this PR -- but I guess we can turn our attention temporarily to #50262 (and I've left a review there). I'm going to mark this as S-blocked for now until #50262 lands. |
Ping from triage. This PR has been blocked for over three months now. In line with the current triage guidelines I'm going to close it. Please reopen this or a new PR once the blocking PR has been merged. I forgot to say: Thanks a lot for your contribution to the Rust project! |
…, r=<try> Less conservative uninhabitedness check Extends the uninhabitedness check to structs, non-empty enums, tuples and arrays. Pulled out of #47291 and #50262. Blocked on #54123. r? @nikomatsakis
…dness-check, r=nikomatsakis Less conservative uninhabitedness check Extends the uninhabitedness check to structs, non-empty enums, tuples and arrays. Pulled out of rust-lang#47291 and rust-lang#50262. Fixes rust-lang#54586. r? @nikomatsakis
…, r=nikomatsakis Less conservative uninhabitedness check Extends the uninhabitedness check to structs, non-empty enums, tuples and arrays. Pulled out of #47291 and #50262. Fixes #54586. r? @nikomatsakis
This change optimises the generation of MIR in the presence of
!
. Blocks are now immediately marked as unreachable if:!
.!
.!
.This avoids unnecessary MIR generation and also fixes #43061 and fixes #41446.
r? @nikomatsakis