Skip to content

Commit cd37d5c

Browse files
committed
Update with PR comments (+docs, +fixes)
Add many of the additional lattice methods with comments, as well as clean up some issues which did not appear in the tests (i.e. SwitchIntEdgeEffect).
1 parent 468742b commit cd37d5c

File tree

5 files changed

+329
-385
lines changed

5 files changed

+329
-385
lines changed

compiler/rustc_mir_dataflow/src/framework/lattice.rs

+18-5
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,16 @@ macro_rules! packed_int_join_semi_lattice {
257257
pub struct $name($base);
258258
impl $name {
259259
pub const TOP: Self = Self(<$base>::MAX);
260-
// If the value is too large it will be top, which is more conservative and thus
261-
// alright. It is only unsafe to make items bot.
262260
#[inline]
263261
pub const fn new(v: $base) -> Self {
264262
Self(v)
265263
}
266264

265+
/// `saturating_new` will convert an arbitrary value (i.e. u32) into a Fact which
266+
/// may have a smaller internal representation (i.e. u8). If the value is too large,
267+
/// it will be converted to `TOP`, which is safe because `TOP` is the most
268+
/// conservative estimate, assuming no information. Note, it is _not_ safe to
269+
/// assume `BOT`, since this assumes information about the value.
267270
#[inline]
268271
pub fn saturating_new(v: impl TryInto<$base>) -> Self {
269272
v.try_into().map(|v| Self(v)).unwrap_or(Self::TOP)
@@ -336,6 +339,14 @@ impl<T: MeetSemiLattice, const N: usize> MeetSemiLattice for FactArray<T, N> {
336339
}
337340
}
338341

342+
/// FactCache is a struct that contains `N` recent facts (of type F) from dataflow analysis,
343+
/// where a fact is information about some component of a program, such as the possible values a
344+
/// variable can take. Variables are indexed by `I: Idx` (i.e. mir::Local), and `L` represents
345+
/// location/recency, so that when merging two fact caches, the more recent information takes
346+
/// precedence.
347+
/// This representation is used because it takes constant memory, and assumes that recent facts
348+
/// will have temporal locality (i.e. will be used closed to where they are generated). Thus, it
349+
/// is more conservative than a complete analysis, but should be fast.
339350
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
340351
pub struct FactCache<I, L, F, const N: usize> {
341352
facts: [F; N],
@@ -350,14 +361,16 @@ impl<I: Idx, L: Ord + Eq + Copy, F, const N: usize> FactCache<I, L, F, N> {
350361
{
351362
Self { facts: [empty_f; N], ord: [(empty_i, empty_l); N], len: 0 }
352363
}
353-
/// inserts a fact into the cache, evicting the oldest one,
364+
/// (nserts a fact into the cache, evicting the oldest one,
354365
/// Or updating it if there is information on one already. If the new fact being
355366
/// inserted is older than the previous fact, it will not be inserted.
356367
pub fn insert(&mut self, i: I, l: L, fact: F) {
357368
let mut idx = None;
358-
for (j, (ci, cl)) in self.ord[..self.len].iter_mut().enumerate() {
369+
for (j, (ci, _cl)) in self.ord[..self.len].iter_mut().enumerate() {
359370
if *ci == i {
360-
assert!(*cl <= l);
371+
// if an older fact is inserted, still update the cache: i.e. cl <= l usually
372+
// but this is broken during apply switch int edge effects, because the engine
373+
// may choose an arbitrary order for basic blocks to apply it to.
361374
idx = Some(j);
362375
break;
363376
}

compiler/rustc_mir_dataflow/src/impls/single_enum_variant.rs

+64-19
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,33 @@ use rustc_middle::mir::*;
99

1010
use crate::{Analysis, AnalysisDomain};
1111

12-
/// A dataflow analysis that tracks whether an enum can hold 0, 1, or more than one variants.
12+
/// A dataflow analysis that tracks whether an enum can hold exactly 1, or more than 1 variants.
13+
///
14+
/// Specifically, if a local is constructed with a value of `Some(1)`,
15+
/// We should be able to optimize it under the assumption that it has the `Some` variant.
16+
/// If a local can be multiple variants, then we assume nothing.
17+
/// This analysis returns whether or not an enum will have a specific discriminant
18+
/// at a given time, associating that local with exactly the 1 discriminant it is.
1319
pub struct SingleEnumVariant<'a, 'tcx> {
1420
tcx: TyCtxt<'tcx>,
1521
body: &'a mir::Body<'tcx>,
1622
}
1723

1824
impl<'tcx> AnalysisDomain<'tcx> for SingleEnumVariant<'_, 'tcx> {
1925
/// For each local, keep track of which enum index it is, if its uninhabited, or unknown.
20-
//type Domain = FactArray<Fact, 128>;
2126
type Domain = FactCache<Local, Location, VariantIdx, 16>;
2227

2328
const NAME: &'static str = "single_enum_variant";
2429

2530
fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
26-
//FactArray { arr: [Fact::TOP; 128] }
2731
FactCache::new(Local::from_u32(0), Location::START, VariantIdx::MAX)
2832
}
2933

3034
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
31-
// assume everything is top initially.
35+
// assume everything is TOP initially (i.e. it can be any variant).
3236
let local_decls = body.local_decls();
3337
for (l, _) in local_decls.iter_enumerated() {
3438
state.remove(l);
35-
//state.insert(l, Fact::TOP);
36-
//state[l] = Fact::TOP;
3739
}
3840
}
3941
}
@@ -57,18 +59,26 @@ impl<'tcx> SingleEnumVariant<'_, 'tcx> {
5759
if !self.is_tracked(lhs) {
5860
return;
5961
}
60-
let lhs_local = lhs.local_or_deref_local()?;
62+
let lhs_local = lhs.as_local()?;
6163

6264
let new_fact = match rhs {
6365
Operand::Copy(rhs) | Operand::Move(rhs) => {
64-
if let Some(rhs_local) = rhs.local_or_deref_local() {
66+
if let Some(rhs_local) = rhs.as_local() {
6567
state.get(rhs_local).map(|f| f.1).copied()
6668
} else {
67-
rhs.ty(self.body, self.tcx).variant_index.map(|var_idx| var_idx)
69+
debug_assert!(
70+
rhs.ty(self.body, self.tcx)
71+
.variant_index
72+
.map(|var_idx| var_idx)
73+
.is_none()
74+
);
75+
None
6876
}
6977
}
70-
// Assigning a constant does not affect discriminant?
71-
Operand::Constant(_c) => return,
78+
// For now, assume that assigning a constant removes known facts.
79+
// More conservative than necessary, but a temp placeholder,
80+
// rather than extracting an enum variant from a constant.
81+
Operand::Constant(_c) => None,
7282
};
7383
if let Some(new_fact) = new_fact {
7484
state.insert(lhs_local, location, new_fact);
@@ -86,6 +96,9 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
8696
statement: &Statement<'tcx>,
8797
loc: Location,
8898
) {
99+
// (place = location which has new information,
100+
// fact = None if we no longer know what variant a value has
101+
// OR fact = Some(var_idx) if we know what variant a value has).
89102
let (place, fact) = match &statement.kind {
90103
StatementKind::Deinit(box place) => (place, None),
91104
StatementKind::SetDiscriminant { box place, variant_index } => {
@@ -112,7 +125,7 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
112125
if !self.is_tracked(place) {
113126
return;
114127
}
115-
let Some(local) = place.local_or_deref_local() else { return };
128+
let Some(local) = place.as_local() else { return };
116129
if let Some(fact) = fact {
117130
state.insert(local, loc, fact);
118131
} else {
@@ -130,7 +143,7 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
130143
self.assign(state, place, value, loc)
131144
}
132145
TerminatorKind::Drop { place, .. } if self.is_tracked(place) => {
133-
let Some(local) = place.local_or_deref_local() else { return };
146+
let Some(local) = place.as_local() else { return };
134147
state.remove(local);
135148
}
136149
_ => {}
@@ -147,18 +160,50 @@ impl<'tcx> Analysis<'tcx> for SingleEnumVariant<'_, 'tcx> {
147160

148161
fn apply_switch_int_edge_effects(
149162
&self,
150-
_block: BasicBlock,
163+
from_block: BasicBlock,
151164
discr: &Operand<'tcx>,
152165
apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
153166
) {
154-
let Some(place) = discr.place() else { return };
155-
if !self.is_tracked(&place) {
167+
let Some(switch_on) = discr.place() else { return };
168+
169+
let mut src_place = None;
170+
let mut adt_def = None;
171+
for stmt in self.body[from_block].statements.iter().rev() {
172+
match stmt.kind {
173+
StatementKind::Assign(box (lhs, Rvalue::Discriminant(disc)))
174+
if lhs == switch_on =>
175+
{
176+
match disc.ty(self.body, self.tcx).ty.kind() {
177+
ty::Adt(adt, _) => {
178+
src_place = Some(disc);
179+
adt_def = Some(adt);
180+
break;
181+
}
182+
183+
// `Rvalue::Discriminant` is also used to get the active yield point for a
184+
// generator, but we do not need edge-specific effects in that case. This may
185+
// change in the future.
186+
ty::Generator(..) => return,
187+
188+
t => bug!("`discriminant` called on unexpected type {:?}", t),
189+
}
190+
}
191+
StatementKind::Coverage(_) => continue,
192+
_ => return,
193+
};
194+
}
195+
196+
let Some(src_place) = src_place else { return };
197+
let Some(adt_def) = adt_def else { return };
198+
if !self.is_tracked(&src_place) {
156199
return;
157200
}
158-
let Some(local) = place.local_or_deref_local() else { return };
201+
let Some(local) = src_place.as_local() else { return };
202+
159203
apply_edge_effects.apply(|state, target| {
160-
// This probably isn't right, need to check that it fits.
161-
let new_fact = target.value.map(|v| VariantIdx::from_u32(v as u32));
204+
let new_fact = target.value.and_then(|discr| {
205+
adt_def.discriminants(self.tcx).find(|(_, d)| d.val == discr).map(|(vi, _)| vi)
206+
});
162207

163208
if let Some(new_fact) = new_fact {
164209
let loc = Location { block: target.target, statement_index: 0 };

compiler/rustc_mir_transform/src/single_enum.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,17 @@ impl<'tcx> MirPass<'tcx> for SingleEnum {
3737
}
3838

3939
for (Location { block, statement_index }, val) in discrs {
40-
let (bbs, local_decls) = &mut body.basic_blocks_and_local_decls_mut();
40+
let local_decls = &body.local_decls;
41+
let bbs = body.basic_blocks.as_mut();
42+
4143
let stmt = &mut bbs[block].statements[statement_index];
4244
let Some((lhs, rval)) = stmt.kind.as_assign_mut() else { unreachable!() };
4345
let Rvalue::Discriminant(rhs) = rval else { unreachable!() };
4446

45-
let Some(disc) = rhs.ty(*local_decls, tcx).ty.discriminant_for_variant(tcx, val)
47+
let Some(disc) = rhs.ty(local_decls, tcx).ty.discriminant_for_variant(tcx, val)
4648
else { continue };
4749

48-
let scalar_ty = lhs.ty(*local_decls, tcx).ty;
50+
let scalar_ty = lhs.ty(local_decls, tcx).ty;
4951
let layout = tcx.layout_of(ParamEnv::empty().and(scalar_ty)).unwrap().layout;
5052
let ct = Operand::const_from_scalar(
5153
tcx,

0 commit comments

Comments
 (0)