Skip to content

Commit 058a710

Browse files
committed
Auto merge of #79670 - Nadrieril:uninhabited-query, r=estebank
Turn type inhabitedness into a query to fix `exhaustive_patterns` perf We measured in #79394 that enabling the [`exhaustive_patterns` feature](#51085) causes significant perf degradation. It was conjectured that the culprit is type inhabitedness checking, and [I hypothesized](#79394 (comment)) that turning this computation into a query would solve most of the problem. This PR turns `tcx.is_ty_uninhabited_from` into a query, and I measured a 25% perf gain on the benchmark that stress-tests `exhaustiveness_patterns`. This more than compensates for the 30% perf hit I measured [when creating it](rust-lang/rustc-perf#801). We'll have to measure enabling the feature again, but I suspect this fixes the perf regression entirely. I'd like a perf run on this PR obviously. I made small atomic commits to help reviewing. The first one is just me discovering the "revisions" feature of the testing framework. I believe there's a push to move things out of `rustc_middle` because it's huge. I guess `inhabitedness/mod.rs` could be moved out, but it's quite small. `DefIdForest` might be movable somewhere too. I don't know what the policy is for that. Ping `@camelid` since you were interested in following along `@rustbot` modify labels: +A-exhaustiveness-checking
2 parents 7a9b552 + e608d8f commit 058a710

15 files changed

+557
-516
lines changed

compiler/rustc_middle/src/query/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,15 @@ rustc_queries! {
13191319
eval_always
13201320
desc { |tcx| "computing visibility of `{}`", tcx.def_path_str(def_id) }
13211321
}
1322+
1323+
/// Computes the set of modules from which this type is visibly uninhabited.
1324+
/// To check whether a type is uninhabited at all (not just from a given module), you could
1325+
/// check whether the forest is empty.
1326+
query type_uninhabited_from(
1327+
key: ty::ParamEnvAnd<'tcx, Ty<'tcx>>
1328+
) -> ty::inhabitedness::DefIdForest {
1329+
desc { "computing the inhabitedness of `{:?}`", key }
1330+
}
13221331
}
13231332

13241333
Other {

compiler/rustc_middle/src/ty/inhabitedness/def_id_forest.rs

+77-43
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ use crate::ty::{DefId, DefIdTree};
33
use rustc_hir::CRATE_HIR_ID;
44
use smallvec::SmallVec;
55
use std::mem;
6+
use std::sync::Arc;
7+
8+
use DefIdForest::*;
69

710
/// Represents a forest of `DefId`s closed under the ancestor relation. That is,
811
/// if a `DefId` representing a module is contained in the forest then all
@@ -11,45 +14,77 @@ use std::mem;
1114
///
1215
/// This is used to represent a set of modules in which a type is visibly
1316
/// uninhabited.
14-
#[derive(Clone)]
15-
pub struct DefIdForest {
16-
/// The minimal set of `DefId`s required to represent the whole set.
17-
/// If A and B are DefIds in the `DefIdForest`, and A is a descendant
18-
/// of B, then only B will be in `root_ids`.
19-
/// We use a `SmallVec` here because (for its use for caching inhabitedness)
20-
/// it's rare that this will contain even two IDs.
21-
root_ids: SmallVec<[DefId; 1]>,
17+
///
18+
/// We store the minimal set of `DefId`s required to represent the whole set. If A and B are
19+
/// `DefId`s in the `DefIdForest`, and A is a parent of B, then only A will be stored. When this is
20+
/// used with `type_uninhabited_from`, there will very rarely be more than one `DefId` stored.
21+
#[derive(Clone, HashStable)]
22+
pub enum DefIdForest {
23+
Empty,
24+
Single(DefId),
25+
/// This variant is very rare.
26+
/// Invariant: >1 elements
27+
/// We use `Arc` because this is used in the output of a query.
28+
Multiple(Arc<[DefId]>),
29+
}
30+
31+
/// Tests whether a slice of roots contains a given DefId.
32+
#[inline]
33+
fn slice_contains(tcx: TyCtxt<'tcx>, slice: &[DefId], id: DefId) -> bool {
34+
slice.iter().any(|root_id| tcx.is_descendant_of(id, *root_id))
2235
}
2336

2437
impl<'tcx> DefIdForest {
2538
/// Creates an empty forest.
2639
pub fn empty() -> DefIdForest {
27-
DefIdForest { root_ids: SmallVec::new() }
40+
DefIdForest::Empty
2841
}
2942

3043
/// Creates a forest consisting of a single tree representing the entire
3144
/// crate.
3245
#[inline]
3346
pub fn full(tcx: TyCtxt<'tcx>) -> DefIdForest {
34-
let crate_id = tcx.hir().local_def_id(CRATE_HIR_ID);
35-
DefIdForest::from_id(crate_id.to_def_id())
47+
DefIdForest::from_id(tcx.hir().local_def_id(CRATE_HIR_ID).to_def_id())
3648
}
3749

3850
/// Creates a forest containing a `DefId` and all its descendants.
3951
pub fn from_id(id: DefId) -> DefIdForest {
40-
let mut root_ids = SmallVec::new();
41-
root_ids.push(id);
42-
DefIdForest { root_ids }
52+
DefIdForest::Single(id)
53+
}
54+
55+
fn as_slice(&self) -> &[DefId] {
56+
match self {
57+
Empty => &[],
58+
Single(id) => std::slice::from_ref(id),
59+
Multiple(root_ids) => root_ids,
60+
}
61+
}
62+
63+
// Only allocates in the rare `Multiple` case.
64+
fn from_slice(root_ids: &[DefId]) -> DefIdForest {
65+
match root_ids {
66+
[] => Empty,
67+
[id] => Single(*id),
68+
_ => DefIdForest::Multiple(root_ids.into()),
69+
}
4370
}
4471

4572
/// Tests whether the forest is empty.
4673
pub fn is_empty(&self) -> bool {
47-
self.root_ids.is_empty()
74+
match self {
75+
Empty => true,
76+
Single(..) | Multiple(..) => false,
77+
}
78+
}
79+
80+
/// Iterate over the set of roots.
81+
fn iter(&self) -> impl Iterator<Item = DefId> + '_ {
82+
self.as_slice().iter().copied()
4883
}
4984

5085
/// Tests whether the forest contains a given DefId.
5186
pub fn contains(&self, tcx: TyCtxt<'tcx>, id: DefId) -> bool {
52-
self.root_ids.iter().any(|root_id| tcx.is_descendant_of(id, *root_id))
87+
slice_contains(tcx, self.as_slice(), id)
5388
}
5489

5590
/// Calculate the intersection of a collection of forests.
@@ -58,56 +93,55 @@ impl<'tcx> DefIdForest {
5893
I: IntoIterator<Item = DefIdForest>,
5994
{
6095
let mut iter = iter.into_iter();
61-
let mut ret = if let Some(first) = iter.next() {
62-
first
96+
let mut ret: SmallVec<[_; 1]> = if let Some(first) = iter.next() {
97+
SmallVec::from_slice(first.as_slice())
6398
} else {
6499
return DefIdForest::full(tcx);
65100
};
66101

67-
let mut next_ret = SmallVec::new();
68-
let mut old_ret: SmallVec<[DefId; 1]> = SmallVec::new();
102+
let mut next_ret: SmallVec<[_; 1]> = SmallVec::new();
69103
for next_forest in iter {
70104
// No need to continue if the intersection is already empty.
71-
if ret.is_empty() {
72-
break;
105+
if ret.is_empty() || next_forest.is_empty() {
106+
return DefIdForest::empty();
73107
}
74108

75-
for id in ret.root_ids.drain(..) {
76-
if next_forest.contains(tcx, id) {
77-
next_ret.push(id);
78-
} else {
79-
old_ret.push(id);
80-
}
81-
}
82-
ret.root_ids.extend(old_ret.drain(..));
109+
// We keep the elements in `ret` that are also in `next_forest`.
110+
next_ret.extend(ret.iter().copied().filter(|&id| next_forest.contains(tcx, id)));
111+
// We keep the elements in `next_forest` that are also in `ret`.
112+
next_ret.extend(next_forest.iter().filter(|&id| slice_contains(tcx, &ret, id)));
83113

84-
next_ret.extend(next_forest.root_ids.into_iter().filter(|&id| ret.contains(tcx, id)));
85-
86-
mem::swap(&mut next_ret, &mut ret.root_ids);
87-
next_ret.drain(..);
114+
mem::swap(&mut next_ret, &mut ret);
115+
next_ret.clear();
88116
}
89-
ret
117+
DefIdForest::from_slice(&ret)
90118
}
91119

92120
/// Calculate the union of a collection of forests.
93121
pub fn union<I>(tcx: TyCtxt<'tcx>, iter: I) -> DefIdForest
94122
where
95123
I: IntoIterator<Item = DefIdForest>,
96124
{
97-
let mut ret = DefIdForest::empty();
98-
let mut next_ret = SmallVec::new();
125+
let mut ret: SmallVec<[_; 1]> = SmallVec::new();
126+
let mut next_ret: SmallVec<[_; 1]> = SmallVec::new();
99127
for next_forest in iter {
100-
next_ret.extend(ret.root_ids.drain(..).filter(|&id| !next_forest.contains(tcx, id)));
128+
// Union with the empty set is a no-op.
129+
if next_forest.is_empty() {
130+
continue;
131+
}
101132

102-
for id in next_forest.root_ids {
103-
if !next_ret.contains(&id) {
133+
// We add everything in `ret` that is not in `next_forest`.
134+
next_ret.extend(ret.iter().copied().filter(|&id| !next_forest.contains(tcx, id)));
135+
// We add everything in `next_forest` that we haven't added yet.
136+
for id in next_forest.iter() {
137+
if !slice_contains(tcx, &next_ret, id) {
104138
next_ret.push(id);
105139
}
106140
}
107141

108-
mem::swap(&mut next_ret, &mut ret.root_ids);
109-
next_ret.drain(..);
142+
mem::swap(&mut next_ret, &mut ret);
143+
next_ret.clear();
110144
}
111-
ret
145+
DefIdForest::from_slice(&ret)
112146
}
113147
}

compiler/rustc_middle/src/ty/inhabitedness/mod.rs

+36-25
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::ty::TyKind::*;
66
use crate::ty::{AdtDef, FieldDef, Ty, TyS, VariantDef};
77
use crate::ty::{AdtKind, Visibility};
88
use crate::ty::{DefId, SubstsRef};
9-
use rustc_data_structures::stack::ensure_sufficient_stack;
109

1110
mod def_id_forest;
1211

@@ -187,34 +186,46 @@ impl<'tcx> FieldDef {
187186

188187
impl<'tcx> TyS<'tcx> {
189188
/// Calculates the forest of `DefId`s from which this type is visibly uninhabited.
190-
fn uninhabited_from(&self, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> DefIdForest {
191-
match *self.kind() {
192-
Adt(def, substs) => {
193-
ensure_sufficient_stack(|| def.uninhabited_from(tcx, substs, param_env))
194-
}
189+
fn uninhabited_from(
190+
&'tcx self,
191+
tcx: TyCtxt<'tcx>,
192+
param_env: ty::ParamEnv<'tcx>,
193+
) -> DefIdForest {
194+
tcx.type_uninhabited_from(param_env.and(self))
195+
}
196+
}
195197

196-
Never => DefIdForest::full(tcx),
198+
// Query provider for `type_uninhabited_from`.
199+
pub(crate) fn type_uninhabited_from<'tcx>(
200+
tcx: TyCtxt<'tcx>,
201+
key: ty::ParamEnvAnd<'tcx, Ty<'tcx>>,
202+
) -> DefIdForest {
203+
let ty = key.value;
204+
let param_env = key.param_env;
205+
match *ty.kind() {
206+
Adt(def, substs) => def.uninhabited_from(tcx, substs, param_env),
197207

198-
Tuple(ref tys) => DefIdForest::union(
199-
tcx,
200-
tys.iter().map(|ty| ty.expect_ty().uninhabited_from(tcx, param_env)),
201-
),
208+
Never => DefIdForest::full(tcx),
202209

203-
Array(ty, len) => match len.try_eval_usize(tcx, param_env) {
204-
Some(0) | None => DefIdForest::empty(),
205-
// If the array is definitely non-empty, it's uninhabited if
206-
// the type of its elements is uninhabited.
207-
Some(1..) => ty.uninhabited_from(tcx, param_env),
208-
},
210+
Tuple(ref tys) => DefIdForest::union(
211+
tcx,
212+
tys.iter().map(|ty| ty.expect_ty().uninhabited_from(tcx, param_env)),
213+
),
209214

210-
// References to uninitialised memory are valid for any type, including
211-
// uninhabited types, in unsafe code, so we treat all references as
212-
// inhabited.
213-
// The precise semantics of inhabitedness with respect to references is currently
214-
// undecided.
215-
Ref(..) => DefIdForest::empty(),
215+
Array(ty, len) => match len.try_eval_usize(tcx, param_env) {
216+
Some(0) | None => DefIdForest::empty(),
217+
// If the array is definitely non-empty, it's uninhabited if
218+
// the type of its elements is uninhabited.
219+
Some(1..) => ty.uninhabited_from(tcx, param_env),
220+
},
216221

217-
_ => DefIdForest::empty(),
218-
}
222+
// References to uninitialised memory are valid for any type, including
223+
// uninhabited types, in unsafe code, so we treat all references as
224+
// inhabited.
225+
// The precise semantics of inhabitedness with respect to references is currently
226+
// undecided.
227+
Ref(..) => DefIdForest::empty(),
228+
229+
_ => DefIdForest::empty(),
219230
}
220231
}

compiler/rustc_middle/src/ty/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3155,6 +3155,7 @@ pub fn provide(providers: &mut ty::query::Providers) {
31553155
*providers = ty::query::Providers {
31563156
trait_impls_of: trait_def::trait_impls_of_provider,
31573157
all_local_trait_impls: trait_def::all_local_trait_impls,
3158+
type_uninhabited_from: inhabitedness::type_uninhabited_from,
31583159
..*providers
31593160
};
31603161
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
#![crate_type = "rlib"]
22
pub enum EmptyForeignEnum {}
3+
4+
pub struct VisiblyUninhabitedForeignStruct {
5+
pub field: EmptyForeignEnum,
6+
}
7+
8+
pub struct SecretlyUninhabitedForeignStruct {
9+
_priv: EmptyForeignEnum,
10+
}

0 commit comments

Comments
 (0)