From 235ea685dfa2ccdde28b607fb2c732c61518a885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Thu, 17 May 2018 05:19:08 +0200 Subject: [PATCH 1/3] Use a custom hash set for interning --- src/librustc/ty/context.rs | 75 +++---- src/librustc_data_structures/fx.rs | 2 + src/librustc_data_structures/interner.rs | 257 +++++++++++++++++++++++ src/librustc_data_structures/lib.rs | 3 + 4 files changed, 294 insertions(+), 43 deletions(-) create mode 100644 src/librustc_data_structures/interner.rs diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index 35b2ce50da79d..c5f5f719a9d0a 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -52,6 +52,7 @@ use ty::BindingMode; use ty::CanonicalTy; use util::nodemap::{DefIdSet, ItemLocalMap}; use util::nodemap::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::FxInterner; use rustc_data_structures::accumulate_vec::AccumulateVec; use rustc_data_structures::stable_hasher::{HashStable, hash_stable_hashmap, StableHasher, StableHasherResult, @@ -132,7 +133,7 @@ pub struct CtxtInterners<'tcx> { /// Specifically use a speedy hash algorithm for these hash sets, /// they're accessed quite often. - type_: InternedSet<'tcx, TyS<'tcx>>, + type_: Lock>>>, type_list: InternedSet<'tcx, Slice>>, substs: InternedSet<'tcx, Substs<'tcx>>, canonical_var_infos: InternedSet<'tcx, Slice>, @@ -173,51 +174,39 @@ impl<'gcx: 'tcx, 'tcx> CtxtInterners<'tcx> { // determine that all contents are in the global tcx. // See comments on Lift for why we can't use that. if flags.flags.intersects(ty::TypeFlags::KEEP_IN_LOCAL_TCX) { - let mut interner = local.type_.borrow_mut(); - if let Some(&Interned(ty)) = interner.get(&st) { - return ty; - } - - let ty_struct = TyS { - sty: st, - flags: flags.flags, - outer_exclusive_binder: flags.outer_exclusive_binder, - }; + local.type_.borrow_mut().intern(st, |st| { + let ty_struct = TyS { + sty: st, + flags: flags.flags, + outer_exclusive_binder: flags.outer_exclusive_binder, + }; - // Make sure we don't end up with inference - // types/regions in the global interner - if local as *const _ as usize == global as *const _ as usize { - bug!("Attempted to intern `{:?}` which contains \ - inference types/regions in the global type context", - &ty_struct); - } + // Make sure we don't end up with inference + // types/regions in the global interner + if local as *const _ as usize == global as *const _ as usize { + bug!("Attempted to intern `{:?}` which contains \ + inference types/regions in the global type context", + &ty_struct); + } - // Don't be &mut TyS. - let ty: Ty<'tcx> = local.arena.alloc(ty_struct); - interner.insert(Interned(ty)); - ty + Interned(local.arena.alloc(ty_struct)) + }).0 } else { - let mut interner = global.type_.borrow_mut(); - if let Some(&Interned(ty)) = interner.get(&st) { - return ty; - } - - let ty_struct = TyS { - sty: st, - flags: flags.flags, - outer_exclusive_binder: flags.outer_exclusive_binder, - }; + global.type_.borrow_mut().intern(st, |st| { + let ty_struct = TyS { + sty: st, + flags: flags.flags, + outer_exclusive_binder: flags.outer_exclusive_binder, + }; - // This is safe because all the types the ty_struct can point to - // already is in the global arena - let ty_struct: TyS<'gcx> = unsafe { - mem::transmute(ty_struct) - }; + // This is safe because all the types the ty_struct can point to + // already is in the global arena + let ty_struct: TyS<'gcx> = unsafe { + mem::transmute(ty_struct) + }; - // Don't be &mut TyS. - let ty: Ty<'gcx> = global.arena.alloc(ty_struct); - interner.insert(Interned(ty)); - ty + Interned(global.arena.alloc(ty_struct)) + }).0 } } } @@ -1929,7 +1918,7 @@ macro_rules! sty_debug_print { }; $(let mut $variant = total;)* - +/* for &Interned(t) in tcx.interners.type_.borrow().iter() { let variant = match t.sty { ty::TyBool | ty::TyChar | ty::TyInt(..) | ty::TyUint(..) | @@ -1945,7 +1934,7 @@ macro_rules! sty_debug_print { if region { total.region_infer += 1; variant.region_infer += 1 } if ty { total.ty_infer += 1; variant.ty_infer += 1 } if region && ty { total.both_infer += 1; variant.both_infer += 1 } - } + }*/ println!("Ty interner total ty region both"); $(println!(" {:18}: {uses:6} {usespc:4.1}%, \ {ty:4.1}% {region:5.1}% {both:4.1}%", diff --git a/src/librustc_data_structures/fx.rs b/src/librustc_data_structures/fx.rs index 3bf3170d1df1a..6d67d778b5354 100644 --- a/src/librustc_data_structures/fx.rs +++ b/src/librustc_data_structures/fx.rs @@ -11,10 +11,12 @@ use std::collections::{HashMap, HashSet}; use std::default::Default; use std::hash::Hash; +use interner; pub use rustc_hash::FxHashMap; pub use rustc_hash::FxHashSet; pub use rustc_hash::FxHasher; +pub type FxInterner = interner::Interner>; #[allow(non_snake_case)] pub fn FxHashMap() -> FxHashMap { diff --git a/src/librustc_data_structures/interner.rs b/src/librustc_data_structures/interner.rs new file mode 100644 index 0000000000000..7761aaa1e6dc1 --- /dev/null +++ b/src/librustc_data_structures/interner.rs @@ -0,0 +1,257 @@ +use std::marker::PhantomData; +use std::hash::Hash; +use std::hash::Hasher; +use std::hash::BuildHasher; +use std::mem::{self, size_of}; +use std::ptr::{Unique, NonNull}; +use std::alloc::{Global, Alloc}; +use std::collections::hash_map::RandomState; +use std::borrow::Borrow; + +const ENTRIES_PER_GROUP: usize = 5; + +#[repr(align(64), C)] +pub struct Group { + hashes: [u32; ENTRIES_PER_GROUP], + size: u32, + values: [u64; ENTRIES_PER_GROUP], +} + +impl Group { + #[inline(always)] + fn search_for_empty(&self) -> Option { + if self.size != ENTRIES_PER_GROUP as u32 { + Some(self.size as usize) + } else { + None + } + } + + #[inline(always)] + fn search_with bool>(&self, eq: &mut F, hash: u32) -> Option<(usize, bool)> { + for i in 0..ENTRIES_PER_GROUP { + let h = unsafe { *self.hashes.get_unchecked(i) }; + if h == hash && eq(unsafe { mem::transmute(self.values.get_unchecked(i)) }) { + return Some((i, false)) + } + } + self.search_for_empty().map(|i| (i, true)) + } + + #[inline(always)] + fn set(&mut self, pos: usize, hash: u32, value: u64) { + unsafe { + *self.hashes.get_unchecked_mut(pos) = hash; + *self.values.get_unchecked_mut(pos) = value; + } + } + + #[inline(always)] + fn iter(&self, f: &mut F) { + for i in 0..ENTRIES_PER_GROUP { + unsafe { + let h = *self.hashes.get_unchecked(i); + if h != 0 { + f(h, *self.values.get_unchecked(i)) + } + } + } + } +} + +pub struct Table { + group_mask: usize, + size: usize, + capacity: usize, + groups: Unique, +} + +pub struct RawEntry { + group: *mut Group, + pos: usize, + empty: bool +} + +impl Drop for Table { + fn drop(&mut self) { + if self.group_mask == 0 { + return; + } + + unsafe { + Global.dealloc_array( + NonNull::new_unchecked(self.groups.as_ptr()), + self.group_mask + 1 + ).unwrap(); + } + } +} + +impl Table { + unsafe fn new_uninitialized(group_count: usize) -> Table { + assert!(size_of::() == 64); + let groups: NonNull = Global.alloc_array(group_count).unwrap(); + let capacity2 = group_count * ENTRIES_PER_GROUP; + let capacity1 = capacity2 - 1; + //let capacity = (capacity1 * 10 + 10 - 1) / 11; + let capacity = (capacity1 * 10 + 10 - 1) / 13; + //println!("capacity1 {} capacity {}", capacity1, capacity); + assert!(capacity < capacity2); + + for i in 0..group_count { + let group = unsafe { + &mut (*groups.as_ptr().offset(i as isize)) + }; + group.hashes = [0; ENTRIES_PER_GROUP]; + group.size = 0; + } + + Table { + group_mask: group_count.wrapping_sub(1), + size: 0, + capacity, + groups: Unique::new_unchecked(groups.as_ptr()), + } + } + + fn search_for_empty(&self, hash: u64) -> RawEntry { + let group_idx = hash as u32 as usize; + let mask = self.group_mask; + let mut group_idx = group_idx & mask; + + loop { + let group_ptr = unsafe { + self.groups.as_ptr().offset(group_idx as isize) + }; + let group = unsafe { + &(*group_ptr) + }; + match unsafe { group.search_for_empty() } { + Some(pos) => return RawEntry { + group: group_ptr, + pos, + empty: true, + }, + None => (), + } + group_idx = (group_idx + 1) & mask; + } + } + + fn search_with bool>(&self, mut eq: F, hash: u64) -> RawEntry { + let group_idx = hash as u32 as usize; + let mask = self.group_mask; + let mut group_idx = group_idx & mask; + + loop { + let group_ptr = unsafe { + self.groups.as_ptr().offset(group_idx as isize) + }; + let group = unsafe { + &(*group_ptr) + }; + let r = unsafe { group.search_with(&mut eq, hash as u32) } ; + match r { + Some((pos, empty)) => return RawEntry { + group: group_ptr, + pos, + empty, + }, + None => (), + } + group_idx = (group_idx + 1) & mask; + } + } + + fn iter(&self, mut f: F) { + if self.group_mask == 0 { + return; + } + for i in 0..(self.group_mask + 1) { + let group = unsafe { + &(*self.groups.as_ptr().offset(i as isize)) + }; + group.iter(&mut f); + } + } +} + +pub struct Interner { + hash_builder: S, + table: Table, + marker: PhantomData, +} + +impl Default for Interner { + fn default() -> Self { + assert!(size_of::() == 8); + Interner { + hash_builder: S::default(), + table: Table { + group_mask: 0, + size: 0, + capacity: 0, + groups: unsafe { Unique::new_unchecked(NonNull::dangling().as_ptr()) }, + }, + marker: PhantomData, + } + } +} + +pub fn make_hash(hash_state: &S, t: &T) -> u64 + where T: Hash, + S: BuildHasher +{ + let mut state = hash_state.build_hasher(); + t.hash(&mut state); + state.finish() | (1 << 31) +} + +impl Interner { + #[inline(never)] + #[cold] + fn expand(&mut self) { + let mut new_table = unsafe { + Table::new_uninitialized((self.table.group_mask + 1) << 1) + }; + new_table.size = self.table.size; + self.table.iter(|h, v| { + let spot = new_table.search_for_empty(h as u64); + unsafe { + (*spot.group).size += 1; + (*spot.group).set(spot.pos, h, v); + } + }); + self.table = new_table; + } + + #[inline(always)] + fn incr(&mut self) { + if self.table.size + 1 > self.table.capacity { + self.expand() + } + } + + pub fn len(&self) -> usize { + self.table.size + } + + #[inline] + pub fn intern K>(&mut self, value: Q, make: F) -> &K + where K: Borrow, + Q: Hash + Eq + { + self.incr(); + let hash = make_hash(&self.hash_builder, &value); + let spot = self.table.search_with::(|k| value.eq(k.borrow()), hash); + unsafe { + if spot.empty { + self.table.size += 1; + (*spot.group).size += 1; + let key = make(value); + (*spot.group).set(spot.pos, hash as u32, *(&key as *const _ as *const u64)); + } + &*((*spot.group).values.get_unchecked(spot.pos) as *const _ as *const K) + } + } +} diff --git a/src/librustc_data_structures/lib.rs b/src/librustc_data_structures/lib.rs index 7046a2a2a493d..d4e8cac592b15 100644 --- a/src/librustc_data_structures/lib.rs +++ b/src/librustc_data_structures/lib.rs @@ -28,6 +28,8 @@ #![feature(optin_builtin_traits)] #![feature(macro_vis_matcher)] #![feature(allow_internal_unstable)] +#![feature(ptr_internals)] +#![feature(allocator_api)] #![cfg_attr(unix, feature(libc))] #![cfg_attr(test, feature(test))] @@ -62,6 +64,7 @@ pub mod bitvec; pub mod graph; pub mod indexed_set; pub mod indexed_vec; +pub mod interner; pub mod obligation_forest; pub mod sip128; pub mod snapshot_map; From 3ee5405adcbd2017ab99aa48cb146219f97a25f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Tue, 22 May 2018 07:08:21 +0200 Subject: [PATCH 2/3] wip --- src/librustc/ty/context.rs | 111 ++++++++--------------- src/librustc_data_structures/interner.rs | 48 ++++++++-- 2 files changed, 80 insertions(+), 79 deletions(-) diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index c5f5f719a9d0a..4024765ac86bf 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -125,7 +125,7 @@ impl<'tcx> GlobalArenas<'tcx> { } } -type InternedSet<'tcx, T> = Lock>>; +type InternedSet<'tcx, T> = Lock>>; pub struct CtxtInterners<'tcx> { /// The arena that types, regions, etc are allocated from @@ -789,12 +789,9 @@ impl<'tcx> CommonTypes<'tcx> { let mk = |sty| CtxtInterners::intern_ty(interners, interners, sty); let mk_region = |r| { - if let Some(r) = interners.region.borrow().get(&r) { - return r.0; - } - let r = interners.arena.alloc(r); - interners.region.borrow_mut().insert(Interned(r)); - &*r + interners.region.borrow_mut().intern(r, |r| { + Interned(interners.arena.alloc(r)) + }).0 }; CommonTypes { bool: mk(TyBool), @@ -905,14 +902,14 @@ pub struct GlobalCtxt<'tcx> { /// Data layout specification for the current target. pub data_layout: TargetDataLayout, - stability_interner: Lock>, + stability_interner: Lock>, /// Stores the value of constants (and deduplicates the actual memory) allocation_interner: Lock>, pub alloc_map: Lock>, - layout_interner: Lock>, + layout_interner: Lock>, /// A general purpose channel to throw data out the back towards LLVM worker /// threads. @@ -995,16 +992,9 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { self, alloc: Allocation, ) -> &'gcx Allocation { - let allocs = &mut self.allocation_interner.borrow_mut(); - if let Some(alloc) = allocs.get(&alloc) { - return alloc; - } - - let interned = self.global_arenas.const_allocs.alloc(alloc); - if let Some(prev) = allocs.replace(interned) { - bug!("Tried to overwrite interned Allocation: {:#?}", prev) - } - interned + self.allocation_interner.borrow_mut().intern(alloc, |alloc| { + self.global_arenas.const_allocs.alloc(alloc) + }) } /// Allocates a byte or string literal for `mir::interpret` @@ -1016,29 +1006,15 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { } pub fn intern_stability(self, stab: attr::Stability) -> &'gcx attr::Stability { - let mut stability_interner = self.stability_interner.borrow_mut(); - if let Some(st) = stability_interner.get(&stab) { - return st; - } - - let interned = self.global_interners.arena.alloc(stab); - if let Some(prev) = stability_interner.replace(interned) { - bug!("Tried to overwrite interned Stability: {:?}", prev) - } - interned + self.stability_interner.borrow_mut().intern(stab, |stab| { + self.global_interners.arena.alloc(stab) + }) } pub fn intern_layout(self, layout: LayoutDetails) -> &'gcx LayoutDetails { - let mut layout_interner = self.layout_interner.borrow_mut(); - if let Some(layout) = layout_interner.get(&layout) { - return layout; - } - - let interned = self.global_arenas.layout.alloc(layout); - if let Some(prev) = layout_interner.replace(interned) { - bug!("Tried to overwrite interned Layout: {:?}", prev) - } - interned + self.layout_interner.borrow_mut().intern(layout, |layout| { + self.global_arenas.layout.alloc(layout) + }) } pub fn lift>(self, value: &T) -> Option { @@ -1160,8 +1136,8 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { evaluation_cache: traits::EvaluationCache::new(), crate_name: Symbol::intern(crate_name), data_layout, - layout_interner: Lock::new(FxHashSet()), - stability_interner: Lock::new(FxHashSet()), + layout_interner: Default::default(), + stability_interner: Default::default(), allocation_interner: Lock::new(FxHashSet()), alloc_map: Lock::new(interpret::AllocMap::new()), tx_to_llvm_workers: Lock::new(tx), @@ -1898,7 +1874,7 @@ macro_rules! sty_debug_print { ($ctxt: expr, $($variant: ident),*) => {{ // curious inner module to allow variant names to be used as // variable names. - #[allow(non_snake_case)] + #[allow(non_snake_case, warnings)] mod inner { use ty::{self, TyCtxt}; use ty::context::Interned; @@ -2085,37 +2061,28 @@ macro_rules! intern_method { // determine that all contents are in the global tcx. // See comments on Lift for why we can't use that. if ($keep_in_local_tcx)(&v) { - let mut interner = self.interners.$name.borrow_mut(); - if let Some(&Interned(v)) = interner.get(key) { - return v; - } - - // Make sure we don't end up with inference - // types/regions in the global tcx. - if self.is_global() { - bug!("Attempted to intern `{:?}` which contains \ - inference types/regions in the global type context", - v); - } - - let i = $alloc_method(&self.interners.arena, v); - interner.insert(Interned(i)); - i + self.interners.$name.borrow_mut().intern_ref(key, || { + // Make sure we don't end up with inference + // types/regions in the global tcx. + if self.is_global() { + bug!("Attempted to intern `{:?}` which contains \ + inference types/regions in the global type context", + v); + } + + Interned($alloc_method(&self.interners.arena, v)) + }).0 } else { - let mut interner = self.global_interners.$name.borrow_mut(); - if let Some(&Interned(v)) = interner.get(key) { - return v; - } - - // This transmutes $alloc<'tcx> to $alloc<'gcx> - let v = unsafe { - mem::transmute(v) - }; - let i: &$lt_tcx $ty = $alloc_method(&self.global_interners.arena, v); - // Cast to 'gcx - let i = unsafe { mem::transmute(i) }; - interner.insert(Interned(i)); - i + self.global_interners.$name.borrow_mut().intern_ref(key, || { + // This transmutes $alloc<'tcx> to $alloc<'gcx> + let v = unsafe { + mem::transmute(v) + }; + let i: &$lt_tcx $ty = $alloc_method(&self.global_interners.arena, v); + // Cast to 'gcx + let i = unsafe { mem::transmute(i) }; + Interned(i) + }).0 } } } diff --git a/src/librustc_data_structures/interner.rs b/src/librustc_data_structures/interner.rs index 7761aaa1e6dc1..6da0ecf311766 100644 --- a/src/librustc_data_structures/interner.rs +++ b/src/librustc_data_structures/interner.rs @@ -1,3 +1,13 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + use std::marker::PhantomData; use std::hash::Hash; use std::hash::Hasher; @@ -7,6 +17,7 @@ use std::ptr::{Unique, NonNull}; use std::alloc::{Global, Alloc}; use std::collections::hash_map::RandomState; use std::borrow::Borrow; +use std::fmt; const ENTRIES_PER_GROUP: usize = 5; @@ -88,7 +99,7 @@ impl Drop for Table { } impl Table { - unsafe fn new_uninitialized(group_count: usize) -> Table { + fn new(group_count: usize) -> Table { assert!(size_of::() == 64); let groups: NonNull = Global.alloc_array(group_count).unwrap(); let capacity2 = group_count * ENTRIES_PER_GROUP; @@ -110,7 +121,7 @@ impl Table { group_mask: group_count.wrapping_sub(1), size: 0, capacity, - groups: Unique::new_unchecked(groups.as_ptr()), + groups: unsafe { Unique::new_unchecked(groups.as_ptr()) }, } } @@ -126,7 +137,7 @@ impl Table { let group = unsafe { &(*group_ptr) }; - match unsafe { group.search_for_empty() } { + match group.search_for_empty() { Some(pos) => return RawEntry { group: group_ptr, pos, @@ -150,7 +161,7 @@ impl Table { let group = unsafe { &(*group_ptr) }; - let r = unsafe { group.search_with(&mut eq, hash as u32) } ; + let r = group.search_with(&mut eq, hash as u32); match r { Some((pos, empty)) => return RawEntry { group: group_ptr, @@ -182,6 +193,12 @@ pub struct Interner { marker: PhantomData, } +impl fmt::Debug for Interner { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + "Interner".fmt(f) + } +} + impl Default for Interner { fn default() -> Self { assert!(size_of::() == 8); @@ -211,9 +228,7 @@ impl Interner { #[inline(never)] #[cold] fn expand(&mut self) { - let mut new_table = unsafe { - Table::new_uninitialized((self.table.group_mask + 1) << 1) - }; + let mut new_table = Table::new((self.table.group_mask + 1) << 1); new_table.size = self.table.size; self.table.iter(|h, v| { let spot = new_table.search_for_empty(h as u64); @@ -236,6 +251,25 @@ impl Interner { self.table.size } + #[inline] + pub fn intern_ref K>(&mut self, value: &Q, make: F) -> &K + where K: Borrow, + Q: Hash + Eq + { + self.incr(); + let hash = make_hash(&self.hash_builder, value); + let spot = self.table.search_with::(|k| value.eq(k.borrow()), hash); + unsafe { + if spot.empty { + self.table.size += 1; + (*spot.group).size += 1; + let key = make(); + (*spot.group).set(spot.pos, hash as u32, *(&key as *const _ as *const u64)); + } + &*((*spot.group).values.get_unchecked(spot.pos) as *const _ as *const K) + } + } + #[inline] pub fn intern K>(&mut self, value: Q, make: F) -> &K where K: Borrow, From c124143736c669f0a3f0ac766434828f92f39378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Sun, 3 Jun 2018 20:59:58 +0200 Subject: [PATCH 3/3] wip --- src/librustc/ty/context.rs | 4 ++-- src/librustc_data_structures/fx.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/librustc/ty/context.rs b/src/librustc/ty/context.rs index 4024765ac86bf..c594465268acc 100644 --- a/src/librustc/ty/context.rs +++ b/src/librustc/ty/context.rs @@ -905,7 +905,7 @@ pub struct GlobalCtxt<'tcx> { stability_interner: Lock>, /// Stores the value of constants (and deduplicates the actual memory) - allocation_interner: Lock>, + allocation_interner: Lock>, pub alloc_map: Lock>, @@ -1138,7 +1138,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> { data_layout, layout_interner: Default::default(), stability_interner: Default::default(), - allocation_interner: Lock::new(FxHashSet()), + allocation_interner: Default::default(), alloc_map: Lock::new(interpret::AllocMap::new()), tx_to_llvm_workers: Lock::new(tx), output_filenames: Arc::new(output_filenames.clone()), diff --git a/src/librustc_data_structures/fx.rs b/src/librustc_data_structures/fx.rs index 6d67d778b5354..7c0d5ff56e83a 100644 --- a/src/librustc_data_structures/fx.rs +++ b/src/librustc_data_structures/fx.rs @@ -11,6 +11,7 @@ use std::collections::{HashMap, HashSet}; use std::default::Default; use std::hash::Hash; +use std::hash::BuildHasherDefault; use interner; pub use rustc_hash::FxHashMap;