Path: blob/main/cranelift/codegen/src/scoped_hash_map.rs
1693 views
//! `ScopedHashMap`1//!2//! This module defines a struct `ScopedHashMap<C, K, V>` which3//! defines a `FxHashMap`-like container that has a concept of scopes4//! that can be entered and exited, such that values inserted while5//! inside a scope aren't visible outside the scope.6//!7//! The context type `C` is given to `CtxEq` and `CtxHash` methods on8//! the key values so that keys do not need to be fully9//! self-contained.1011use crate::ctxhash::{CtxEq, CtxHash, CtxHashMap};12use smallvec::{SmallVec, smallvec};1314struct Val<V> {15value: V,16level: u32,17generation: u32,18}1920/// A view into an occupied entry in a `ScopedHashMap`. It is part of the `Entry` enum.21pub struct OccupiedEntry<'a, K: 'a, V: 'a> {22entry: crate::ctxhash::OccupiedEntry<'a, K, Val<V>>,23}2425impl<'a, K, V> OccupiedEntry<'a, K, V> {26/// Gets a reference to the value in the entry.27pub fn get(&self) -> &V {28&self.entry.get().value29}30}3132/// A view into a vacant entry in a `ScopedHashMap`. It is part of the `Entry` enum.33pub struct VacantEntry<'a, K: 'a, V: 'a> {34entry: InsertLoc<'a, K, V>,35depth: u32,36generation: u32,37}3839/// Where to insert from a `VacantEntry`. May be vacant or occupied in40/// the underlying map because of lazy (generation-based) deletion.41enum InsertLoc<'a, K: 'a, V: 'a> {42Vacant(crate::ctxhash::VacantEntry<'a, K, Val<V>>),43Occupied(crate::ctxhash::OccupiedEntry<'a, K, Val<V>>),44}4546impl<'a, K, V> VacantEntry<'a, K, V> {47/// Sets the value of the entry with the `VacantEntry`'s key.48pub fn insert(self, value: V) {49let val = Val {50value,51level: self.depth,52generation: self.generation,53};54match self.entry {55InsertLoc::Vacant(v) => {56v.insert(val);57}58InsertLoc::Occupied(mut o) => {59*o.get_mut() = val;60}61}62}63}6465/// A view into a single entry in a map, which may either be vacant or occupied.66///67/// This enum is constructed from the `entry` method on `ScopedHashMap`.68pub enum Entry<'a, K: 'a, V: 'a> {69Occupied(OccupiedEntry<'a, K, V>),70Vacant(VacantEntry<'a, K, V>),71}7273/// A wrapper around a `FxHashMap` which adds the concept of scopes. Items inserted74/// within a scope are removed when the scope is exited.75///76/// Shadowing, where one scope has entries with the same keys as a containing scope,77/// is not supported in this implementation.78pub struct ScopedHashMap<K, V> {79map: CtxHashMap<K, Val<V>>,80generation_by_depth: SmallVec<[u32; 8]>,81generation: u32,82}8384impl<K, V> ScopedHashMap<K, V>85where86K: Clone,87{88/// Creates an empty `ScopedHashMap`.89#[cfg(test)]90pub fn new() -> Self {91Self::with_capacity(16)92}9394/// Creates an empty `ScopedHashMap` with some pre-allocated capacity.95pub fn with_capacity(cap: usize) -> Self {96Self {97map: CtxHashMap::with_capacity(cap),98generation: 0,99generation_by_depth: smallvec![0],100}101}102103/// Similar to `FxHashMap::entry`, gets the given key's corresponding entry in the map for104/// in-place manipulation.105pub fn entry<'a, C>(&'a mut self, ctx: &C, key: K) -> Entry<'a, K, V>106where107C: CtxEq<K, K> + CtxHash<K>,108{109self.entry_with_depth(ctx, key, self.depth())110}111112/// Get the entry, setting the scope depth at which to insert.113pub fn entry_with_depth<'a, C>(&'a mut self, ctx: &C, key: K, depth: usize) -> Entry<'a, K, V>114where115C: CtxEq<K, K> + CtxHash<K>,116{117debug_assert!(depth <= self.generation_by_depth.len());118let generation = self.generation_by_depth[depth];119let depth = depth as u32;120match self.map.entry(key, ctx) {121crate::ctxhash::Entry::Occupied(entry) => {122let entry_generation = entry.get().generation;123let entry_depth = entry.get().level as usize;124if self.generation_by_depth.get(entry_depth).cloned() == Some(entry_generation) {125Entry::Occupied(OccupiedEntry { entry })126} else {127Entry::Vacant(VacantEntry {128entry: InsertLoc::Occupied(entry),129depth,130generation,131})132}133}134crate::ctxhash::Entry::Vacant(entry) => Entry::Vacant(VacantEntry {135entry: InsertLoc::Vacant(entry),136depth,137generation,138}),139}140}141142/// Get a value from a key, if present.143pub fn get<'a, C>(&'a self, ctx: &C, key: &K) -> Option<&'a V>144where145C: CtxEq<K, K> + CtxHash<K>,146{147self.map148.get(key, ctx)149.filter(|entry| {150let level = entry.level as usize;151self.generation_by_depth.get(level).cloned() == Some(entry.generation)152})153.map(|entry| &entry.value)154}155156/// Insert a key-value pair if absent. No-op if already exists.157pub fn insert_if_absent<C>(&mut self, ctx: &C, key: K, value: V)158where159C: CtxEq<K, K> + CtxHash<K>,160{161self.insert_if_absent_with_depth(ctx, key, value, self.depth());162}163164/// Insert a key-value pair if absent, using the given depth for165/// the insertion. No-op if already exists.166pub fn insert_if_absent_with_depth<C>(&mut self, ctx: &C, key: K, value: V, depth: usize)167where168C: CtxEq<K, K> + CtxHash<K>,169{170match self.entry_with_depth(ctx, key, depth) {171Entry::Vacant(v) => {172v.insert(value);173}174Entry::Occupied(_) => {175// Nothing.176}177}178}179180/// Insert a key-value pair, using the given depth for the181/// insertion. Removes existing entry and overwrites if already182/// existed.183pub fn insert_with_depth<C>(&mut self, ctx: &C, key: K, value: V, depth: usize)184where185C: CtxEq<K, K> + CtxHash<K>,186{187let val = Val {188value,189level: depth as u32,190generation: self.generation_by_depth[depth],191};192self.map.insert(key, val, ctx);193}194195/// Enter a new scope.196pub fn increment_depth(&mut self) {197self.generation_by_depth.push(self.generation);198}199200/// Exit the current scope.201pub fn decrement_depth(&mut self) {202self.generation += 1;203self.generation_by_depth.pop();204}205206/// Return the current scope depth.207pub fn depth(&self) -> usize {208self.generation_by_depth209.len()210.checked_sub(1)211.expect("generation_by_depth cannot be empty")212}213}214215#[cfg(test)]216mod tests {217use super::*;218use crate::ctxhash::NullCtx;219220#[test]221fn basic() {222let mut map: ScopedHashMap<i32, i32> = ScopedHashMap::new();223224match map.entry(&NullCtx, 0) {225Entry::Occupied(_entry) => panic!(),226Entry::Vacant(entry) => entry.insert(1),227}228match map.entry(&NullCtx, 2) {229Entry::Occupied(_entry) => panic!(),230Entry::Vacant(entry) => entry.insert(8),231}232match map.entry(&NullCtx, 2) {233Entry::Occupied(entry) => assert!(*entry.get() == 8),234Entry::Vacant(_entry) => panic!(),235}236map.increment_depth();237match map.entry(&NullCtx, 2) {238Entry::Occupied(entry) => assert!(*entry.get() == 8),239Entry::Vacant(_entry) => panic!(),240}241match map.entry(&NullCtx, 1) {242Entry::Occupied(_entry) => panic!(),243Entry::Vacant(entry) => entry.insert(3),244}245match map.entry(&NullCtx, 1) {246Entry::Occupied(entry) => assert!(*entry.get() == 3),247Entry::Vacant(_entry) => panic!(),248}249match map.entry(&NullCtx, 0) {250Entry::Occupied(entry) => assert!(*entry.get() == 1),251Entry::Vacant(_entry) => panic!(),252}253match map.entry(&NullCtx, 2) {254Entry::Occupied(entry) => assert!(*entry.get() == 8),255Entry::Vacant(_entry) => panic!(),256}257map.decrement_depth();258match map.entry(&NullCtx, 0) {259Entry::Occupied(entry) => assert!(*entry.get() == 1),260Entry::Vacant(_entry) => panic!(),261}262match map.entry(&NullCtx, 2) {263Entry::Occupied(entry) => assert!(*entry.get() == 8),264Entry::Vacant(_entry) => panic!(),265}266map.increment_depth();267match map.entry(&NullCtx, 2) {268Entry::Occupied(entry) => assert!(*entry.get() == 8),269Entry::Vacant(_entry) => panic!(),270}271match map.entry(&NullCtx, 1) {272Entry::Occupied(_entry) => panic!(),273Entry::Vacant(entry) => entry.insert(4),274}275match map.entry(&NullCtx, 1) {276Entry::Occupied(entry) => assert!(*entry.get() == 4),277Entry::Vacant(_entry) => panic!(),278}279match map.entry(&NullCtx, 2) {280Entry::Occupied(entry) => assert!(*entry.get() == 8),281Entry::Vacant(_entry) => panic!(),282}283map.decrement_depth();284map.increment_depth();285map.increment_depth();286map.increment_depth();287match map.entry(&NullCtx, 2) {288Entry::Occupied(entry) => assert!(*entry.get() == 8),289Entry::Vacant(_entry) => panic!(),290}291match map.entry(&NullCtx, 1) {292Entry::Occupied(_entry) => panic!(),293Entry::Vacant(entry) => entry.insert(5),294}295match map.entry(&NullCtx, 1) {296Entry::Occupied(entry) => assert!(*entry.get() == 5),297Entry::Vacant(_entry) => panic!(),298}299match map.entry(&NullCtx, 2) {300Entry::Occupied(entry) => assert!(*entry.get() == 8),301Entry::Vacant(_entry) => panic!(),302}303map.decrement_depth();304map.decrement_depth();305map.decrement_depth();306match map.entry(&NullCtx, 2) {307Entry::Occupied(entry) => assert!(*entry.get() == 8),308Entry::Vacant(_entry) => panic!(),309}310match map.entry(&NullCtx, 1) {311Entry::Occupied(_entry) => panic!(),312Entry::Vacant(entry) => entry.insert(3),313}314}315316#[test]317fn insert_arbitrary_depth() {318let mut map: ScopedHashMap<i32, i32> = ScopedHashMap::new();319map.insert_if_absent(&NullCtx, 1, 2);320assert_eq!(map.get(&NullCtx, &1), Some(&2));321map.increment_depth();322assert_eq!(map.get(&NullCtx, &1), Some(&2));323map.insert_if_absent(&NullCtx, 3, 4);324assert_eq!(map.get(&NullCtx, &3), Some(&4));325map.decrement_depth();326assert_eq!(map.get(&NullCtx, &3), None);327map.increment_depth();328map.insert_if_absent_with_depth(&NullCtx, 3, 4, 0);329assert_eq!(map.get(&NullCtx, &3), Some(&4));330map.decrement_depth();331assert_eq!(map.get(&NullCtx, &3), Some(&4));332}333}334335336