Question:
The match remains exhaustive with highlighted pattern removed, but it does not produce any warnings: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I know where the problem comes from: this bit of code tries to handle unreachable sub-or-patterns diagnostics, but it's a bit of a hack. What remains is to come up with a better solution. claim

//     (false | true, false | true) => {} // } // ``` <del> let mut unreachable_subpats = FxHashSet::default(); // Whether any branch at all is useful. </del> <ins> // If however the sub-branches come from the current column, they come from the inside of // the current or-pattern, and we want to keep them all. Eg. in the following, we _do_ want // to lint that the last `false` is unreachable. // ``` // match None { //     Some(false) => {} //     None | Some(true | false) => {} // } // ``` let mut matrix = matrix.clone(); // We keep track of sub-branches separately depending on whether they come from this column // or from others. let mut unreachables_this_column: FxHashSet<Span> = FxHashSet::default(); let mut unreachables_other_columns: Vec<FxHashSet<Span>> = Vec::default(); // Whether at least one branch is reachable. </ins> let mut any_is_useful = false; for v in vs { let res = is_useful(cx, &matrix, &v, witness_preference, hir_id, is_under_guard, false); match res { <del> Useful(pats) => { if !any_is_useful { any_is_useful = true; // Initialize with the first set of unreachable subpatterns encountered. unreachable_subpats = pats.into_iter().collect(); } else { // Keep the patterns unreachable from both this and previous branches. unreachable_subpats = pats.into_iter().filter(|p| unreachable_subpats.contains(p)).collect(); </del> <ins> Useful(unreachables) => { if let Some((this_column, other_columns)) = unreachables.split_last() { // We keep the union of unreachables found in the first column. unreachables_this_column.extend(this_column); // We keep the intersection of unreachables found in other columns. if unreachables_other_columns.is_empty() { unreachables_other_columns = other_columns.to_vec(); } else { unreachables_other_columns = unreachables_other_columns .into_iter() .zip(other_columns) .map(|(x, y)| x.intersection(&y).copied().collect()) .collect(); } </ins> } <ins> any_is_useful = true; </ins> } <del> NotUseful => unreachable_branches.push(v.head().span), UsefulWithWitness(_) => { bug!("Encountered or-pat in `v` during exhaustiveness checking") </del> <ins> NotUseful => { unreachables_this_column.insert(v.head().span); </ins> } <ins> UsefulWithWitness(_) => bug!( "encountered or-pat in the expansion of `_` during exhaustiveness checking" ), </ins> } <del> // If pattern has a guard don't add it to the matrix </del> <ins> // If pattern has a guard don't add it to the matrix. </ins> if !is_under_guard { <ins> // We push the already-seen patterns into the matrix in order to detect redundant // branches like `Some(_) | Some(0)`. </ins> matrix.push(v); } } <del> if any_is_useful { // Collect all the unreachable patterns. unreachable_branches.extend(unreachable_subpats); return Useful(unreachable_branches); </del> <ins> return if any_is_useful { let mut unreachables = if unreachables_other_columns.is_empty() { let n_columns = v.len(); (0..n_columns - 1).map(|_| FxHashSet::default()).collect() } else { unreachables_other_columns }; unreachables.push(unreachables_this_column); Useful(unreachables) </ins> } else { <del> return NotUseful; } </del> <ins> NotUseful }; </ins> } // FIXME(Nadrieril): Hack to work around type normalization issues (see #72476).


Question:
The match remains exhaustive with highlighted pattern removed, but it does not produce any warnings: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I know where the problem comes from: this bit of code tries to handle unreachable sub-or-patterns diagnostics, but it's a bit of a hack. What remains is to come up with a better solution. claim

hir::MatchSource::AwaitDesugar | hir::MatchSource::TryDesugar => {} } } <del> Useful(unreachable_subpatterns) => { for span in unreachable_subpatterns { </del> <ins> Useful(unreachables) => { let mut unreachables: Vec<_> = unreachables.into_iter().flatten().collect(); // Emit lints in the order in which they occur in the file. unreachables.sort_unstable(); for span in unreachables { </ins> unreachable_pattern(cx.tcx, span, id, None); } }


Question:
The match remains exhaustive with highlighted pattern removed, but it does not produce any warnings: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I know where the problem comes from: this bit of code tries to handle unreachable sub-or-patterns diagnostics, but it's a bit of a hack. What remains is to come up with a better solution. claim

(false | true, false | true) => {} } match (true, true) { <del> (true, false) => {} (false, true) => {} </del> <ins> (true, true) => {} (false, false) => {} </ins> (false | true, false | true) => {} } <ins> // https://github.com/rust-lang/rust/issues/76836 match None { Some(false) => {} None | Some(true | false) => {} //~ ERROR unreachable } </ins> // A subpattern that is unreachable in all branches is overall unreachable. match (true, true) { (false, true) => {}


Question:
The match remains exhaustive with highlighted pattern removed, but it does not produce any warnings: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I know where the problem comes from: this bit of code tries to handle unreachable sub-or-patterns diagnostics, but it's a bit of a hack. What remains is to come up with a better solution. claim

|              ^ error: unreachable pattern <del> --> $DIR/exhaustiveness-unreachable-pattern.rs:89:15 </del> <ins> --> $DIR/exhaustiveness-unreachable-pattern.rs:88:19 | LL |                 | false) => {} |                   ^^^^^ error: unreachable pattern --> $DIR/exhaustiveness-unreachable-pattern.rs:96:15 </ins> | LL |             | true) => {} |               ^^^^ error: unreachable pattern <del> --> $DIR/exhaustiveness-unreachable-pattern.rs:95:15 </del> <ins> --> $DIR/exhaustiveness-unreachable-pattern.rs:102:15 </ins> | LL |             | true, |               ^^^^ <del> error: aborting due to 18 previous errors </del> <ins> error: aborting due to 19 previous errors </ins>


Question:
Seaching for in the docs only surfaces . The solution seems to link from to so people can read more. However I'm not sure how to create a link between these since they live in separate crates. What's the right way to go about this? edit: if someone wants to pick this up, please do. This is probably a "good first issue" once we figure out how to link between the two. ! -- this is how I eventually learned about ; ideally it'd be easier to find. <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
These can't use intra-doc links, so you need to use relative links. I think would work.
Mentoring instructions: At add a link to .
Could I take this on as my first issue?
please do!
Feel free to use the command to assign the issue to yourself and show you're working on it
claim
yay; thanks for landing this

} /// Attribute macro applied to a static to register it as a global allocator. <ins> /// /// See also [`std::alloc::GlobalAlloc`](../std/alloc/trait.GlobalAlloc.html). </ins> #[stable(feature = "global_allocator", since = "1.28.0")] #[allow_internal_unstable(rustc_attrs)] #[rustc_builtin_macro]


Question:
The uses a heuristic based on the worst-case number of comparisons to determine between two strategies: , equivalent to pushing all elements of the other heap. the vector of the other heap and rebuilding the heap from scratch. I've done some benchmarking, and it turns out that method 1 (based on ) is always faster for two heaps based on randomly shuffled data – on the computers I've tested it on, anyway. I've included images of below: first the current strategy, then the strategy (method 1). In the benchmarks, two heaps are merged, one containing the number of elements on the x-axis, the other containing 100,000 minus that number (for the rightmost data points both heaps are equal in size). The red line (sometimes hiding behind the green one) is in both images that corresponding to , the other lines are not relevant to this issue (they aren't standard library types). ! ! From the jump in performance in the first graph, you can clearly see when the switch between the two approaches happens. Graphs are similar for smaller heaps, I didn't test larger ones. It's possible method 2 is faster under other circumstances (maybe if one of the heaps contains mostly very small/large elements?), especially if both heaps are (almost) equal in size. However, I think the benchmark is more close to the "average" real-world use case than such a case would be (I'd be happy to be proven wrong about this, though). <details<summarySimplification of the benchmark that only runs the parts relevant for this issue</summary</details
The comment suggests that the rebuild strategy is more efficient if at least one of the following is true: comparisons are very expensive the appended heap is much much larger than self but self must still contain a handful of elements so that the logarithm is 2 You can see how the slope of the append method is lower, so its advantage would show if you extended your benchmark by an order of magnitude. If you used a more expensive comparison function it would probably show earlier too.
The code for swaps the heaps if is larger than , so the appended heap is always smaller than or equal to the self heap. Indeed, the slope of the rebuild method is smaller, but I can't find conditions where that is outweighed by the higher base cost of the method. To simulate more expensive comparisons I've made a benchmark where each comparison involves some global atomic operations. Below that I've included a benchmark with ten times the number of items in the heaps in total (an order of magnitude more than that makes the benchmark very slow). The trends remain similar. ! ! The original implementation ( and ) seems to base its choice for heuristic mostly on the expectation it is a reasonable heuristic. However, I think reasoning from the worst-case scenario (O(n log(m))) is pessimistic for the method as a large number of es require only one or two comparisons (instead of log(m)) before ending up in the bottom two layers (that contain 75% of all items in the end). Maybe for even larger heaps the method actually is advantageous again, but I'm not sure how to determine a heuristic for that. <details<summaryChanged code for atomic benchmark</summary</details
Ah, I missed that. That indeed limits the opportunities to realize its advantage. The x-axis still says 50k though? First we would need a benchmark demonstrating that advantage anyway.
Only for the first image, that one has the same number of elements as before but a more expansive comparison implementation. The x-axis of the second image goes up to 500k, that one has normal "cheap" integer comparison but a larger number of items in the heaps. Certainly.
Naively, when I look at the graphs in the OP what I see isn't that extend is always going to be better -- the slopes seem to be clearly different, though two graphs with different scales and extra lines makes that comparison harder than it ought to be -- but that the current heuristic is doing a bad job of accounting for constant factors when picking the switchover point. Why isn't the answer here just use a number other than two in the heuristic?
Here's the two lines from a different measurement of the same benchmark in the same graph. It has a weird jump between and (artifact?), that's why included the more messy pictures initially. ! The slopes are indeed very different, and extrapolating them would lead to a crossover point. However, this crossover point can never actually be reached because you can't extrapolate beyond the rightmost data point. The benchmark is set up such that two heaps are merged, one containing x elements and the other 100,000 - x. The rightmost data point at x = 50,000 corresponds to merging two equally sized heaps. The data point that would be to the right of that (x = 52,500) would give the same result as the data point to the left (x = 47,500) because both involve merging a heap of 47,500 and 52,500 elements and the heaps are swapped in one of them to make sure the smaller heap is append to the larger one. In other words, beyond the rightmost data point the slopes change (become negative) and the data would mirror the data to the left of that point (which is why I didn't include it). The crossover point is thus only reachable (in this benchmark) if you'd remove the swap-optimization (theoretically I mean, practically that would worsen performance, or course). The TLDR is that it appears the number would have to be such that the condition always evaluates to . On another note, I've now also benchmarked merging very large heaps (100 million elements after merge), and is always more favorable there as well. !
Just tried the benchmark with changes from PR and scores significantly better. The heuristic could still be improved though. !
Thanks for the update to remind me about this One thing that I'd missed last time: ing from the contents of a can never hit the worst case (needing to bubble to the root every time), since it's being extended by something that's in roughly-descending order. Do you want to wait on before looking at more?
I think for now it would be better to wait and/or work towards a better heuristic (even though can perform better, it's still up to 25% slower when the second heap is much smaller). PS/Disclaimer: I'm the author of and not a reviewer, so I may be a little biased, however the benchmarks should speak for themself. Would be nice someone else could confirm my claims
Nice performance improvements in , just a pity it makes determining the switchover heuristic more difficult ;). I ran the benchmark again with and on my machine it also improves 's performance enough that it's the faster method when merging larger heaps. I guess it doesn't make much sense to merge as-is because of that. I'll have a look if I can determine a better heuristic based on the improved performance.
In order to determine the new switchover point, I ran some benchmarks for heaps of different lengths. For a constant total heap length (heap length after the operation), the execution time has an (approximately) linear relationship with the length of the smaller heap. This means the data can be fitted with a least-squares linear fit: ! The blue line is the rebuild strategy, and purple the extend strategy. The green vertical line indicates the intersection of the fitted lines, i.e. where the switchover between strategies should ideally be. The red vertical line indicates the switchover point using the current heuristic. The fits for all the different other lengths can be found in the collapsible section at the end of this comment. I've plotted the location of the ideal switchover point as a function of the total heap length (the current heuristic is not a smooth curve because it uses a fast but inexact log2): ! I think a couple points are noteworthy: The current heuristic is quite close to the ideal point for smaller heaps. The switchover point stays roughly constant (when considered as a percentage as in the graph above) for heap lengths 1000 to . For larger heaps, the ideal switchover point rises again, which is not reflected in the current heuristic. I am not sure how to best model a heuristic for larger heaps, especially because for the power-of-2 heaps show it drops again for the two largest heaps. However, I think it should be fairly straightforward to improve the current heuristic by setting a lower bound on the switchover point: This results in the green curve in the graph above. It is closer to the ideal switchover point in all cases, but for larger heaps it is still suboptimal. I'd love to hear ideas on how to improve the heuristic further for larger heaps. <details<summaryImages of all fits</summaryIn all graphs, blue is the rebuild strategy and purple the extend strategy. The dots indicate raw data points and the line is a least-squares linear fit. The green vertical line indicates the intersection of the fitted lines, i.e. where the switchover between strategies should ideally be. The red vertical line indicates the current switchover point. ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! </details

// and about 2 * (len1 + len2) comparisons in the worst case // while `extend` takes O(len2 * log(len1)) operations // and about 1 * len2 * log_2(len1) comparisons in the worst case, <del> // assuming len1 >= len2. </del> <ins> // assuming len1 >= len2. For larger heaps, the crossover point // no longer follows this reasoning and was determined empirically. </ins> #[inline] fn better_to_rebuild(len1: usize, len2: usize) -> bool { <del> 2 * (len1 + len2) < len2 * log2_fast(len1) </del> <ins> let tot_len = len1 + len2; if tot_len <= 2048 { 2 * tot_len < len2 * log2_fast(len1) } else { 2 * tot_len < len2 * 11 } </ins> } if better_to_rebuild(self.len(), other.len()) {


Question:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried this code: I expected to see this happen: Compiler doesn't generate any warnings Instead, this happened: Rustc says that is unused, even though it is declared as in asm. Adding arrows () fixes it <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
Interesting bug! I used to determine that this bug has been around . and doing some debugging, I found that for all live nodes in , is never marked as being used in the RWU table. It only gets marked as read/written. , this is part of the information used to determine that is unused. That doesn't really make much sense though- does seem to fit the case where a variable is used as both an input and an output to some arbitrary assembly instruction. I'll go ahead and claim this. I have a solution in mind, just going to ask around zulip first before putting out a PR. claim

} } hir::InlineAsmOperand::InOut { expr, .. } => { <del> succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE); </del> <ins> succ = self.write_place(expr, succ, ACC_READ | ACC_WRITE | ACC_USE); </ins> } hir::InlineAsmOperand::SplitInOut { out_expr, .. } => { if let Some(expr) = out_expr {


Question:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried this code: I expected to see this happen: Compiler doesn't generate any warnings Instead, this happened: Rustc says that is unused, even though it is declared as in asm. Adding arrows () fixes it <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
Interesting bug! I used to determine that this bug has been around . and doing some debugging, I found that for all live nodes in , is never marked as being used in the RWU table. It only gets marked as read/written. , this is part of the information used to determine that is unused. That doesn't really make much sense though- does seem to fit the case where a variable is used as both an input and an output to some arbitrary assembly instruction. I'll go ahead and claim this. I have a solution in mind, just going to ask around zulip first before putting out a PR. claim

<ins> // Ensure inout asm! operands are marked as used by the liveness pass // only-x86_64 // check-pass #![feature(asm)] #![allow(dead_code)] #![warn(unused_assignments)] #![warn(unused_variables)] // Test the single inout case unsafe fn f1(mut src: *const u8) { asm!("/*{0}*/", inout(reg) src); //~ WARN value assigned to `src` is never read } unsafe fn f2(mut src: *const u8) -> *const u8 { asm!("/*{0}*/", inout(reg) src); src } // Test the split inout case unsafe fn f3(mut src: *const u8) { asm!("/*{0}*/", inout(reg) src => src); //~ WARN value assigned to `src` is never read } unsafe fn f4(mut src: *const u8) -> *const u8 { asm!("/*{0}*/", inout(reg) src => src); src } // Tests the use of field projections struct S { field: *mut u8, } unsafe fn f5(src: &mut S) { asm!("/*{0}*/", inout(reg) src.field); } unsafe fn f6(src: &mut S) { asm!("/*{0}*/", inout(reg) src.field => src.field); } fn main() {} </ins>


Question:
<!-- Thank you for filing a bug report! Please provide a short summary of the bug, along with any information you feel relevant to replicating the bug. --I tried this code: I expected to see this happen: Compiler doesn't generate any warnings Instead, this happened: Rustc says that is unused, even though it is declared as in asm. Adding arrows () fixes it <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
Interesting bug! I used to determine that this bug has been around . and doing some debugging, I found that for all live nodes in , is never marked as being used in the RWU table. It only gets marked as read/written. , this is part of the information used to determine that is unused. That doesn't really make much sense though- does seem to fit the case where a variable is used as both an input and an output to some arbitrary assembly instruction. I'll go ahead and claim this. I have a solution in mind, just going to ask around zulip first before putting out a PR. claim

<ins> warning: value assigned to `src` is never read --> $DIR/liveness-asm.rs:13:32 | LL |     asm!("/*{0}*/", inout(reg) src); |                                ^^^ | note: the lint level is defined here --> $DIR/liveness-asm.rs:8:9 | LL | #![warn(unused_assignments)] |         ^^^^^^^^^^^^^^^^^^ = help: maybe it is overwritten before being read? warning: value assigned to `src` is never read --> $DIR/liveness-asm.rs:23:39 | LL |     asm!("/*{0}*/", inout(reg) src => src); |                                       ^^^ | = help: maybe it is overwritten before being read? warning: 2 warnings emitted </ins>


Question:
Hello, this is your friendly neighborhood mergebot. After merging PR rust-lang/rust, I observed that the tool rustfmt no longer builds. A follow-up PR to the repository is needed to fix the fallout. cc do you think you would have time to do the follow-up work? If so, that would be great!
Hi can you please the command to replicate this bug? Thanks
with set in
won't be able to get to this myself in the next few days. To fix this we probably have to compile it with , find the place where we call and put a before that
Hi the error occurred where we just erasing regions by simply doing this (tcx.eraseregions(&traitref)) and provided assertion with (tcx.normalizeerasingregions(paramenv, traitref)). Is this scenario correct? Just want to confirm as you already mentioned to put a tcx.normalizeerasingregions. Thanks
yes, so we do not want to use in as that can be a bit costly but instead normalize eagerly before then. To detect cases where we are still using unnormalized values a debug assert there. If we look at the backtrace we can find out where the is coming from and normalize it there
okay, thanks for the update.
Hi as you mentioned can you please elaborate it please? As I can see the Instace::resolve in but don't get you point to put tcx.normalizeerasing_regions. Thanks
Can you post the resulting backtrace using ?
here it is:
So I think that the easiest fix is to add before this call. I am however surprised that isn't actually normalized here. cc do we want to instead normalize this in mir building?

let func_ty = func.ty(body, tcx); if let ty::FnDef(callee, substs) = *func_ty.kind() { <del> let (callee, call_substs) = if let Ok(Some(instance)) = Instance::resolve(tcx, param_env, callee, substs) { (instance.def_id(), instance.substs) } else { (callee, substs) }; </del> <ins> let normalized_substs = tcx.normalize_erasing_regions(param_env, substs); let (callee, call_substs) = if let Ok(Some(instance)) = Instance::resolve(tcx, param_env, callee, normalized_substs) { (instance.def_id(), instance.substs) } else { (callee, normalized_substs) }; </ins> // FIXME(#57965): Make this work across function boundaries


Question:
dilbert-feed is ICEing on beta with error: internal compiler error: could not fully normalize https://crater-
Full error log:
I'll bisect this one
Doesn't happen on the latest commit by on .
I can still reproduce with :
It may just be an instance of me not doing something right, I used:
gives me searched toolchains through regression in So this would have regressed in , cc
The PR did introduce some additional uses of inside specialized implementations. itself it is a stable function so I am not aware of any restrictions on its use. Perhaps this is some interaction with specialization?
Both sites where it is used are of this pattern: They're just there as hints to to let llvm DCE the dropinplace earlier. So if needed removing them should be safe.
Assigning as and removing .
Assigning myself to revert on beta (and potentially nightly, depending on if we can get a fix soon).
Do we know if removing fixes the problem? We also could use an MVCE, right?
MCVE would be good, yes.
I hit (what seems like) this ICE with in a pretty small experiment, would it be useful to try reducing that?
Yes, it would be -- even if it's a separate issue we can just open that :) MCVE is always useful.
ping cleanup This issue needs an MVCE
Hey Cleanup Crew ICE-breakers! This bug has been identified as a good "Cleanup ICE-breaking candidate". In case it's useful, here are some [instructions] for tackling these sorts of bugs. Maybe take a look? Thanks! <3 [instructions]: https://rustc-dev- cc
Here's my nightly MCVE: Interestingly, it goes away when putting all of the code into one crate. <details<summaryFull error message (with <codeRUST_BACKTRACE=1</code):</summary</details
Hmm, this may be a distinct problem, but maybe not. Hard to tell.
I'm re-adding the MCVE label because the crate on beta does not require features, so we should be able to come up with a non-nightly MCVE. (Also, it would be good to have a single-crate MCVE, but perhaps the multicrate nature is critical here?)
cc , which may be the same underlying bug (but pre-existing?)
hey , did you manage to land a revert of on beta (and nightly?)?
If reverting the PR proves difficult more targeted options would be either removing the use of or only removing / stubbing while leaving the rest of the machinery unchanged. But the former may be insufficient since it also uses similar methods such as and .
Do we think given that we're likely to fix the regression here at least in and we should close this bug? Neither of those PRs fixes the underlying issue (whatever it is) since the lack of normalization is almost certainly still present, but we also don't have (AFAIK) a good test case here for the bug.
I expect so.

impl<T> Drop for InPlaceDrop<T> { #[inline] fn drop(&mut self) { <del> if mem::needs_drop::<T>() { unsafe { ptr::drop_in_place(slice::from_raw_parts_mut(self.inner, self.len())); } </del> <ins> unsafe { ptr::drop_in_place(slice::from_raw_parts_mut(self.inner, self.len())); </ins> } } }


Question:
dilbert-feed is ICEing on beta with error: internal compiler error: could not fully normalize https://crater-
Full error log:
I'll bisect this one
Doesn't happen on the latest commit by on .
I can still reproduce with :
It may just be an instance of me not doing something right, I used:
gives me searched toolchains through regression in So this would have regressed in , cc
The PR did introduce some additional uses of inside specialized implementations. itself it is a stable function so I am not aware of any restrictions on its use. Perhaps this is some interaction with specialization?
Both sites where it is used are of this pattern: They're just there as hints to to let llvm DCE the dropinplace earlier. So if needed removing them should be safe.
Assigning as and removing .
Assigning myself to revert on beta (and potentially nightly, depending on if we can get a fix soon).
Do we know if removing fixes the problem? We also could use an MVCE, right?
MCVE would be good, yes.
I hit (what seems like) this ICE with in a pretty small experiment, would it be useful to try reducing that?
Yes, it would be -- even if it's a separate issue we can just open that :) MCVE is always useful.
ping cleanup This issue needs an MVCE
Hey Cleanup Crew ICE-breakers! This bug has been identified as a good "Cleanup ICE-breaking candidate". In case it's useful, here are some [instructions] for tackling these sorts of bugs. Maybe take a look? Thanks! <3 [instructions]: https://rustc-dev- cc
Here's my nightly MCVE: Interestingly, it goes away when putting all of the code into one crate. <details<summaryFull error message (with <codeRUST_BACKTRACE=1</code):</summary</details
Hmm, this may be a distinct problem, but maybe not. Hard to tell.
I'm re-adding the MCVE label because the crate on beta does not require features, so we should be able to come up with a non-nightly MCVE. (Also, it would be good to have a single-crate MCVE, but perhaps the multicrate nature is critical here?)
cc , which may be the same underlying bug (but pre-existing?)
hey , did you manage to land a revert of on beta (and nightly?)?
If reverting the PR proves difficult more targeted options would be either removing the use of or only removing / stubbing while leaving the rest of the machinery unchanged. But the former may be insufficient since it also uses similar methods such as and .
Do we think given that we're likely to fix the regression here at least in and we should close this bug? Neither of those PRs fixes the underlying issue (whatever it is) since the lack of normalization is almost certainly still present, but we also don't have (AFAIK) a good test case here for the bug.
I expect so.

} fn drop_remaining(&mut self) { <del> if mem::needs_drop::<T>() { unsafe { ptr::drop_in_place(self.as_mut_slice()); } </del> <ins> unsafe { ptr::drop_in_place(self.as_mut_slice()); </ins> } self.ptr = self.end; }


Question:
When panicking, every line of stack traces contain instead of a code location. E.g. I'm on rustc 1.47.0 and FreeBSD 12.1: Possible duplicate of I've tried mounting /proc but it didn't fix the problem.
Can you test on old Rust stable version to see if this is a regression ? Edit: This is an expected regression
It worked on 1.46.0:
Do you know if there is a build farm for freebsd like gcc build farm ? Because we could fo find from which PR caused the regression. Otherwise, since freebsd is a tier-2 target, not everyone could investigate it. modify labels: T-compiler
Error: Label P-medium can only be set by Rust team members Please let know if you're having trouble with this bot.
Assigning as discussed as part of the and removing .) and removing .
For what it's worth, this problem occurs on iOS (x86_64-apple-ios) builds too: 1.47.0: 1.46.0:
Likely caused by . (switching from libbacktrace to gimli) It seems that is not implemented for FreeBSD: I think this means that it can't find the debuginfo for any libraries and the current executable.
For iOS it may or may not work to extend the condition at to include iOS too.
You're right! I submitted a PR to fix it for iOS. Thanks a lot!
You might want to file another PR to update backtrace-rs submodule in this repo.
Thanks!
It looks like , so I think we could just extend to cover FreeBSD
Hi, could you ( or ) use rustup- toolchain-install-master to install and try this build: for freebsd?
Looks good!
backtrace-rs issue: backtrace-rs PR for issue:
What's left to do here? Would a PR that updates the submodule for library/backtrace be sufficient?

<del> Subproject commit af078ecc0b069ec594982f92d4c6c58af99efbb5 </del> <ins> Subproject commit 710fc18ddcb6c7677b3c96359abb35da37f2a488 </ins>


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

code_offset: usize, margin: Margin, ) { <ins> // Tabs are assumed to have been replaced by spaces in calling code. assert!(!source_string.contains('t')); </ins> let line_len = source_string.len(); // Create the source line we will highlight. let left = margin.left(line_len);


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

} let source_string = match file.get_line(line.line_index - 1) { <del> Some(s) => s, </del> <ins> Some(s) => replace_tabs(&*s), </ins> None => return Vec::new(), };


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

let file = annotated_file.file.clone(); let line = &annotated_file.lines[line_idx]; if let Some(source_string) = file.get_line(line.line_index - 1) { <del> let leading_whitespace = source_string.chars().take_while(|c| c.is_whitespace()).count(); </del> <ins> let leading_whitespace = source_string .chars() .take_while(|c| c.is_whitespace()) .map(|c| { match c { // Tabs are displayed as 4 spaces 't' => 4, _ => 1, } }) .sum(); </ins> if source_string.chars().any(|c| !c.is_whitespace()) { whitespace_margin = min(whitespace_margin, leading_whitespace); }


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

self.draw_line( &mut buffer, <del> &unannotated_line, </del> <ins> &replace_tabs(&unannotated_line), </ins> annotated_file.lines[line_idx + 1].line_index - 1, last_buffer_line_num, width_offset,


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

); // print the suggestion draw_col_separator(&mut buffer, row_num, max_line_num_len + 1); <del> buffer.append(row_num, line, Style::NoStyle); </del> <ins> buffer.append(row_num, &replace_tabs(line), Style::NoStyle); </ins> row_num += 1; }


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

} } <ins> fn replace_tabs(str: &str) -> String { str.replace('t', "    ") } </ins> fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) { buffer.puts(line, col, "| ", Style::LineNumber); }


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

StyledBuffer { text: vec![], styles: vec![] } } <del> fn replace_tabs(&mut self) { for (line_pos, line) in self.text.iter_mut().enumerate() { let mut tab_pos = vec![]; for (pos, c) in line.iter().enumerate() { if *c == 't' { tab_pos.push(pos); } } // start with the tabs at the end of the line to replace them with 4 space chars for pos in tab_pos.iter().rev() { assert_eq!(line.remove(*pos), 't'); // fix the position of the style to match up after replacing the tabs let s = self.styles[line_pos].remove(*pos); for _ in 0..4 { line.insert(*pos, ' '); self.styles[line_pos].insert(*pos, s); } } } } </del> <ins> pub fn render(&self) -> Vec<Vec<StyledString>> { // Tabs are assumed to have been replaced by spaces in calling code. assert!(self.text.iter().all(|r| !r.contains(&'t'))); </ins> <del> pub fn render(&mut self) -> Vec<Vec<StyledString>> { </del> let mut output: Vec<Vec<StyledString>> = vec![]; let mut styled_vec: Vec<StyledString> = vec![]; <del> // before we render, replace tabs with spaces self.replace_tabs(); </del> for (row, row_style) in self.text.iter().zip(&self.styles) { let mut current_style = Style::NoStyle; let mut current_text = String::new();


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

<ins> // Test for #78438: ensure underline alignment with many tabs on the left, long line on the right // ignore-tidy-linelength // ignore-tidy-tab fn main() { let money = 42i32; match money { v @ 1 | 2 | 3 => panic!("You gave me too little money {}", v), // Long text here: TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT //~^ ERROR variable `v` is not bound in all patterns v => println!("Enough money {}", v), } } </ins>


Question:
The code that truncates long lines doesn't take (hard) tabs into account, so the lines printed are off. Example code: Gives the following error: If you use four spaces instead of each tab it works as expected: cc and cc <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOTEND --
I thought we were already accounting for tabs, but looking we rely on which considers to not have a width, which we then assume to be 1.
claim

<ins> error[E0408]: variable `v` is not bound in all patterns --> $DIR/tabs-trimming.rs:9:16 | LL | ...   v @ 1 | 2 | 3 => panic!("You gave me too little money {}", v), // Long text here: TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT... |       -       ^   ^ pattern doesn't bind `v` |       |       | |       |       pattern doesn't bind `v` |       variable not in all patterns error: aborting due to previous error For more information about this error, try `rustc --explain E0408`. </ins>


Question:
For details see , opening an issue since PR was closed:
While this one particular test case was fixed, the transformation still suffers from issues hinted at earlier comment: assumes that terminator always switches on a local that was assigned last in the block. The assumption is false. moves a read of a discriminant from the second block into first without ensuring that it holds the same value still. some executions it introduces a read that would not have happen otherwise and could have an undefined behaviour. some executions it skips execution of statements from one the blocks without checking if they are side-effect free. cc
, EarlyOtherwiseBranch only applies to basic blocks of which the last statement is a discriminant (and the terminator is a switch): And, We could require the discriminant read in the second basic block to be the only statement within the block. Not sure by how much would the change affect optimization hit rate, but at least the example in would still be optimized. What makes me really concerned is this one: When would the first discriminant be safe but not the second? The example you constructed is a legit one: the second read assumes the value of the first read and does a downcast; that is the case my patch catches. The only other possible invalid case I could think of, is that when the second discriminant call's argument is an invalid pointer. Probably something like: But today the MIR that FE emits dodges the problem by accident: Because these two switch instructions have different blocks. So, my guess is, even though EarlyOtherwiseBranch is unsound, today the compiler FE is still unable to produce MIR that could trigger that.
If one considers shape of MIR immediately after it has been built, that is probably true. One thing to keep mind: this transformation runs on MIR that have been transformed already. In fact, the whole optimization pipeline might have run an arbitrary number of times as a result of inlining. Transformations shouldn't rely on any invariants that aren't preserved by all transformations. The might be unrelated to a local used in switch, since this is not checked anywhere. Last time out of curiosity I an assertion checking that, enabled the transform by default and bootstraped rustc, the assertion did fail.
Ah! I misunderstood your point , but now I get it. You are right. I think we could resolve your , , and by adding the following restrictions: discriminant read in the second block must be the only statement in there; switch must use the value of the discriminant read. But I found that the same effect of EarlyOtherwiseBranch could be achieved through a much easier change; we could provide a hint for LLVM optimizer to compare the discriminant values before entering the switch, e.g.: With this hint, (unfortunately, isn't enough) produces: IMO this is much more elegant than EarlyOtherwiseBranch; it should also be pretty easy to implement, so I will give it a try.

return None; } <ins> // when the second place is a projection of the first one, it's not safe to calculate their discriminant values sequentially. // for example, this should not be optimized: // // ```rust // enum E<'a> { Empty, Some(&'a E<'a>), } // let Some(Some(_)) = e; // ``` // // ```mir // bb0: { //   _2 = discriminant(*_1) //   switchInt(_2) -> [...] // } // bb1: { //   _3 = discriminant(*(((*_1) as Some).0: &E)) //   switchInt(_3) -> [...] // } // ``` let discr_place = discr_info.place_of_adt_discr_read; let this_discr_place = this_bb_discr_info.place_of_adt_discr_read; if discr_place.local == this_discr_place.local && this_discr_place.projection.starts_with(discr_place.projection) { trace!("NO: one target is the projection of another"); return None; } </ins> // if we reach this point, the optimization applies, and we should be able to optimize this case // store the info that is needed to apply the optimization


Question:
For details see , opening an issue since PR was closed:
While this one particular test case was fixed, the transformation still suffers from issues hinted at earlier comment: assumes that terminator always switches on a local that was assigned last in the block. The assumption is false. moves a read of a discriminant from the second block into first without ensuring that it holds the same value still. some executions it introduces a read that would not have happen otherwise and could have an undefined behaviour. some executions it skips execution of statements from one the blocks without checking if they are side-effect free. cc
, EarlyOtherwiseBranch only applies to basic blocks of which the last statement is a discriminant (and the terminator is a switch): And, We could require the discriminant read in the second basic block to be the only statement within the block. Not sure by how much would the change affect optimization hit rate, but at least the example in would still be optimized. What makes me really concerned is this one: When would the first discriminant be safe but not the second? The example you constructed is a legit one: the second read assumes the value of the first read and does a downcast; that is the case my patch catches. The only other possible invalid case I could think of, is that when the second discriminant call's argument is an invalid pointer. Probably something like: But today the MIR that FE emits dodges the problem by accident: Because these two switch instructions have different blocks. So, my guess is, even though EarlyOtherwiseBranch is unsound, today the compiler FE is still unable to produce MIR that could trigger that.
If one considers shape of MIR immediately after it has been built, that is probably true. One thing to keep mind: this transformation runs on MIR that have been transformed already. In fact, the whole optimization pipeline might have run an arbitrary number of times as a result of inlining. Transformations shouldn't rely on any invariants that aren't preserved by all transformations. The might be unrelated to a local used in switch, since this is not checked anywhere. Last time out of curiosity I an assertion checking that, enabled the transform by default and bootstraped rustc, the assertion did fail.
Ah! I misunderstood your point , but now I get it. You are right. I think we could resolve your , , and by adding the following restrictions: discriminant read in the second block must be the only statement in there; switch must use the value of the discriminant read. But I found that the same effect of EarlyOtherwiseBranch could be achieved through a much easier change; we could provide a hint for LLVM optimizer to compare the discriminant values before entering the switch, e.g.: With this hint, (unfortunately, isn't enough) produces: IMO this is much more elegant than EarlyOtherwiseBranch; it should also be pretty easy to implement, so I will give it a try.

<ins> // run-pass // compile-flags: -Z mir-opt-level=2 -C opt-level=0 // example from #78496 pub enum E<'a> { Empty, Some(&'a E<'a>), } fn f(e: &E) -> u32 { if let E::Some(E::Some(_)) = e { 1 } else { 2 } } fn main() { assert_eq!(f(&E::Empty), 2); } </ins>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --Playground stable: Playground nightly: Somewhat similar to Edit: Actually, it's identical except this is with two lifetimes. Edit 2: And this one crashes stable, while the other does not. Also crashes without enabling features, and complains that However, constraining removes this error, but not the crash. Two lifetimes are required for the crash. <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: Also crashes on Rust Playground with stable <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
glacier ""
Seems related to
No longer ICEs since

<ins> // check-pass #![feature(type_alias_impl_trait)] pub trait Trait { type A; fn f() -> Self::A; } pub trait Tr2<'a, 'b> {} pub struct A<T>(T); pub trait Tr { type B; } impl<'a, 'b, T: Tr<B = dyn Tr2<'a, 'b>>> Trait for A<T> { type A = impl core::fmt::Debug; fn f() -> Self::A {} } fn main() {} </ins>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --from <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
This is probably
Hm, the issue is already a couple of weeks old while I was not able to reproduce this one a few days ago.
This happens since :thinking:
modify labels: +requires-nightly
Yes, this is a duplicate of . The changes from fixed the validator, so the bug is now correctly identified when incorrect transform is enabled with .
Does it still ICE if you enable optimizations?
It does, looks like it's still an ICE
This (and ) no longer ICE since , seems to be the case for any opt level cc is that intentional or is still pending?
This is only the case because the changes to MIR make that opt completely broken and just never fire under any circumstances. needs to be revised because of that change, but the plan is still for it to fix these bugs for real

pub mod simplify; mod simplify_branches; mod simplify_comparison_integral; <del> mod simplify_try; </del> mod sroa; mod uninhabited_enum_branching; mod unreachable_prop;


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --from <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
This is probably
Hm, the issue is already a couple of weeks old while I was not able to reproduce this one a few days ago.
This happens since :thinking:
modify labels: +requires-nightly
Yes, this is a duplicate of . The changes from fixed the validator, so the bug is now correctly identified when incorrect transform is enabled with .
Does it still ICE if you enable optimizations?
It does, looks like it's still an ICE
This (and ) no longer ICE since , seems to be the case for any opt level cc is that intentional or is still pending?
This is only the case because the changes to MIR make that opt completely broken and just never fire under any circumstances. needs to be revised because of that change, but the plan is still for it to fix these bugs for real

&o1(simplify_branches::SimplifyConstCondition::new("after-const-prop")), &early_otherwise_branch::EarlyOtherwiseBranch, &simplify_comparison_integral::SimplifyComparisonIntegral, <del> &simplify_try::SimplifyArmIdentity, &simplify_try::SimplifyBranchSame, </del> &dead_store_elimination::DeadStoreElimination, &dest_prop::DestinationPropagation, &o1(simplify_branches::SimplifyConstCondition::new("final")),


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --from <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
This is probably
Hm, the issue is already a couple of weeks old while I was not able to reproduce this one a few days ago.
This happens since :thinking:
modify labels: +requires-nightly
Yes, this is a duplicate of . The changes from fixed the validator, so the bug is now correctly identified when incorrect transform is enabled with .
Does it still ICE if you enable optimizations?
It does, looks like it's still an ICE
This (and ) no longer ICE since , seems to be the case for any opt level cc is that intentional or is still pending?
This is only the case because the changes to MIR make that opt completely broken and just never fire under any circumstances. needs to be revised because of that change, but the plan is still for it to fix these bugs for real

<del> //! The general point of the optimizations provided here is to simplify something like: //! //! ```rust //! # fn foo<T, E>(x: Result<T, E>) -> Result<T, E> { //! match x { //!     Ok(x) => Ok(x), //!     Err(x) => Err(x) //! } //! # } //! ``` //! //! into just `x`. use crate::{simplify, MirPass}; use itertools::Itertools as _; use rustc_index::{bit_set::BitSet, vec::IndexVec}; use rustc_middle::mir::visit::{NonUseContext, PlaceContext, Visitor}; use rustc_middle::mir::*; use rustc_middle::ty::{self, List, Ty, TyCtxt}; use rustc_target::abi::VariantIdx; use std::iter::{once, Enumerate, Peekable}; use std::slice::Iter; /// Simplifies arms of form `Variant(x) => Variant(x)` to just a move. /// /// This is done by transforming basic blocks where the statements match: /// /// ```ignore (MIR) /// _LOCAL_TMP = ((_LOCAL_1 as Variant ).FIELD: TY ); /// _TMP_2 = _LOCAL_TMP; /// ((_LOCAL_0 as Variant).FIELD: TY) = move _TMP_2; /// discriminant(_LOCAL_0) = VAR_IDX; /// ``` /// /// into: /// /// ```ignore (MIR) /// _LOCAL_0 = move _LOCAL_1 /// ``` pub struct SimplifyArmIdentity; #[derive(Debug)] struct ArmIdentityInfo<'tcx> { /// Storage location for the variant's field local_temp_0: Local, /// Storage location holding the variant being read from local_1: Local, /// The variant field being read from vf_s0: VarField<'tcx>, /// Index of the statement which loads the variant being read get_variant_field_stmt: usize, /// Tracks each assignment to a temporary of the variant's field field_tmp_assignments: Vec<(Local, Local)>, /// Storage location holding the variant's field that was read from local_tmp_s1: Local, /// Storage location holding the enum that we are writing to local_0: Local, /// The variant field being written to vf_s1: VarField<'tcx>, /// Storage location that the discriminant is being written to set_discr_local: Local, /// The variant being written set_discr_var_idx: VariantIdx, /// Index of the statement that should be overwritten as a move stmt_to_overwrite: usize, /// SourceInfo for the new move source_info: SourceInfo, /// Indices of matching Storage{Live,Dead} statements encountered. /// (StorageLive index,, StorageDead index, Local) storage_stmts: Vec<(usize, usize, Local)>, /// The statements that should be removed (turned into nops) stmts_to_remove: Vec<usize>, /// Indices of debug variables that need to be adjusted to point to // `{local_0}.{dbg_projection}`. dbg_info_to_adjust: Vec<usize>, /// The projection used to rewrite debug info. dbg_projection: &'tcx List<PlaceElem<'tcx>>, } fn get_arm_identity_info<'a, 'tcx>( stmts: &'a [Statement<'tcx>], locals_count: usize, debug_info: &'a [VarDebugInfo<'tcx>], ) -> Option<ArmIdentityInfo<'tcx>> { // This can't possibly match unless there are at least 3 statements in the block // so fail fast on tiny blocks. if stmts.len() < 3 { return None; } let mut tmp_assigns = Vec::new(); let mut nop_stmts = Vec::new(); let mut storage_stmts = Vec::new(); let mut storage_live_stmts = Vec::new(); let mut storage_dead_stmts = Vec::new(); type StmtIter<'a, 'tcx> = Peekable<Enumerate<Iter<'a, Statement<'tcx>>>>; fn is_storage_stmt(stmt: &Statement<'_>) -> bool { matches!(stmt.kind, StatementKind::StorageLive(_) | StatementKind::StorageDead(_)) } /// Eats consecutive Statements which match `test`, performing the specified `action` for each. /// The iterator `stmt_iter` is not advanced if none were matched. fn try_eat<'a, 'tcx>( stmt_iter: &mut StmtIter<'a, 'tcx>, test: impl Fn(&'a Statement<'tcx>) -> bool, mut action: impl FnMut(usize, &'a Statement<'tcx>), ) { while stmt_iter.peek().map_or(false, |(_, stmt)| test(stmt)) { let (idx, stmt) = stmt_iter.next().unwrap(); action(idx, stmt); } } /// Eats consecutive `StorageLive` and `StorageDead` Statements. /// The iterator `stmt_iter` is not advanced if none were found. fn try_eat_storage_stmts( stmt_iter: &mut StmtIter<'_, '_>, storage_live_stmts: &mut Vec<(usize, Local)>, storage_dead_stmts: &mut Vec<(usize, Local)>, ) { try_eat(stmt_iter, is_storage_stmt, |idx, stmt| { if let StatementKind::StorageLive(l) = stmt.kind { storage_live_stmts.push((idx, l)); } else if let StatementKind::StorageDead(l) = stmt.kind { storage_dead_stmts.push((idx, l)); } }) } fn is_tmp_storage_stmt(stmt: &Statement<'_>) -> bool { use rustc_middle::mir::StatementKind::Assign; if let Assign(box (place, Rvalue::Use(Operand::Copy(p) | Operand::Move(p)))) = &stmt.kind { place.as_local().is_some() && p.as_local().is_some() } else { false } } /// Eats consecutive `Assign` Statements. // The iterator `stmt_iter` is not advanced if none were found. fn try_eat_assign_tmp_stmts( stmt_iter: &mut StmtIter<'_, '_>, tmp_assigns: &mut Vec<(Local, Local)>, nop_stmts: &mut Vec<usize>, ) { try_eat(stmt_iter, is_tmp_storage_stmt, |idx, stmt| { use rustc_middle::mir::StatementKind::Assign; if let Assign(box (place, Rvalue::Use(Operand::Copy(p) | Operand::Move(p)))) = &stmt.kind { tmp_assigns.push((place.as_local().unwrap(), p.as_local().unwrap())); nop_stmts.push(idx); } }) } fn find_storage_live_dead_stmts_for_local( local: Local, stmts: &[Statement<'_>], ) -> Option<(usize, usize)> { trace!("looking for {:?}", local); let mut storage_live_stmt = None; let mut storage_dead_stmt = None; for (idx, stmt) in stmts.iter().enumerate() { if stmt.kind == StatementKind::StorageLive(local) { storage_live_stmt = Some(idx); } else if stmt.kind == StatementKind::StorageDead(local) { storage_dead_stmt = Some(idx); } } Some((storage_live_stmt?, storage_dead_stmt.unwrap_or(usize::MAX))) } // Try to match the expected MIR structure with the basic block we're processing. // We want to see something that looks like: // ``` // (StorageLive(_) | StorageDead(_));* // _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY); // (StorageLive(_) | StorageDead(_));* // (tmp_n+1 = tmp_n);* // (StorageLive(_) | StorageDead(_));* // (tmp_n+1 = tmp_n);* // ((LOCAL_FROM as Variant).FIELD: TY) = move tmp; // discriminant(LOCAL_FROM) = VariantIdx; // (StorageLive(_) | StorageDead(_));* // ``` let mut stmt_iter = stmts.iter().enumerate().peekable(); try_eat_storage_stmts(&mut stmt_iter, &mut storage_live_stmts, &mut storage_dead_stmts); let (get_variant_field_stmt, stmt) = stmt_iter.next()?; let (local_tmp_s0, local_1, vf_s0, dbg_projection) = match_get_variant_field(stmt)?; try_eat_storage_stmts(&mut stmt_iter, &mut storage_live_stmts, &mut storage_dead_stmts); try_eat_assign_tmp_stmts(&mut stmt_iter, &mut tmp_assigns, &mut nop_stmts); try_eat_storage_stmts(&mut stmt_iter, &mut storage_live_stmts, &mut storage_dead_stmts); try_eat_assign_tmp_stmts(&mut stmt_iter, &mut tmp_assigns, &mut nop_stmts); let (idx, stmt) = stmt_iter.next()?; let (local_tmp_s1, local_0, vf_s1) = match_set_variant_field(stmt)?; nop_stmts.push(idx); let (idx, stmt) = stmt_iter.next()?; let (set_discr_local, set_discr_var_idx) = match_set_discr(stmt)?; let discr_stmt_source_info = stmt.source_info; nop_stmts.push(idx); try_eat_storage_stmts(&mut stmt_iter, &mut storage_live_stmts, &mut storage_dead_stmts); for (live_idx, live_local) in storage_live_stmts { if let Some(i) = storage_dead_stmts.iter().rposition(|(_, l)| *l == live_local) { let (dead_idx, _) = storage_dead_stmts.swap_remove(i); storage_stmts.push((live_idx, dead_idx, live_local)); if live_local == local_tmp_s0 { nop_stmts.push(get_variant_field_stmt); } } } // We sort primitive usize here so we can use unstable sort nop_stmts.sort_unstable(); // Use one of the statements we're going to discard between the point // where the storage location for the variant field becomes live and // is killed. let (live_idx, dead_idx) = find_storage_live_dead_stmts_for_local(local_tmp_s0, stmts)?; let stmt_to_overwrite = nop_stmts.iter().find(|stmt_idx| live_idx < **stmt_idx && **stmt_idx < dead_idx); let mut tmp_assigned_vars = BitSet::new_empty(locals_count); for (l, r) in &tmp_assigns { tmp_assigned_vars.insert(*l); tmp_assigned_vars.insert(*r); } let dbg_info_to_adjust: Vec<_> = debug_info .iter() .enumerate() .filter_map(|(i, var_info)| { if let VarDebugInfoContents::Place(p) = var_info.value { if tmp_assigned_vars.contains(p.local) { return Some(i); } } None }) .collect(); Some(ArmIdentityInfo { local_temp_0: local_tmp_s0, local_1, vf_s0, get_variant_field_stmt, field_tmp_assignments: tmp_assigns, local_tmp_s1, local_0, vf_s1, set_discr_local, set_discr_var_idx, stmt_to_overwrite: *stmt_to_overwrite?, source_info: discr_stmt_source_info, storage_stmts, stmts_to_remove: nop_stmts, dbg_info_to_adjust, dbg_projection, }) } fn optimization_applies<'tcx>( opt_info: &ArmIdentityInfo<'tcx>, local_decls: &IndexVec<Local, LocalDecl<'tcx>>, local_uses: &IndexVec<Local, usize>, var_debug_info: &[VarDebugInfo<'tcx>], ) -> bool { trace!("testing if optimization applies..."); // FIXME(wesleywiser): possibly relax this restriction? if opt_info.local_0 == opt_info.local_1 { trace!("NO: moving into ourselves"); return false; } else if opt_info.vf_s0 != opt_info.vf_s1 { trace!("NO: the field-and-variant information do not match"); return false; } else if local_decls[opt_info.local_0].ty != local_decls[opt_info.local_1].ty { // FIXME(Centril,oli-obk): possibly relax to same layout? trace!("NO: source and target locals have different types"); return false; } else if (opt_info.local_0, opt_info.vf_s0.var_idx) != (opt_info.set_discr_local, opt_info.set_discr_var_idx) { trace!("NO: the discriminants do not match"); return false; } // Verify the assignment chain consists of the form b = a; c = b; d = c; etc... if opt_info.field_tmp_assignments.is_empty() { trace!("NO: no assignments found"); return false; } let mut last_assigned_to = opt_info.field_tmp_assignments[0].1; let source_local = last_assigned_to; for (l, r) in &opt_info.field_tmp_assignments { if *r != last_assigned_to { trace!("NO: found unexpected assignment {:?} = {:?}", l, r); return false; } last_assigned_to = *l; } // Check that the first and last used locals are only used twice // since they are of the form: // // ``` // _first = ((_x as Variant).n: ty); // _n = _first; // ... // ((_y as Variant).n: ty) = _n; // discriminant(_y) = z; // ``` for (l, r) in &opt_info.field_tmp_assignments { if local_uses[*l] != 2 { warn!("NO: FAILED assignment chain local {:?} was used more than twice", l); return false; } else if local_uses[*r] != 2 { warn!("NO: FAILED assignment chain local {:?} was used more than twice", r); return false; } } // Check that debug info only points to full Locals and not projections. for dbg_idx in &opt_info.dbg_info_to_adjust { let dbg_info = &var_debug_info[*dbg_idx]; if let VarDebugInfoContents::Place(p) = dbg_info.value { if !p.projection.is_empty() { trace!("NO: debug info for {:?} had a projection {:?}", dbg_info.name, p); return false; } } } if source_local != opt_info.local_temp_0 { trace!( "NO: start of assignment chain does not match enum variant temp: {:?} != {:?}", source_local, opt_info.local_temp_0 ); return false; } else if last_assigned_to != opt_info.local_tmp_s1 { trace!( "NO: end of assignment chain does not match written enum temp: {:?} != {:?}", last_assigned_to, opt_info.local_tmp_s1 ); return false; } trace!("SUCCESS: optimization applies!"); true } impl<'tcx> MirPass<'tcx> for SimplifyArmIdentity { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // FIXME(77359): This optimization can result in unsoundness. if !tcx.sess.opts.unstable_opts.unsound_mir_opts { return; } let source = body.source; trace!("running SimplifyArmIdentity on {:?}", source); let local_uses = LocalUseCounter::get_local_uses(body); for bb in body.basic_blocks.as_mut() { if let Some(opt_info) = get_arm_identity_info(&bb.statements, body.local_decls.len(), &body.var_debug_info) { trace!("got opt_info = {:#?}", opt_info); if !optimization_applies( &opt_info, &body.local_decls, &local_uses, &body.var_debug_info, ) { debug!("optimization skipped for {:?}", source); continue; } // Also remove unused Storage{Live,Dead} statements which correspond // to temps used previously. for (live_idx, dead_idx, local) in &opt_info.storage_stmts { // The temporary that we've read the variant field into is scoped to this block, // so we can remove the assignment. if *local == opt_info.local_temp_0 { bb.statements[opt_info.get_variant_field_stmt].make_nop(); } for (left, right) in &opt_info.field_tmp_assignments { if local == left || local == right { bb.statements[*live_idx].make_nop(); bb.statements[*dead_idx].make_nop(); } } } // Right shape; transform for stmt_idx in opt_info.stmts_to_remove { bb.statements[stmt_idx].make_nop(); } let stmt = &mut bb.statements[opt_info.stmt_to_overwrite]; stmt.source_info = opt_info.source_info; stmt.kind = StatementKind::Assign(Box::new(( opt_info.local_0.into(), Rvalue::Use(Operand::Move(opt_info.local_1.into())), ))); bb.statements.retain(|stmt| stmt.kind != StatementKind::Nop); // Fix the debug info to point to the right local for dbg_index in opt_info.dbg_info_to_adjust { let dbg_info = &mut body.var_debug_info[dbg_index]; assert!( matches!(dbg_info.value, VarDebugInfoContents::Place(_)), "value was not a Place" ); if let VarDebugInfoContents::Place(p) = &mut dbg_info.value { assert!(p.projection.is_empty()); p.local = opt_info.local_0; p.projection = opt_info.dbg_projection; } } trace!("block is now {:?}", bb.statements); } } } } struct LocalUseCounter { local_uses: IndexVec<Local, usize>, } impl LocalUseCounter { fn get_local_uses(body: &Body<'_>) -> IndexVec<Local, usize> { let mut counter = LocalUseCounter { local_uses: IndexVec::from_elem(0, &body.local_decls) }; counter.visit_body(body); counter.local_uses } } impl Visitor<'_> for LocalUseCounter { fn visit_local(&mut self, local: Local, context: PlaceContext, _location: Location) { if context.is_storage_marker() || context == PlaceContext::NonUse(NonUseContext::VarDebugInfo) { return; } self.local_uses[local] += 1; } } /// Match on: /// ```ignore (MIR) /// _LOCAL_INTO = ((_LOCAL_FROM as Variant).FIELD: TY); /// ``` fn match_get_variant_field<'tcx>( stmt: &Statement<'tcx>, ) -> Option<(Local, Local, VarField<'tcx>, &'tcx List<PlaceElem<'tcx>>)> { match &stmt.kind { StatementKind::Assign(box ( place_into, Rvalue::Use(Operand::Copy(pf) | Operand::Move(pf)), )) => { let local_into = place_into.as_local()?; let (local_from, vf) = match_variant_field_place(*pf)?; Some((local_into, local_from, vf, pf.projection)) } _ => None, } } /// Match on: /// ```ignore (MIR) /// ((_LOCAL_FROM as Variant).FIELD: TY) = move _LOCAL_INTO; /// ``` fn match_set_variant_field<'tcx>(stmt: &Statement<'tcx>) -> Option<(Local, Local, VarField<'tcx>)> { match &stmt.kind { StatementKind::Assign(box (place_from, Rvalue::Use(Operand::Move(place_into)))) => { let local_into = place_into.as_local()?; let (local_from, vf) = match_variant_field_place(*place_from)?; Some((local_into, local_from, vf)) } _ => None, } } /// Match on: /// ```ignore (MIR) /// discriminant(_LOCAL_TO_SET) = VAR_IDX; /// ``` fn match_set_discr(stmt: &Statement<'_>) -> Option<(Local, VariantIdx)> { match &stmt.kind { StatementKind::SetDiscriminant { place, variant_index } => { Some((place.as_local()?, *variant_index)) } _ => None, } } #[derive(PartialEq, Debug)] struct VarField<'tcx> { field: Field, field_ty: Ty<'tcx>, var_idx: VariantIdx, } /// Match on `((_LOCAL as Variant).FIELD: TY)`. fn match_variant_field_place(place: Place<'_>) -> Option<(Local, VarField<'_>)> { match place.as_ref() { PlaceRef { local, projection: &[ProjectionElem::Downcast(_, var_idx), ProjectionElem::Field(field, ty)], } => Some((local, VarField { field, field_ty: ty, var_idx })), _ => None, } } /// Simplifies `SwitchInt(_) -> [targets]`, /// where all the `targets` have the same form, /// into `goto -> target_first`. pub struct SimplifyBranchSame; impl<'tcx> MirPass<'tcx> for SimplifyBranchSame { fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { // This optimization is disabled by default for now due to // soundness concerns; see issue #89485 and PR #89489. if !tcx.sess.opts.unstable_opts.unsound_mir_opts { return; } trace!("Running SimplifyBranchSame on {:?}", body.source); let finder = SimplifyBranchSameOptimizationFinder { body, tcx }; let opts = finder.find(); let did_remove_blocks = opts.len() > 0; for opt in opts.iter() { trace!("SUCCESS: Applying optimization {:?}", opt); // Replace `SwitchInt(..) -> [bb_first, ..];` with a `goto -> bb_first;`. body.basic_blocks_mut()[opt.bb_to_opt_terminator].terminator_mut().kind = TerminatorKind::Goto { target: opt.bb_to_goto }; } if did_remove_blocks { // We have dead blocks now, so remove those. simplify::remove_dead_blocks(tcx, body); } } } #[derive(Debug)] struct SimplifyBranchSameOptimization { /// All basic blocks are equal so go to this one bb_to_goto: BasicBlock, /// Basic block where the terminator can be simplified to a goto bb_to_opt_terminator: BasicBlock, } struct SwitchTargetAndValue { target: BasicBlock, // None in case of the `otherwise` case value: Option<u128>, } struct SimplifyBranchSameOptimizationFinder<'a, 'tcx> { body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, } impl<'tcx> SimplifyBranchSameOptimizationFinder<'_, 'tcx> { fn find(&self) -> Vec<SimplifyBranchSameOptimization> { self.body .basic_blocks .iter_enumerated() .filter_map(|(bb_idx, bb)| { let (discr_switched_on, targets_and_values) = match &bb.terminator().kind { TerminatorKind::SwitchInt { targets, discr, .. } => { let targets_and_values: Vec<_> = targets.iter() .map(|(val, target)| SwitchTargetAndValue { target, value: Some(val) }) .chain(once(SwitchTargetAndValue { target: targets.otherwise(), value: None })) .collect(); (discr, targets_and_values) }, _ => return None, }; // find the adt that has its discriminant read // assuming this must be the last statement of the block let adt_matched_on = match &bb.statements.last()?.kind { StatementKind::Assign(box (place, rhs)) if Some(*place) == discr_switched_on.place() => { match rhs { Rvalue::Discriminant(adt_place) if adt_place.ty(self.body, self.tcx).ty.is_enum() => adt_place, _ => { trace!("NO: expected a discriminant read of an enum instead of: {:?}", rhs); return None; } } } other => { trace!("NO: expected an assignment of a discriminant read to a place. Found: {:?}", other); return None }, }; let mut iter_bbs_reachable = targets_and_values .iter() .map(|target_and_value| (target_and_value, &self.body.basic_blocks[target_and_value.target])) .filter(|(_, bb)| { // Reaching `unreachable` is UB so assume it doesn't happen. bb.terminator().kind != TerminatorKind::Unreachable }) .peekable(); let bb_first = iter_bbs_reachable.peek().map_or(&targets_and_values[0], |(idx, _)| *idx); let mut all_successors_equivalent = StatementEquality::TrivialEqual; // All successor basic blocks must be equal or contain statements that are pairwise considered equal. for ((target_and_value_l,bb_l), (target_and_value_r,bb_r)) in iter_bbs_reachable.tuple_windows() { let trivial_checks = bb_l.is_cleanup == bb_r.is_cleanup && bb_l.terminator().kind == bb_r.terminator().kind && bb_l.statements.len() == bb_r.statements.len(); let statement_check = || { bb_l.statements.iter().zip(&bb_r.statements).try_fold(StatementEquality::TrivialEqual, |acc,(l,r)| { let stmt_equality = self.statement_equality(*adt_matched_on, &l, target_and_value_l, &r, target_and_value_r); if matches!(stmt_equality, StatementEquality::NotEqual) { // short circuit None } else { Some(acc.combine(&stmt_equality)) } }) .unwrap_or(StatementEquality::NotEqual) }; if !trivial_checks { all_successors_equivalent = StatementEquality::NotEqual; break; } all_successors_equivalent = all_successors_equivalent.combine(&statement_check()); }; match all_successors_equivalent{ StatementEquality::TrivialEqual => { // statements are trivially equal, so just take first trace!("Statements are trivially equal"); Some(SimplifyBranchSameOptimization { bb_to_goto: bb_first.target, bb_to_opt_terminator: bb_idx, }) } StatementEquality::ConsideredEqual(bb_to_choose) => { trace!("Statements are considered equal"); Some(SimplifyBranchSameOptimization { bb_to_goto: bb_to_choose, bb_to_opt_terminator: bb_idx, }) } StatementEquality::NotEqual => { trace!("NO: not all successors of basic block {:?} were equivalent", bb_idx); None } } }) .collect() } /// Tests if two statements can be considered equal /// /// Statements can be trivially equal if the kinds match. /// But they can also be considered equal in the following case A: /// ```ignore (MIR) /// discriminant(_0) = 0;   // bb1 /// _0 = move _1;           // bb2 /// ``` /// In this case the two statements are equal iff /// - `_0` is an enum where the variant index 0 is fieldless, and /// -  bb1 was targeted by a switch where the discriminant of `_1` was switched on fn statement_equality( &self, adt_matched_on: Place<'tcx>, x: &Statement<'tcx>, x_target_and_value: &SwitchTargetAndValue, y: &Statement<'tcx>, y_target_and_value: &SwitchTargetAndValue, ) -> StatementEquality { let helper = |rhs: &Rvalue<'tcx>, place: &Place<'tcx>, variant_index: VariantIdx, switch_value: u128, side_to_choose| { let place_type = place.ty(self.body, self.tcx).ty; let adt = match *place_type.kind() { ty::Adt(adt, _) if adt.is_enum() => adt, _ => return StatementEquality::NotEqual, }; // We need to make sure that the switch value that targets the bb with // SetDiscriminant is the same as the variant discriminant. let variant_discr = adt.discriminant_for_variant(self.tcx, variant_index).val; if variant_discr != switch_value { trace!( "NO: variant discriminant {} does not equal switch value {}", variant_discr, switch_value ); return StatementEquality::NotEqual; } let variant_is_fieldless = adt.variant(variant_index).fields.is_empty(); if !variant_is_fieldless { trace!("NO: variant {:?} was not fieldless", variant_index); return StatementEquality::NotEqual; } match rhs { Rvalue::Use(operand) if operand.place() == Some(adt_matched_on) => { StatementEquality::ConsideredEqual(side_to_choose) } _ => { trace!( "NO: RHS of assignment was {:?}, but expected it to match the adt being matched on in the switch, which is {:?}", rhs, adt_matched_on ); StatementEquality::NotEqual } } }; match (&x.kind, &y.kind) { // trivial case (x, y) if x == y => StatementEquality::TrivialEqual, // check for case A ( StatementKind::Assign(box (_, rhs)), &StatementKind::SetDiscriminant { ref place, variant_index }, ) if y_target_and_value.value.is_some() => { // choose basic block of x, as that has the assign helper( rhs, place, variant_index, y_target_and_value.value.unwrap(), x_target_and_value.target, ) } ( &StatementKind::SetDiscriminant { ref place, variant_index }, &StatementKind::Assign(box (_, ref rhs)), ) if x_target_and_value.value.is_some() => { // choose basic block of y, as that has the assign helper( rhs, place, variant_index, x_target_and_value.value.unwrap(), y_target_and_value.target, ) } _ => { trace!("NO: statements `{:?}` and `{:?}` not considered equal", x, y); StatementEquality::NotEqual } } } } #[derive(Copy, Clone, Eq, PartialEq)] enum StatementEquality { /// The two statements are trivially equal; same kind TrivialEqual, /// The two statements are considered equal, but may be of different kinds. The BasicBlock field is the basic block to jump to when performing the branch-same optimization. /// For example, `_0 = _1` and `discriminant(_0) = discriminant(0)` are considered equal if 0 is a fieldless variant of an enum. But we don't want to jump to the basic block with the SetDiscriminant, as that is not legal if _1 is not the 0 variant index ConsideredEqual(BasicBlock), /// The two statements are not equal NotEqual, } impl StatementEquality { fn combine(&self, other: &StatementEquality) -> StatementEquality { use StatementEquality::*; match (self, other) { (TrivialEqual, TrivialEqual) => TrivialEqual, (TrivialEqual, ConsideredEqual(b)) | (ConsideredEqual(b), TrivialEqual) => { ConsideredEqual(*b) } (ConsideredEqual(b1), ConsideredEqual(b2)) => { if b1 == b2 { ConsideredEqual(*b1) } else { NotEqual } } (_, NotEqual) | (NotEqual, _) => NotEqual, } } } </del>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --from <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
This is probably
Hm, the issue is already a couple of weeks old while I was not able to reproduce this one a few days ago.
This happens since :thinking:
modify labels: +requires-nightly
Yes, this is a duplicate of . The changes from fixed the validator, so the bug is now correctly identified when incorrect transform is enabled with .
Does it still ICE if you enable optimizations?
It does, looks like it's still an ICE
This (and ) no longer ICE since , seems to be the case for any opt level cc is that intentional or is still pending?
This is only the case because the changes to MIR make that opt completely broken and just never fire under any circumstances. needs to be revised because of that change, but the plan is still for it to fix these bugs for real

<del> - // MIR for `encode` before SimplifyBranchSame + // MIR for `encode` after SimplifyBranchSame fn encode(_1: Type) -> Type { debug v => _1;                       // in scope 0 at $DIR/76803_regression.rs:+0:15: +0:16 let mut _0: Type;                    // return place in scope 0 at $DIR/76803_regression.rs:+0:27: +0:31 let mut _2: isize;                   // in scope 0 at $DIR/76803_regression.rs:+2:9: +2:16 bb0: { _2 = discriminant(_1);           // scope 0 at $DIR/76803_regression.rs:+1:11: +1:12 switchInt(move _2) -> [0: bb2, otherwise: bb1]; // scope 0 at $DIR/76803_regression.rs:+1:5: +1:12 } bb1: { _0 = move _1;                    // scope 0 at $DIR/76803_regression.rs:+3:14: +3:15 goto -> bb3;                     // scope 0 at $DIR/76803_regression.rs:+3:14: +3:15 } bb2: { Deinit(_0);                      // scope 0 at $DIR/76803_regression.rs:+2:20: +2:27 discriminant(_0) = 1;            // scope 0 at $DIR/76803_regression.rs:+2:20: +2:27 goto -> bb3;                     // scope 0 at $DIR/76803_regression.rs:+2:20: +2:27 } bb3: { return;                          // scope 0 at $DIR/76803_regression.rs:+5:2: +5:2 } } </del>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --from <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
This is probably
Hm, the issue is already a couple of weeks old while I was not able to reproduce this one a few days ago.
This happens since :thinking:
modify labels: +requires-nightly
Yes, this is a duplicate of . The changes from fixed the validator, so the bug is now correctly identified when incorrect transform is enabled with .
Does it still ICE if you enable optimizations?
It does, looks like it's still an ICE
This (and ) no longer ICE since , seems to be the case for any opt level cc is that intentional or is still pending?
This is only the case because the changes to MIR make that opt completely broken and just never fire under any circumstances. needs to be revised because of that change, but the plan is still for it to fix these bugs for real

<del> // compile-flags: -Z mir-opt-level=1 // EMIT_MIR 76803_regression.encode.SimplifyBranchSame.diff #[derive(Debug, Eq, PartialEq)] pub enum Type { A, B, } pub fn encode(v: Type) -> Type { match v { Type::A => Type::B, _ => v, } } fn main() { assert_eq!(Type::B, encode(Type::A)); } </del>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --from <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
This is probably
Hm, the issue is already a couple of weeks old while I was not able to reproduce this one a few days ago.
This happens since :thinking:
modify labels: +requires-nightly
Yes, this is a duplicate of . The changes from fixed the validator, so the bug is now correctly identified when incorrect transform is enabled with .
Does it still ICE if you enable optimizations?
It does, looks like it's still an ICE
This (and ) no longer ICE since , seems to be the case for any opt level cc is that intentional or is still pending?
This is only the case because the changes to MIR make that opt completely broken and just never fire under any circumstances. needs to be revised because of that change, but the plan is still for it to fix these bugs for real

<del> - // MIR for `main` before SimplifyArmIdentity + // MIR for `main` after SimplifyArmIdentity fn main() -> () { let mut _0: ();                      // return place in scope 0 at $DIR/issue_73223.rs:+0:11: +0:11 let _1: i32;                         // in scope 0 at $DIR/issue_73223.rs:+1:9: +1:14 let mut _2: std::option::Option<i32>; // in scope 0 at $DIR/issue_73223.rs:+1:23: +1:30 let mut _3: isize;                   // in scope 0 at $DIR/issue_73223.rs:+2:9: +2:16 let _4: i32;                         // in scope 0 at $DIR/issue_73223.rs:+2:14: +2:15 let mut _6: i32;                     // in scope 0 at $DIR/issue_73223.rs:+6:22: +6:27 let mut _7: &i32;                    // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _8: &i32;                    // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _11: bool;                   // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _12: bool;                   // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _13: i32;                    // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _14: i32;                    // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let _16: !;                          // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _17: core::panicking::AssertKind; // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _18: &i32;                   // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let _19: &i32;                       // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _20: &i32;                   // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let _21: &i32;                       // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _22: std::option::Option<std::fmt::Arguments<'_>>; // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _24: &i32;                   // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _25: &i32;                   // in scope 0 at $SRC_DIR/core/src/macros/mod.rs:LL:COL scope 1 { debug split => _1;               // in scope 1 at $DIR/issue_73223.rs:+1:9: +1:14 let _5: std::option::Option<i32>; // in scope 1 at $DIR/issue_73223.rs:+6:9: +6:14 scope 3 { debug _prev => _5;           // in scope 3 at $DIR/issue_73223.rs:+6:9: +6:14 let _9: &i32;                // in scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let _10: &i32;               // in scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let mut _23: &i32;           // in scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL scope 4 { debug left_val => _9;    // in scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL debug right_val => _10;  // in scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL let _15: core::panicking::AssertKind; // in scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL scope 5 { debug kind => _15;   // in scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL } } } } scope 2 { debug v => _4;                   // in scope 2 at $DIR/issue_73223.rs:+2:14: +2:15 } bb0: { StorageLive(_1);                 // scope 0 at $DIR/issue_73223.rs:+1:9: +1:14 StorageLive(_2);                 // scope 0 at $DIR/issue_73223.rs:+1:23: +1:30 Deinit(_2);                      // scope 0 at $DIR/issue_73223.rs:+1:23: +1:30 ((_2 as Some).0: i32) = const 1_i32; // scope 0 at $DIR/issue_73223.rs:+1:23: +1:30 discriminant(_2) = 1;            // scope 0 at $DIR/issue_73223.rs:+1:23: +1:30 _3 = const 1_isize;              // scope 0 at $DIR/issue_73223.rs:+1:23: +1:30 goto -> bb3;                     // scope 0 at $DIR/issue_73223.rs:+1:17: +1:30 } bb1: { StorageDead(_2);                 // scope 0 at $DIR/issue_73223.rs:+4:6: +4:7 StorageDead(_1);                 // scope 0 at $DIR/issue_73223.rs:+8:1: +8:2 return;                          // scope 0 at $DIR/issue_73223.rs:+8:2: +8:2 } bb2: { unreachable;                     // scope 0 at $DIR/issue_73223.rs:+1:23: +1:30 } bb3: { StorageLive(_4);                 // scope 0 at $DIR/issue_73223.rs:+2:14: +2:15 _4 = ((_2 as Some).0: i32);      // scope 0 at $DIR/issue_73223.rs:+2:14: +2:15 _1 = _4;                         // scope 2 at $DIR/issue_73223.rs:+2:20: +2:21 StorageDead(_4);                 // scope 0 at $DIR/issue_73223.rs:+2:20: +2:21 StorageDead(_2);                 // scope 0 at $DIR/issue_73223.rs:+4:6: +4:7 StorageLive(_5);                 // scope 1 at $DIR/issue_73223.rs:+6:9: +6:14 StorageLive(_6);                 // scope 1 at $DIR/issue_73223.rs:+6:22: +6:27 _6 = _1;                         // scope 1 at $DIR/issue_73223.rs:+6:22: +6:27 Deinit(_5);                      // scope 1 at $DIR/issue_73223.rs:+6:17: +6:28 ((_5 as Some).0: i32) = move _6; // scope 1 at $DIR/issue_73223.rs:+6:17: +6:28 discriminant(_5) = 1;            // scope 1 at $DIR/issue_73223.rs:+6:17: +6:28 StorageDead(_6);                 // scope 1 at $DIR/issue_73223.rs:+6:27: +6:28 StorageLive(_24);                // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_25);                // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_7);                 // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _7 = &_1;                        // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_8);                 // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _23 = const _;                   // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL // mir::Constant // + span: $SRC_DIR/core/src/macros/mod.rs:LL:COL // + literal: Const { ty: &i32, val: Unevaluated(main, [], Some(promoted[0])) } _8 = _23;                        // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL Deinit(_24);                     // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL Deinit(_25);                     // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _24 = move _7;                   // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _25 = move _8;                   // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_8);                 // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_7);                 // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_9);                 // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _9 = _24;                        // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_10);                // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _10 = _25;                       // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_11);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_12);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_13);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _13 = (*_9);                     // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_14);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _14 = const 1_i32;               // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _12 = Eq(move _13, const 1_i32); // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_14);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_13);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _11 = Not(move _12);             // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_12);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL switchInt(move _11) -> [0: bb5, otherwise: bb4]; // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL } bb4: { StorageLive(_15);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL Deinit(_15);                     // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL discriminant(_15) = 0;           // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_16);                // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_17);                // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _17 = const core::panicking::AssertKind::Eq; // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL // mir::Constant // + span: $SRC_DIR/core/src/macros/mod.rs:LL:COL // + literal: Const { ty: core::panicking::AssertKind, val: Value(Scalar(0x00)) } StorageLive(_18);                // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_19);                // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _19 = _9;                        // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _18 = _19;                       // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_20);                // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_21);                // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _21 = _10;                       // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _20 = _21;                       // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageLive(_22);                // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL Deinit(_22);                     // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL discriminant(_22) = 0;           // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL _16 = core::panicking::assert_failed::<i32, i32>(const core::panicking::AssertKind::Eq, move _18, move _20, move _22); // scope 5 at $SRC_DIR/core/src/macros/mod.rs:LL:COL // mir::Constant // + span: $SRC_DIR/core/src/macros/mod.rs:LL:COL // + literal: Const { ty: for<'a, 'b, 'c> fn(core::panicking::AssertKind, &'a i32, &'b i32, Option<Arguments<'c>>) -> ! {core::panicking::assert_failed::<i32, i32>}, val: Value(<ZST>) } // mir::Constant // + span: $SRC_DIR/core/src/macros/mod.rs:LL:COL // + literal: Const { ty: core::panicking::AssertKind, val: Value(Scalar(0x00)) } } bb5: { StorageDead(_11);                // scope 4 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_10);                // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_9);                 // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_24);                // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_25);                // scope 3 at $SRC_DIR/core/src/macros/mod.rs:LL:COL StorageDead(_5);                 // scope 1 at $DIR/issue_73223.rs:+8:1: +8:2 StorageDead(_1);                 // scope 0 at $DIR/issue_73223.rs:+8:1: +8:2 return;                          // scope 0 at $DIR/issue_73223.rs:+8:2: +8:2 } } </del>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --from <!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</details
This is probably
Hm, the issue is already a couple of weeks old while I was not able to reproduce this one a few days ago.
This happens since :thinking:
modify labels: +requires-nightly
Yes, this is a duplicate of . The changes from fixed the validator, so the bug is now correctly identified when incorrect transform is enabled with .
Does it still ICE if you enable optimizations?
It does, looks like it's still an ICE
This (and ) no longer ICE since , seems to be the case for any opt level cc is that intentional or is still pending?
This is only the case because the changes to MIR make that opt completely broken and just never fire under any circumstances. needs to be revised because of that change, but the plan is still for it to fix these bugs for real

<del> fn main() { let split = match Some(1) { Some(v) => v, None => return, }; let _prev = Some(split); assert_eq!(split, 1); } // EMIT_MIR issue_73223.main.SimplifyArmIdentity.diff </del>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --<!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</detailsNOTE: The bug is found by our work-in-progress compiler testing tool Kira, and the test program is reduced/minimized by
Assigning and removing .
This ICEs since modify labels: +regression-from-stable-to-stable

} _ => { self.tcx.sess.emit_err(YieldExprOutsideOfGenerator { span: expr.span }); <ins> // Avoid expressions without types during writeback (#78653). self.check_expr(value); </ins> self.tcx.mk_unit() } }


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --<!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</detailsNOTE: The bug is found by our work-in-progress compiler testing tool Kira, and the test program is reduced/minimized by
Assigning and removing .
This ICEs since modify labels: +regression-from-stable-to-stable

<ins> #![feature(generators)] fn main() { yield || for i in 0 { } //~^ ERROR yield expression outside of generator literal //~| ERROR `{integer}` is not an iterator } </ins>


Question:
<!-- Thank you for finding an Internal Compiler Error! If possible, try to provide a minimal verifiable example. You can read "Rust Bug Minimization Patterns" for how to create smaller examples. --<!-- If you're using the stable version of the compiler, you should also check if the bug also exists in the beta or nightly versions. --: <!-- Include a backtrace in the code block by setting in your environment. E.g. . --<details<summary<strongBacktrace</strong</summary<p</p</detailsNOTE: The bug is found by our work-in-progress compiler testing tool Kira, and the test program is reduced/minimized by
Assigning and removing .
This ICEs since modify labels: +regression-from-stable-to-stable

<ins> error[E0627]: yield expression outside of generator literal --> $DIR/yield-outside-generator-issue-78653.rs:4:5 | LL |     yield || for i in 0 { } |     ^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: `{integer}` is not an iterator --> $DIR/yield-outside-generator-issue-78653.rs:4:23 | LL |     yield || for i in 0 { } |                       ^ `{integer}` is not an iterator | = help: the trait `Iterator` is not implemented for `{integer}` = note: if you want to iterate between `start` until a value `end`, use the exclusive range syntax `start..end` or the inclusive range syntax `start..=end` = note: required because of the requirements on the impl of `IntoIterator` for `{integer}` = note: required by `into_iter` error: aborting due to 2 previous errors Some errors have detailed explanations: E0277, E0627. For more information about an error, try `rustc --explain E0277`. </ins>