Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 607878d

Browse files
committedOct 29, 2022
Auto merge of #102698 - michaelwoerister:unord-collections, r=lncr
Introduce UnordMap, UnordSet, and UnordBag (MCP 533) This is the start of implementing [MCP 533](rust-lang/compiler-team#533). I followed `@eddyb's` suggestion of naming the collection types `Unord(Map/Set/Bag)` which is a bit easier to type than `Unordered(Map/Set/Bag)` r? `@eddyb`
2 parents 33b55ac + 9117ea9 commit 607878d

File tree

10 files changed

+400
-12
lines changed

10 files changed

+400
-12
lines changed
 

‎compiler/rustc_data_structures/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#![feature(new_uninit)]
2323
#![feature(once_cell)]
2424
#![feature(rustc_attrs)]
25+
#![feature(negative_impls)]
2526
#![feature(test)]
2627
#![feature(thread_id_value)]
2728
#![feature(vec_into_raw_parts)]
@@ -86,6 +87,7 @@ pub mod steal;
8687
pub mod tagged_ptr;
8788
pub mod temp_dir;
8889
pub mod unhash;
90+
pub mod unord;
8991

9092
pub use ena::undo_log;
9193
pub use ena::unify;
Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
//! This module contains collection types that don't expose their internal
2+
//! ordering. This is a useful property for deterministic computations, such
3+
//! as required by the query system.
4+
5+
use rustc_hash::{FxHashMap, FxHashSet};
6+
use smallvec::SmallVec;
7+
use std::{
8+
borrow::Borrow,
9+
hash::Hash,
10+
iter::{Product, Sum},
11+
};
12+
13+
use crate::{
14+
fingerprint::Fingerprint,
15+
stable_hasher::{HashStable, StableHasher, ToStableHashKey},
16+
};
17+
18+
/// `UnordItems` is the order-less version of `Iterator`. It only contains methods
19+
/// that don't (easily) expose an ordering of the underlying items.
20+
///
21+
/// Most methods take an `Fn` where the `Iterator`-version takes an `FnMut`. This
22+
/// is to reduce the risk of accidentally leaking the internal order via the closure
23+
/// environment. Otherwise one could easily do something like
24+
///
25+
/// ```rust,ignore (pseudo code)
26+
/// let mut ordered = vec![];
27+
/// unordered_items.all(|x| ordered.push(x));
28+
/// ```
29+
///
30+
/// It's still possible to do the same thing with an `Fn` by using interior mutability,
31+
/// but the chance of doing it accidentally is reduced.
32+
pub struct UnordItems<T, I: Iterator<Item = T>>(I);
33+
34+
impl<T, I: Iterator<Item = T>> UnordItems<T, I> {
35+
#[inline]
36+
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> UnordItems<U, impl Iterator<Item = U>> {
37+
UnordItems(self.0.map(f))
38+
}
39+
40+
#[inline]
41+
pub fn all<U, F: Fn(T) -> bool>(mut self, f: F) -> bool {
42+
self.0.all(f)
43+
}
44+
45+
#[inline]
46+
pub fn any<U, F: Fn(T) -> bool>(mut self, f: F) -> bool {
47+
self.0.any(f)
48+
}
49+
50+
#[inline]
51+
pub fn filter<U, F: Fn(&T) -> bool>(self, f: F) -> UnordItems<T, impl Iterator<Item = T>> {
52+
UnordItems(self.0.filter(f))
53+
}
54+
55+
#[inline]
56+
pub fn filter_map<U, F: Fn(T) -> Option<U>>(
57+
self,
58+
f: F,
59+
) -> UnordItems<U, impl Iterator<Item = U>> {
60+
UnordItems(self.0.filter_map(f))
61+
}
62+
63+
#[inline]
64+
pub fn max(self) -> Option<T>
65+
where
66+
T: Ord,
67+
{
68+
self.0.max()
69+
}
70+
71+
#[inline]
72+
pub fn min(self) -> Option<T>
73+
where
74+
T: Ord,
75+
{
76+
self.0.min()
77+
}
78+
79+
#[inline]
80+
pub fn sum<S>(self) -> S
81+
where
82+
S: Sum<T>,
83+
{
84+
self.0.sum()
85+
}
86+
87+
#[inline]
88+
pub fn product<S>(self) -> S
89+
where
90+
S: Product<T>,
91+
{
92+
self.0.product()
93+
}
94+
95+
#[inline]
96+
pub fn count(self) -> usize {
97+
self.0.count()
98+
}
99+
}
100+
101+
impl<'a, T: Clone + 'a, I: Iterator<Item = &'a T>> UnordItems<&'a T, I> {
102+
#[inline]
103+
pub fn cloned(self) -> UnordItems<T, impl Iterator<Item = T>> {
104+
UnordItems(self.0.cloned())
105+
}
106+
}
107+
108+
impl<'a, T: Copy + 'a, I: Iterator<Item = &'a T>> UnordItems<&'a T, I> {
109+
#[inline]
110+
pub fn copied(self) -> UnordItems<T, impl Iterator<Item = T>> {
111+
UnordItems(self.0.copied())
112+
}
113+
}
114+
115+
impl<T: Ord, I: Iterator<Item = T>> UnordItems<T, I> {
116+
pub fn into_sorted<HCX>(self, hcx: &HCX) -> Vec<T>
117+
where
118+
T: ToStableHashKey<HCX>,
119+
{
120+
let mut items: Vec<T> = self.0.collect();
121+
items.sort_by_cached_key(|x| x.to_stable_hash_key(hcx));
122+
items
123+
}
124+
125+
pub fn into_sorted_small_vec<HCX, const LEN: usize>(self, hcx: &HCX) -> SmallVec<[T; LEN]>
126+
where
127+
T: ToStableHashKey<HCX>,
128+
{
129+
let mut items: SmallVec<[T; LEN]> = self.0.collect();
130+
items.sort_by_cached_key(|x| x.to_stable_hash_key(hcx));
131+
items
132+
}
133+
}
134+
135+
/// This is a set collection type that tries very hard to not expose
136+
/// any internal iteration. This is a useful property when trying to
137+
/// uphold the determinism invariants imposed by the query system.
138+
///
139+
/// This collection type is a good choice for set-like collections the
140+
/// keys of which don't have a semantic ordering.
141+
///
142+
/// See [MCP 533](https://github.com/rust-lang/compiler-team/issues/533)
143+
/// for more information.
144+
#[derive(Debug, Eq, PartialEq, Clone, Encodable, Decodable)]
145+
pub struct UnordSet<V: Eq + Hash> {
146+
inner: FxHashSet<V>,
147+
}
148+
149+
impl<V: Eq + Hash> Default for UnordSet<V> {
150+
fn default() -> Self {
151+
Self { inner: FxHashSet::default() }
152+
}
153+
}
154+
155+
impl<V: Eq + Hash> UnordSet<V> {
156+
#[inline]
157+
pub fn new() -> Self {
158+
Self { inner: Default::default() }
159+
}
160+
161+
#[inline]
162+
pub fn len(&self) -> usize {
163+
self.inner.len()
164+
}
165+
166+
#[inline]
167+
pub fn insert(&mut self, v: V) -> bool {
168+
self.inner.insert(v)
169+
}
170+
171+
#[inline]
172+
pub fn contains<Q: ?Sized>(&self, v: &Q) -> bool
173+
where
174+
V: Borrow<Q>,
175+
Q: Hash + Eq,
176+
{
177+
self.inner.contains(v)
178+
}
179+
180+
#[inline]
181+
pub fn items<'a>(&'a self) -> UnordItems<&'a V, impl Iterator<Item = &'a V>> {
182+
UnordItems(self.inner.iter())
183+
}
184+
185+
#[inline]
186+
pub fn into_items(self) -> UnordItems<V, impl Iterator<Item = V>> {
187+
UnordItems(self.inner.into_iter())
188+
}
189+
190+
// We can safely extend this UnordSet from a set of unordered values because that
191+
// won't expose the internal ordering anywhere.
192+
#[inline]
193+
pub fn extend<I: Iterator<Item = V>>(&mut self, items: UnordItems<V, I>) {
194+
self.inner.extend(items.0)
195+
}
196+
}
197+
198+
impl<V: Hash + Eq> Extend<V> for UnordSet<V> {
199+
fn extend<T: IntoIterator<Item = V>>(&mut self, iter: T) {
200+
self.inner.extend(iter)
201+
}
202+
}
203+
204+
impl<HCX, V: Hash + Eq + HashStable<HCX>> HashStable<HCX> for UnordSet<V> {
205+
#[inline]
206+
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
207+
hash_iter_order_independent(self.inner.iter(), hcx, hasher);
208+
}
209+
}
210+
211+
/// This is a map collection type that tries very hard to not expose
212+
/// any internal iteration. This is a useful property when trying to
213+
/// uphold the determinism invariants imposed by the query system.
214+
///
215+
/// This collection type is a good choice for map-like collections the
216+
/// keys of which don't have a semantic ordering.
217+
///
218+
/// See [MCP 533](https://github.com/rust-lang/compiler-team/issues/533)
219+
/// for more information.
220+
#[derive(Debug, Eq, PartialEq, Clone, Encodable, Decodable)]
221+
pub struct UnordMap<K: Eq + Hash, V> {
222+
inner: FxHashMap<K, V>,
223+
}
224+
225+
impl<K: Eq + Hash, V> Default for UnordMap<K, V> {
226+
fn default() -> Self {
227+
Self { inner: FxHashMap::default() }
228+
}
229+
}
230+
231+
impl<K: Hash + Eq, V> Extend<(K, V)> for UnordMap<K, V> {
232+
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
233+
self.inner.extend(iter)
234+
}
235+
}
236+
237+
impl<K: Eq + Hash, V> UnordMap<K, V> {
238+
#[inline]
239+
pub fn len(&self) -> usize {
240+
self.inner.len()
241+
}
242+
243+
#[inline]
244+
pub fn insert(&mut self, k: K, v: V) -> Option<V> {
245+
self.inner.insert(k, v)
246+
}
247+
248+
#[inline]
249+
pub fn contains_key<Q: ?Sized>(&self, k: &Q) -> bool
250+
where
251+
K: Borrow<Q>,
252+
Q: Hash + Eq,
253+
{
254+
self.inner.contains_key(k)
255+
}
256+
257+
#[inline]
258+
pub fn items<'a>(&'a self) -> UnordItems<(&'a K, &'a V), impl Iterator<Item = (&'a K, &'a V)>> {
259+
UnordItems(self.inner.iter())
260+
}
261+
262+
#[inline]
263+
pub fn into_items(self) -> UnordItems<(K, V), impl Iterator<Item = (K, V)>> {
264+
UnordItems(self.inner.into_iter())
265+
}
266+
267+
// We can safely extend this UnordMap from a set of unordered values because that
268+
// won't expose the internal ordering anywhere.
269+
#[inline]
270+
pub fn extend<I: Iterator<Item = (K, V)>>(&mut self, items: UnordItems<(K, V), I>) {
271+
self.inner.extend(items.0)
272+
}
273+
}
274+
275+
impl<HCX, K: Hash + Eq + HashStable<HCX>, V: HashStable<HCX>> HashStable<HCX> for UnordMap<K, V> {
276+
#[inline]
277+
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
278+
hash_iter_order_independent(self.inner.iter(), hcx, hasher);
279+
}
280+
}
281+
282+
/// This is a collection type that tries very hard to not expose
283+
/// any internal iteration. This is a useful property when trying to
284+
/// uphold the determinism invariants imposed by the query system.
285+
///
286+
/// This collection type is a good choice for collections the
287+
/// keys of which don't have a semantic ordering and don't implement
288+
/// `Hash` or `Eq`.
289+
///
290+
/// See [MCP 533](https://github.com/rust-lang/compiler-team/issues/533)
291+
/// for more information.
292+
#[derive(Default, Debug, Eq, PartialEq, Clone, Encodable, Decodable)]
293+
pub struct UnordBag<V> {
294+
inner: Vec<V>,
295+
}
296+
297+
impl<V> UnordBag<V> {
298+
#[inline]
299+
pub fn new() -> Self {
300+
Self { inner: Default::default() }
301+
}
302+
303+
#[inline]
304+
pub fn len(&self) -> usize {
305+
self.inner.len()
306+
}
307+
308+
#[inline]
309+
pub fn push(&mut self, v: V) {
310+
self.inner.push(v);
311+
}
312+
313+
#[inline]
314+
pub fn items<'a>(&'a self) -> UnordItems<&'a V, impl Iterator<Item = &'a V>> {
315+
UnordItems(self.inner.iter())
316+
}
317+
318+
#[inline]
319+
pub fn into_items(self) -> UnordItems<V, impl Iterator<Item = V>> {
320+
UnordItems(self.inner.into_iter())
321+
}
322+
323+
// We can safely extend this UnordSet from a set of unordered values because that
324+
// won't expose the internal ordering anywhere.
325+
#[inline]
326+
pub fn extend<I: Iterator<Item = V>>(&mut self, items: UnordItems<V, I>) {
327+
self.inner.extend(items.0)
328+
}
329+
}
330+
331+
impl<T> Extend<T> for UnordBag<T> {
332+
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
333+
self.inner.extend(iter)
334+
}
335+
}
336+
337+
impl<HCX, V: Hash + Eq + HashStable<HCX>> HashStable<HCX> for UnordBag<V> {
338+
#[inline]
339+
fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
340+
hash_iter_order_independent(self.inner.iter(), hcx, hasher);
341+
}
342+
}
343+
344+
fn hash_iter_order_independent<
345+
HCX,
346+
T: HashStable<HCX>,
347+
I: Iterator<Item = T> + ExactSizeIterator,
348+
>(
349+
mut it: I,
350+
hcx: &mut HCX,
351+
hasher: &mut StableHasher,
352+
) {
353+
let len = it.len();
354+
len.hash_stable(hcx, hasher);
355+
356+
match len {
357+
0 => {
358+
// We're done
359+
}
360+
1 => {
361+
// No need to instantiate a hasher
362+
it.next().unwrap().hash_stable(hcx, hasher);
363+
}
364+
_ => {
365+
let mut accumulator = Fingerprint::ZERO;
366+
for item in it {
367+
let mut item_hasher = StableHasher::new();
368+
item.hash_stable(hcx, &mut item_hasher);
369+
let item_fingerprint: Fingerprint = item_hasher.finish();
370+
accumulator = accumulator.combine_commutative(item_fingerprint);
371+
}
372+
accumulator.hash_stable(hcx, hasher);
373+
}
374+
}
375+
}
376+
377+
// Do not implement IntoIterator for the collections in this module.
378+
// They only exist to hide iteration order in the first place.
379+
impl<T> !IntoIterator for UnordBag<T> {}
380+
impl<V> !IntoIterator for UnordSet<V> {}
381+
impl<K, V> !IntoIterator for UnordMap<K, V> {}
382+
impl<T, I> !IntoIterator for UnordItems<T, I> {}

‎compiler/rustc_hir_analysis/src/check_unused.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::errors::{ExternCrateNotIdiomatic, UnusedExternCrate};
2-
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
2+
use rustc_data_structures::fx::FxHashMap;
3+
use rustc_data_structures::unord::UnordSet;
34
use rustc_hir as hir;
45
use rustc_hir::def::DefKind;
56
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -8,12 +9,12 @@ use rustc_session::lint;
89
use rustc_span::{Span, Symbol};
910

1011
pub fn check_crate(tcx: TyCtxt<'_>) {
11-
let mut used_trait_imports: FxHashSet<LocalDefId> = FxHashSet::default();
12+
let mut used_trait_imports: UnordSet<LocalDefId> = Default::default();
1213

1314
for item_def_id in tcx.hir().body_owners() {
1415
let imports = tcx.used_trait_imports(item_def_id);
1516
debug!("GatherVisitor: item_def_id={:?} with imports {:#?}", item_def_id, imports);
16-
used_trait_imports.extend(imports.iter());
17+
used_trait_imports.extend(imports.items().copied());
1718
}
1819

1920
for &id in tcx.maybe_unused_trait_imports(()) {

‎compiler/rustc_hir_typeck/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub use inherited::{Inherited, InheritedBuilder};
5252
use crate::check::check_fn;
5353
use crate::coercion::DynamicCoerceMany;
5454
use crate::gather_locals::GatherLocalsVisitor;
55-
use rustc_data_structures::fx::FxHashSet;
55+
use rustc_data_structures::unord::UnordSet;
5656
use rustc_errors::{struct_span_err, MultiSpan};
5757
use rustc_hir as hir;
5858
use rustc_hir::def::Res;
@@ -174,7 +174,7 @@ fn has_typeck_results(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
174174
}
175175
}
176176

177-
fn used_trait_imports(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &FxHashSet<LocalDefId> {
177+
fn used_trait_imports(tcx: TyCtxt<'_>, def_id: LocalDefId) -> &UnordSet<LocalDefId> {
178178
&*tcx.typeck(def_id).used_trait_imports
179179
}
180180

‎compiler/rustc_middle/src/arena.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ macro_rules! arena_types {
9696
// since we need to allocate this type on both the `rustc_hir` arena
9797
// (during lowering) and the `librustc_middle` arena (for decoding MIR)
9898
[decode] asm_template: rustc_ast::InlineAsmTemplatePiece,
99-
[decode] used_trait_imports: rustc_data_structures::fx::FxHashSet<rustc_hir::def_id::LocalDefId>,
99+
[decode] used_trait_imports: rustc_data_structures::unord::UnordSet<rustc_hir::def_id::LocalDefId>,
100100
[decode] is_late_bound_map: rustc_data_structures::fx::FxIndexSet<rustc_hir::def_id::LocalDefId>,
101101
[decode] impl_source: rustc_middle::traits::ImplSource<'tcx, ()>,
102102

‎compiler/rustc_middle/src/query/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,7 @@ rustc_queries! {
912912
cache_on_disk_if { true }
913913
}
914914

915-
query used_trait_imports(key: LocalDefId) -> &'tcx FxHashSet<LocalDefId> {
915+
query used_trait_imports(key: LocalDefId) -> &'tcx UnordSet<LocalDefId> {
916916
desc { |tcx| "finding used_trait_imports `{}`", tcx.def_path_str(key.to_def_id()) }
917917
cache_on_disk_if { true }
918918
}

‎compiler/rustc_middle/src/ty/context.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use rustc_data_structures::sharded::{IntoPointer, ShardedHashMap};
3434
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
3535
use rustc_data_structures::steal::Steal;
3636
use rustc_data_structures::sync::{self, Lock, Lrc, ReadGuard, RwLock, WorkerLocal};
37+
use rustc_data_structures::unord::UnordSet;
3738
use rustc_data_structures::vec_map::VecMap;
3839
use rustc_errors::{
3940
DecorateLint, DiagnosticBuilder, DiagnosticMessage, ErrorGuaranteed, MultiSpan,
@@ -531,7 +532,7 @@ pub struct TypeckResults<'tcx> {
531532
/// This is used for warning unused imports. During type
532533
/// checking, this `Lrc` should not be cloned: it must have a ref-count
533534
/// of 1 so that we can insert things into the set mutably.
534-
pub used_trait_imports: Lrc<FxHashSet<LocalDefId>>,
535+
pub used_trait_imports: Lrc<UnordSet<LocalDefId>>,
535536

536537
/// If any errors occurred while type-checking this body,
537538
/// this field will be set to `Some(ErrorGuaranteed)`.

‎compiler/rustc_middle/src/ty/query.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
4040
use rustc_data_structures::steal::Steal;
4141
use rustc_data_structures::svh::Svh;
4242
use rustc_data_structures::sync::Lrc;
43+
use rustc_data_structures::unord::UnordSet;
4344
use rustc_errors::ErrorGuaranteed;
4445
use rustc_hir as hir;
4546
use rustc_hir::def::DefKind;

‎compiler/rustc_query_impl/src/on_disk_cache.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::QueryCtxt;
2-
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2+
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
33
use rustc_data_structures::memmap::Mmap;
44
use rustc_data_structures::sync::{HashMapExt, Lock, Lrc, RwLock};
55
use rustc_data_structures::unhash::UnhashMap;
6+
use rustc_data_structures::unord::UnordSet;
67
use rustc_hir::def_id::{CrateNum, DefId, DefIndex, LocalDefId, StableCrateId, LOCAL_CRATE};
78
use rustc_hir::definitions::DefPathHash;
89
use rustc_index::vec::{Idx, IndexVec};
@@ -792,7 +793,7 @@ impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for DefId {
792793
}
793794
}
794795

795-
impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for &'tcx FxHashSet<LocalDefId> {
796+
impl<'a, 'tcx> Decodable<CacheDecoder<'a, 'tcx>> for &'tcx UnordSet<LocalDefId> {
796797
fn decode(d: &mut CacheDecoder<'a, 'tcx>) -> Self {
797798
RefDecodable::decode(d)
798799
}

‎src/librustdoc/core.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use rustc_ast::NodeId;
22
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
33
use rustc_data_structures::sync::{self, Lrc};
4+
use rustc_data_structures::unord::UnordSet;
45
use rustc_errors::emitter::{Emitter, EmitterWriter};
56
use rustc_errors::json::JsonEmitter;
67
use rustc_feature::UnstableFeatures;
@@ -288,8 +289,7 @@ pub(crate) fn create_config(
288289
providers.typeck_item_bodies = |_, _| {};
289290
// hack so that `used_trait_imports` won't try to call typeck
290291
providers.used_trait_imports = |_, _| {
291-
static EMPTY_SET: LazyLock<FxHashSet<LocalDefId>> =
292-
LazyLock::new(FxHashSet::default);
292+
static EMPTY_SET: LazyLock<UnordSet<LocalDefId>> = LazyLock::new(UnordSet::default);
293293
&EMPTY_SET
294294
};
295295
// In case typeck does end up being called, don't ICE in case there were name resolution errors

0 commit comments

Comments
 (0)
Please sign in to comment.