-
Notifications
You must be signed in to change notification settings - Fork 13.6k
Remove fewer Storage calls in CopyProp and GVN #142531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
834fcf3
6e365ed
286b17a
70b5407
9e6b148
4281c53
905f1af
64eec36
a2d7554
91df8d4
3b9d22f
a0e538d
06bcdeb
8b5a70c
0bfa706
70aa4fc
1627f39
aea9a38
3585b4d
045917c
352dcd0
2b05980
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -104,11 +104,12 @@ use rustc_middle::mir::visit::*; | |
use rustc_middle::mir::*; | ||
use rustc_middle::ty::layout::HasTypingEnv; | ||
use rustc_middle::ty::{self, Ty, TyCtxt}; | ||
use rustc_mir_dataflow::{Analysis, ResultsCursor}; | ||
use rustc_span::DUMMY_SP; | ||
use smallvec::SmallVec; | ||
use tracing::{debug, instrument, trace}; | ||
|
||
use crate::ssa::SsaLocals; | ||
use crate::ssa::{MaybeUninitializedLocals, SsaLocals}; | ||
|
||
pub(super) struct GVN; | ||
|
||
|
@@ -139,10 +140,34 @@ impl<'tcx> crate::MirPass<'tcx> for GVN { | |
state.visit_basic_block_data(bb, data); | ||
} | ||
|
||
// For each local that is reused (`y` above), we remove its storage statements do avoid any | ||
// difficulty. Those locals are SSA, so should be easy to optimize by LLVM without storage | ||
// statements. | ||
StorageRemover { tcx, reused_locals: state.reused_locals }.visit_body_preserves_cfg(body); | ||
// When emitting storage statements, we want to retain the reused locals' storage statements, | ||
// as this enables better optimizations. For each local use location, we mark it for storage removal | ||
// only if it might be uninitialized at that point. | ||
let storage_to_remove = if tcx.sess.emit_lifetime_markers() { | ||
let maybe_uninit = MaybeUninitializedLocals | ||
.iterate_to_fixpoint(tcx, body, Some("mir_opt::gvn")) | ||
.into_results_cursor(body); | ||
|
||
let mut storage_checker = StorageChecker { | ||
reused_locals: &state.reused_locals, | ||
storage_to_remove: DenseBitSet::new_empty(body.local_decls.len()), | ||
maybe_uninit, | ||
}; | ||
|
||
for (bb, data) in traversal::reachable(body) { | ||
storage_checker.visit_basic_block_data(bb, data); | ||
} | ||
|
||
storage_checker.storage_to_remove | ||
} else { | ||
// Remove the storage statements of all the reused locals. | ||
state.reused_locals.clone() | ||
}; | ||
|
||
debug!(?storage_to_remove); | ||
|
||
StorageRemover { tcx, reused_locals: state.reused_locals, storage_to_remove } | ||
.visit_body_preserves_cfg(body); | ||
} | ||
|
||
fn is_required(&self) -> bool { | ||
|
@@ -1781,6 +1806,7 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, 'tcx> { | |
struct StorageRemover<'tcx> { | ||
tcx: TyCtxt<'tcx>, | ||
reused_locals: DenseBitSet<Local>, | ||
storage_to_remove: DenseBitSet<Local>, | ||
} | ||
|
||
impl<'tcx> MutVisitor<'tcx> for StorageRemover<'tcx> { | ||
|
@@ -1801,11 +1827,50 @@ impl<'tcx> MutVisitor<'tcx> for StorageRemover<'tcx> { | |
match stmt.kind { | ||
// When removing storage statements, we need to remove both (#107511). | ||
StatementKind::StorageLive(l) | StatementKind::StorageDead(l) | ||
if self.reused_locals.contains(l) => | ||
if self.storage_to_remove.contains(l) => | ||
{ | ||
stmt.make_nop() | ||
} | ||
_ => self.super_statement(stmt, loc), | ||
} | ||
} | ||
} | ||
|
||
struct StorageChecker<'a, 'tcx> { | ||
reused_locals: &'a DenseBitSet<Local>, | ||
storage_to_remove: DenseBitSet<Local>, | ||
maybe_uninit: ResultsCursor<'a, 'tcx, MaybeUninitializedLocals>, | ||
} | ||
|
||
impl<'a, 'tcx> Visitor<'tcx> for StorageChecker<'a, 'tcx> { | ||
fn visit_local(&mut self, local: Local, context: PlaceContext, location: Location) { | ||
match context { | ||
// These mutating uses do not require the local to be initialized. | ||
PlaceContext::MutatingUse(MutatingUseContext::AsmOutput) | ||
| PlaceContext::MutatingUse(MutatingUseContext::Call) | ||
| PlaceContext::MutatingUse(MutatingUseContext::Store) | ||
| PlaceContext::MutatingUse(MutatingUseContext::Yield) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They do not require the local to be initialized, but they do require it to have storage. Mixing the two notions makes me uneasy. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can probably add a test that triggers this. Is the right solution to use two separate analysis checks (so for these we'll check the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, thinking about this some more: we know that the For any replacements (eg target of the What do you think? |
||
| PlaceContext::NonUse(_) => { | ||
return; | ||
} | ||
// Must check validity for other mutating usages and all non-mutating uses. | ||
PlaceContext::MutatingUse(_) | PlaceContext::NonMutatingUse(_) => {} | ||
} | ||
|
||
// We only need to check reused locals which we haven't already removed storage for. | ||
if !self.reused_locals.contains(local) || self.storage_to_remove.contains(local) { | ||
return; | ||
} | ||
|
||
self.maybe_uninit.seek_before_primary_effect(location); | ||
|
||
if self.maybe_uninit.get().contains(local) { | ||
debug!( | ||
?location, | ||
?local, | ||
"local is reused and is maybe uninit at this location, marking it for storage statement removal" | ||
); | ||
self.storage_to_remove.insert(local); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This logic is very close to the one in
copy_prop
. Could you merge them into a singlecompute_storage_to_remove
function inrustc_mir_transform::ssa
? It would take a set of relevant locals (copy-prop :copy_classes[local] != local
, GVN :reused_locals
) and computestorage_to_remove
.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can, but I think such a function will need to also accept a
map: Fn(Local) -> Local
(|local| copy_class[local]
incopy_prop
) since incopy_prop
we check thehead
and not the actualvisit_local
'slocal
param. We could change the storage check to run after the replacement, but then we'll need to add a separateStorageRemover
(similar to GVN).Edit: Also, after implementing #142531 (comment), the storage check logic diverged even more between the two passes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementations should not diverge. They should use the same code. That comment applies to both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be missing something, but I think that because we run the storage check at different stages (in
copy_prop
, we do this before the replacement) they have to be different: removing thePlaceContext::MutatingUse
part in GVN results in removing many more storage statements because we end up checking assignment as well.