-
Notifications
You must be signed in to change notification settings - Fork 13.5k
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
d9829c1
814fe41
d65a0ce
2ee4a9b
f4ecbcf
bde405a
5063f8a
2c521dd
ce2e9b6
80fce82
9218a47
e2349a7
f85c6a4
19679d3
5e870b2
f9c282d
4f356a3
675c79d
20134ed
a543486
3404964
afe3d46
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,12 +104,13 @@ 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 rustc_span::def_id::DefId; | ||
use smallvec::SmallVec; | ||
use tracing::{debug, instrument, trace}; | ||
|
||
use crate::ssa::SsaLocals; | ||
use crate::ssa::{MaybeUninitializedLocals, SsaLocals}; | ||
|
||
pub(super) struct GVN; | ||
|
||
|
@@ -140,10 +141,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 { | ||
|
@@ -1824,6 +1849,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> { | ||
|
@@ -1844,11 +1870,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.