Skip to content

Commit 2705998

Browse files
committed
Extend GVN to perform local value numbering.
1 parent 5d6d4b5 commit 2705998

27 files changed

+396
-256
lines changed

compiler/rustc_mir_transform/src/gvn.rs

Lines changed: 110 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Global value numbering.
1+
//! Value numbering.
22
//!
33
//! MIR may contain repeated and/or redundant computations. The objective of this pass is to detect
44
//! such redundancies and re-use the already-computed result when possible.
@@ -8,15 +8,23 @@
88
//!
99
//! We traverse all assignments `x = rvalue` and operands.
1010
//!
11-
//! For each SSA one, we compute a symbolic representation of values that are assigned to SSA
11+
//! For each assignment, we compute a symbolic representation of values that are assigned to SSA
1212
//! locals. This symbolic representation is defined by the `Value` enum. Each produced instance of
1313
//! `Value` is interned as a `VnIndex`, which allows us to cheaply compute identical values.
1414
//!
15-
//! For each non-SSA
16-
//! one, we compute the `VnIndex` of the rvalue. If this `VnIndex` is associated to a constant, we
17-
//! replace the rvalue/operand by that constant. Otherwise, if there is an SSA local `y`
18-
//! associated to this `VnIndex`, and if its definition location strictly dominates the assignment
19-
//! to `x`, we replace the assignment by `x = y`.
15+
//! If the local is SSA, we append it into the mapping `rev_locals_ssa[value]` for later reuse.
16+
//! That mapping accumulates the knowledge across basic blocks.
17+
//!
18+
//! If the local is non-SSA, we remove it from `rev_locals_non_ssa[old_value]` and append it to
19+
//! `rev_locals_non_ssa[new_value]`. That mapping is cleared at the beginning of each basic block,
20+
//! to ensure we do not carry information across blocks.
21+
//!
22+
//! If the computed `VnIndex` is associated to a constant, we replace the rvalue/operand by that
23+
//! constant. Otherwise, if there is a local `y` associated to this `VnIndex`:
24+
//! - if `y` is SSA and its definition location strictly dominates the assignment to `x`, we
25+
//! replace the assignment by `x = y`;
26+
//! - if `y` is not SSA, then the assignment happens earlier in the same block, so we replace the
27+
//! assignment by `x = y`.
2028
//!
2129
//! By opportunity, this pass simplifies some `Rvalue`s based on the accumulated knowledge.
2230
//!
@@ -97,7 +105,7 @@ use rustc_const_eval::interpret::{
97105
ImmTy, Immediate, InterpCx, MemPlaceMeta, MemoryKind, OpTy, Projectable, Scalar,
98106
intern_const_alloc_for_constprop,
99107
};
100-
use rustc_data_structures::fx::FxHasher;
108+
use rustc_data_structures::fx::{FxHashMap, FxHasher};
101109
use rustc_data_structures::graph::dominators::Dominators;
102110
use rustc_hir::def::DefKind;
103111
use rustc_index::bit_set::DenseBitSet;
@@ -358,8 +366,11 @@ struct VnState<'body, 'a, 'tcx> {
358366
/// Value stored in each local.
359367
locals: IndexVec<Local, Option<VnIndex>>,
360368
/// Locals that are assigned that value.
361-
// This vector does not hold all the values of `VnIndex` that we create.
362-
rev_locals: IndexVec<VnIndex, SmallVec<[Local; 1]>>,
369+
// This vector holds the locals that are SSA.
370+
rev_locals_ssa: IndexVec<VnIndex, SmallVec<[Local; 1]>>,
371+
// This map holds the locals that are not SSA. This map is cleared at the end of each block.
372+
// Therefore, we do not need a location, the local always appears before the current location.
373+
rev_locals_non_ssa: FxHashMap<VnIndex, SmallVec<[Local; 1]>>,
363374
values: ValueSet<'a, 'tcx>,
364375
/// Values evaluated as constants if possible.
365376
evaluated: IndexVec<VnIndex, Option<OpTy<'tcx>>>,
@@ -394,7 +405,8 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
394405
local_decls,
395406
is_coroutine: body.coroutine.is_some(),
396407
locals: IndexVec::from_elem(None, local_decls),
397-
rev_locals: IndexVec::with_capacity(num_values),
408+
rev_locals_ssa: IndexVec::with_capacity(num_values),
409+
rev_locals_non_ssa: FxHashMap::default(),
398410
values: ValueSet::new(num_values),
399411
evaluated: IndexVec::with_capacity(num_values),
400412
derefs: Vec::new(),
@@ -413,11 +425,11 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
413425
fn insert(&mut self, ty: Ty<'tcx>, value: Value<'a, 'tcx>) -> VnIndex {
414426
let (index, new) = self.values.insert(ty, value);
415427
if new {
416-
// Grow `evaluated` and `rev_locals` here to amortize the allocations.
428+
// Grow `evaluated` and `rev_locals_ssa` here to amortize the allocations.
417429
let evaluated = self.eval_to_const(index);
418430
let _index = self.evaluated.push(evaluated);
419431
debug_assert_eq!(index, _index);
420-
let _index = self.rev_locals.push(SmallVec::new());
432+
let _index = self.rev_locals_ssa.push(Default::default());
421433
debug_assert_eq!(index, _index);
422434
}
423435
index
@@ -430,7 +442,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
430442
let index = self.values.insert_unique(ty, Value::Opaque);
431443
let _index = self.evaluated.push(None);
432444
debug_assert_eq!(index, _index);
433-
let _index = self.rev_locals.push(SmallVec::new());
445+
let _index = self.rev_locals_ssa.push(SmallVec::new());
434446
debug_assert_eq!(index, _index);
435447
index
436448
}
@@ -448,17 +460,19 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
448460

449461
let mut projection = place.projection.iter();
450462
let base = if place.is_indirect_first_projection() {
451-
let base = self.locals[place.local]?;
463+
let base = self.local(place.local);
452464
// Skip the initial `Deref`.
453465
projection.next();
454466
AddressBase::Deref(base)
455467
} else {
456468
AddressBase::Local(place.local)
457469
};
470+
let arena = self.arena;
471+
458472
// Do not try evaluating inside `Index`, this has been done by `simplify_place_projection`.
459473
let projection =
460-
projection.map(|proj| proj.try_map(|index| self.locals[index], |ty| ty).ok_or(()));
461-
let projection = self.arena.try_alloc_from_iter(projection).ok()?;
474+
projection.map(|proj| proj.try_map(|index| Some(self.local(index)), |ty| ty).ok_or(()));
475+
let projection = arena.try_alloc_from_iter(projection).ok()?;
462476

463477
let index = self.values.insert_unique(ty, |provenance| Value::Address {
464478
base,
@@ -469,7 +483,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
469483
let evaluated = self.eval_to_const(index);
470484
let _index = self.evaluated.push(evaluated);
471485
debug_assert_eq!(index, _index);
472-
let _index = self.rev_locals.push(SmallVec::new());
486+
let _index = self.rev_locals_ssa.push(SmallVec::new());
473487
debug_assert_eq!(index, _index);
474488

475489
Some(index)
@@ -494,7 +508,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
494508
let evaluated = self.eval_to_const(index);
495509
let _index = self.evaluated.push(evaluated);
496510
debug_assert_eq!(index, _index);
497-
let _index = self.rev_locals.push(SmallVec::new());
511+
let _index = self.rev_locals_ssa.push(SmallVec::new());
498512
debug_assert_eq!(index, _index);
499513
}
500514
index
@@ -510,12 +524,49 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
510524
self.values.ty(index)
511525
}
512526

513-
/// Record that `local` is assigned `value`. `local` must be SSA.
527+
/// Record that `local` is assigned `value`.
514528
#[instrument(level = "trace", skip(self))]
515529
fn assign(&mut self, local: Local, value: VnIndex) {
516-
debug_assert!(self.ssa.is_ssa(local));
517530
self.locals[local] = Some(value);
518-
self.rev_locals[value].push(local);
531+
if self.ssa.is_ssa(local) {
532+
self.rev_locals_ssa[value].push(local);
533+
} else {
534+
self.rev_locals_non_ssa.entry(value).or_default().push(local);
535+
}
536+
}
537+
538+
/// Return the value assigned to a local, or assign an opaque value and return it.
539+
#[instrument(level = "trace", skip(self), ret)]
540+
fn local(&mut self, local: Local) -> VnIndex {
541+
if let Some(value) = self.locals[local] {
542+
return value;
543+
}
544+
let value = self.new_opaque(self.local_decls[local].ty);
545+
self.locals[local] = Some(value);
546+
self.rev_locals_non_ssa.entry(value).or_default().push(local);
547+
value
548+
}
549+
550+
#[instrument(level = "trace", skip(self))]
551+
fn discard_place(&mut self, place: Place<'tcx>) {
552+
let discard_local = |this: &mut Self, local| {
553+
if this.ssa.is_ssa(local) {
554+
return;
555+
}
556+
if let Some(value) = this.locals[local].take() {
557+
this.rev_locals_non_ssa.entry(value).or_default().retain(|l| *l != local);
558+
}
559+
};
560+
if place.is_indirect_first_projection() {
561+
// Non-local mutation maybe invalidate deref.
562+
self.invalidate_derefs();
563+
// Remove stored value from borrowed locals.
564+
for local in self.ssa.borrowed_locals().iter() {
565+
discard_local(self, local);
566+
}
567+
} else {
568+
discard_local(self, place.local);
569+
}
519570
}
520571

521572
fn insert_bool(&mut self, flag: bool) -> VnIndex {
@@ -759,7 +810,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
759810
let (mut place_ty, mut value) = match base {
760811
// The base is a local, so we take the local's value and project from it.
761812
AddressBase::Local(local) => {
762-
let local = self.locals[local]?;
813+
let local = self.local(local);
763814
let place_ty = PlaceTy::from_ty(self.ty(local));
764815
(place_ty, local)
765816
}
@@ -865,7 +916,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
865916
// If the projection is indirect, we treat the local as a value, so can replace it with
866917
// another local.
867918
if place.is_indirect_first_projection()
868-
&& let Some(base) = self.locals[place.local]
919+
&& let base = self.local(place.local)
869920
&& let Some(new_local) = self.try_as_local(base, location)
870921
&& place.local != new_local
871922
{
@@ -877,9 +928,8 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
877928

878929
for i in 0..projection.len() {
879930
let elem = projection[i];
880-
if let ProjectionElem::Index(idx_local) = elem
881-
&& let Some(idx) = self.locals[idx_local]
882-
{
931+
if let ProjectionElem::Index(idx_local) = elem {
932+
let idx = self.local(idx_local);
883933
if let Some(offset) = self.evaluated[idx].as_ref()
884934
&& let Some(offset) = self.ecx.read_target_usize(offset).discard_err()
885935
&& let Some(min_length) = offset.checked_add(1)
@@ -915,7 +965,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
915965
let mut place_ref = place.as_ref();
916966

917967
// Invariant: `value` holds the value up-to the `index`th projection excluded.
918-
let Some(mut value) = self.locals[place.local] else { return Err(place_ref) };
968+
let mut value = self.local(place.local);
919969
// Invariant: `value` has type `place_ty`, with optional downcast variant if needed.
920970
let mut place_ty = PlaceTy::from_ty(self.local_decls[place.local].ty);
921971
for (index, proj) in place.projection.iter().enumerate() {
@@ -926,7 +976,7 @@ impl<'body, 'a, 'tcx> VnState<'body, 'a, 'tcx> {
926976
place_ref = PlaceRef { local, projection: &place.projection[index..] };
927977
}
928978

929-
let Some(proj) = proj.try_map(|value| self.locals[value], |ty| ty) else {
979+
let Some(proj) = proj.try_map(|value| Some(self.local(value)), |ty| ty) else {
930980
return Err(place_ref);
931981
};
932982
let Some(ty_and_value) = self.project(place_ty, value, proj) else {
@@ -1809,11 +1859,17 @@ impl<'tcx> VnState<'_, '_, 'tcx> {
18091859
/// If there is a local which is assigned `index`, and its assignment strictly dominates `loc`,
18101860
/// return it. If you used this local, add it to `reused_locals` to remove storage statements.
18111861
fn try_as_local(&mut self, index: VnIndex, loc: Location) -> Option<Local> {
1812-
let other = self.rev_locals.get(index)?;
1813-
other
1814-
.iter()
1815-
.find(|&&other| self.ssa.assignment_dominates(&self.dominators, other, loc))
1816-
.copied()
1862+
if let Some(ssa) = self.rev_locals_ssa.get(index)
1863+
&& let Some(other) = ssa
1864+
.iter()
1865+
.find(|&&other| self.ssa.assignment_dominates(&self.dominators, other, loc))
1866+
{
1867+
Some(*other)
1868+
} else if let Some(non_ssa) = self.rev_locals_non_ssa.get(&index) {
1869+
non_ssa.first().copied()
1870+
} else {
1871+
None
1872+
}
18171873
}
18181874
}
18191875

@@ -1822,11 +1878,20 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
18221878
self.tcx
18231879
}
18241880

1881+
fn visit_basic_block_data(&mut self, block: BasicBlock, bbdata: &mut BasicBlockData<'tcx>) {
1882+
self.rev_locals_non_ssa.clear();
1883+
for local in self.locals.indices() {
1884+
if !self.ssa.is_ssa(local) {
1885+
self.locals[local] = None;
1886+
}
1887+
}
1888+
self.super_basic_block_data(block, bbdata);
1889+
}
1890+
18251891
fn visit_place(&mut self, place: &mut Place<'tcx>, context: PlaceContext, location: Location) {
18261892
self.simplify_place_projection(place, location);
1827-
if context.is_mutating_use() && place.is_indirect() {
1828-
// Non-local mutation maybe invalidate deref.
1829-
self.invalidate_derefs();
1893+
if context.is_mutating_use() {
1894+
self.discard_place(*place);
18301895
}
18311896
self.super_place(place, context, location);
18321897
}
@@ -1865,13 +1930,9 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
18651930
}
18661931
}
18671932

1868-
if lhs.is_indirect() {
1869-
// Non-local mutation maybe invalidate deref.
1870-
self.invalidate_derefs();
1871-
}
1933+
self.discard_place(*lhs);
18721934

18731935
if let Some(local) = lhs.as_local()
1874-
&& self.ssa.is_ssa(local)
18751936
&& let rvalue_ty = rvalue.ty(self.local_decls, self.tcx)
18761937
// FIXME(#112651) `rvalue` may have a subtype to `local`. We can only mark
18771938
// `local` as reusable if we have an exact type match.
@@ -1883,14 +1944,13 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
18831944
}
18841945

18851946
fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Location) {
1886-
if let Terminator { kind: TerminatorKind::Call { destination, .. }, .. } = terminator {
1887-
if let Some(local) = destination.as_local()
1888-
&& self.ssa.is_ssa(local)
1889-
{
1890-
let ty = self.local_decls[local].ty;
1891-
let opaque = self.new_opaque(ty);
1892-
self.assign(local, opaque);
1893-
}
1947+
self.super_terminator(terminator, location);
1948+
if let Terminator { kind: TerminatorKind::Call { destination, .. }, .. } = terminator
1949+
&& let Some(local) = destination.as_local()
1950+
{
1951+
let ty = self.local_decls[local].ty;
1952+
let opaque = self.new_opaque(ty);
1953+
self.assign(local, opaque);
18941954
}
18951955
// Function calls and ASM may invalidate (nested) derefs. We must handle them carefully.
18961956
// Currently, only preserving derefs for trivial terminators like SwitchInt and Goto.
@@ -1901,7 +1961,6 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
19011961
if !safe_to_preserve_derefs {
19021962
self.invalidate_derefs();
19031963
}
1904-
self.super_terminator(terminator, location);
19051964
}
19061965
}
19071966

0 commit comments

Comments
 (0)