|
| 1 | +{-# LANGUAGE CPP #-} |
| 2 | +{-# LANGUAGE TypeApplications #-} |
| 3 | + |
| 4 | +-- | = WARNING |
| 5 | +-- |
| 6 | +-- This module is considered __internal__. |
| 7 | +-- |
| 8 | +-- The Package Versioning Policy __does not apply__. |
| 9 | +-- |
| 10 | +-- The contents of this module may change __in any way whatsoever__ |
| 11 | +-- and __without any warning__ between minor versions of this package. |
| 12 | +-- |
| 13 | +-- Authors importing this module are expected to track development |
| 14 | +-- closely. |
| 15 | +-- |
| 16 | +-- = Description |
| 17 | +-- |
| 18 | +-- Debugging utilities for 'HashMap's. |
| 19 | + |
| 20 | +module Data.HashMap.Internal.Debug |
| 21 | + ( valid |
| 22 | + , Validity(..) |
| 23 | + , Error(..) |
| 24 | + , SubHash |
| 25 | + , SubHashPath |
| 26 | + ) where |
| 27 | + |
| 28 | +import Data.Bits (complement, countTrailingZeros, popCount, shiftL, |
| 29 | + unsafeShiftL, (.&.), (.|.)) |
| 30 | +import Data.Hashable (Hashable) |
| 31 | +import Data.HashMap.Internal (Bitmap, Hash, HashMap (..), Leaf (..), |
| 32 | + bitsPerSubkey, fullBitmap, hash, |
| 33 | + isLeafOrCollision, maxChildren, sparseIndex) |
| 34 | +import Data.Semigroup (Sum (..)) |
| 35 | + |
| 36 | +import qualified Data.HashMap.Internal.Array as A |
| 37 | + |
| 38 | + |
| 39 | +#if !MIN_VERSION_base(4,11,0) |
| 40 | +import Data.Semigroup (Semigroup (..)) |
| 41 | +#endif |
| 42 | + |
| 43 | +data Validity k = Invalid (Error k) SubHashPath | Valid |
| 44 | + deriving (Eq, Show) |
| 45 | + |
| 46 | +instance Semigroup (Validity k) where |
| 47 | + Valid <> y = y |
| 48 | + x <> _ = x |
| 49 | + |
| 50 | +instance Monoid (Validity k) where |
| 51 | + mempty = Valid |
| 52 | + mappend = (<>) |
| 53 | + |
| 54 | +-- | An error corresponding to a broken invariant. |
| 55 | +-- |
| 56 | +-- See 'HashMap' for the documentation of the invariants. |
| 57 | +data Error k |
| 58 | + = INV1_internal_Empty |
| 59 | + | INV2_Bitmap_unexpected_1_bits !Bitmap |
| 60 | + | INV3_bad_BitmapIndexed_size !Int |
| 61 | + | INV4_bitmap_array_size_mismatch !Bitmap !Int |
| 62 | + | INV5_BitmapIndexed_invalid_single_subtree |
| 63 | + | INV6_misplaced_hash !Hash |
| 64 | + | INV7_key_hash_mismatch k !Hash |
| 65 | + | INV8_bad_Full_size !Int |
| 66 | + | INV9_Collision_size !Int |
| 67 | + | INV10_Collision_duplicate_key k !Hash |
| 68 | + deriving (Eq, Show) |
| 69 | + |
| 70 | +-- TODO: Name this 'Index'?! |
| 71 | +-- (https://github.com/haskell-unordered-containers/unordered-containers/issues/425) |
| 72 | +-- | A part of a 'Hash' with 'bitsPerSubkey' bits. |
| 73 | +type SubHash = Word |
| 74 | + |
| 75 | +data SubHashPath = SubHashPath |
| 76 | + { partialHash :: !Word |
| 77 | + -- ^ The bits we already know, starting from the lower bits. |
| 78 | + -- The unknown upper bits are @0@. |
| 79 | + , lengthInBits :: !Int |
| 80 | + -- ^ The number of bits known. |
| 81 | + } deriving (Eq, Show) |
| 82 | + |
| 83 | +initialSubHashPath :: SubHashPath |
| 84 | +initialSubHashPath = SubHashPath 0 0 |
| 85 | + |
| 86 | +addSubHash :: SubHashPath -> SubHash -> SubHashPath |
| 87 | +addSubHash (SubHashPath ph l) sh = |
| 88 | + SubHashPath (ph .|. (sh `unsafeShiftL` l)) (l + bitsPerSubkey) |
| 89 | + |
| 90 | +hashMatchesSubHashPath :: SubHashPath -> Hash -> Bool |
| 91 | +hashMatchesSubHashPath (SubHashPath ph l) h = maskToLength h l == ph |
| 92 | + where |
| 93 | + -- Note: This needs to use `shiftL` instead of `unsafeShiftL` because |
| 94 | + -- @l'@ may be greater than 32/64 at the deepest level. |
| 95 | + maskToLength h' l' = h' .&. complement (complement 0 `shiftL` l') |
| 96 | + |
| 97 | +valid :: Hashable k => HashMap k v -> Validity k |
| 98 | +valid Empty = Valid |
| 99 | +valid t = validInternal initialSubHashPath t |
| 100 | + where |
| 101 | + validInternal p Empty = Invalid INV1_internal_Empty p |
| 102 | + validInternal p (Leaf h l) = validHash p h <> validLeaf p h l |
| 103 | + validInternal p (Collision h ary) = validHash p h <> validCollision p h ary |
| 104 | + validInternal p (BitmapIndexed b ary) = validBitmapIndexed p b ary |
| 105 | + validInternal p (Full ary) = validFull p ary |
| 106 | + |
| 107 | + validHash p h | hashMatchesSubHashPath p h = Valid |
| 108 | + | otherwise = Invalid (INV6_misplaced_hash h) p |
| 109 | + |
| 110 | + validLeaf p h (L k _) | hash k == h = Valid |
| 111 | + | otherwise = Invalid (INV7_key_hash_mismatch k h) p |
| 112 | + |
| 113 | + validCollision p h ary = validCollisionSize <> A.foldMap (validLeaf p h) ary <> distinctKeys |
| 114 | + where |
| 115 | + n = A.length ary |
| 116 | + validCollisionSize | n < 2 = Invalid (INV9_Collision_size n) p |
| 117 | + | otherwise = Valid |
| 118 | + distinctKeys = A.foldMap (\(L k _) -> appearsOnce k) ary |
| 119 | + appearsOnce k | A.foldMap (\(L k' _) -> if k' == k then Sum @Int 1 else Sum 0) ary == 1 = Valid |
| 120 | + | otherwise = Invalid (INV10_Collision_duplicate_key k h) p |
| 121 | + |
| 122 | + validBitmapIndexed p b ary = validBitmap <> validArraySize <> validSubTrees p b ary |
| 123 | + where |
| 124 | + validBitmap | b .&. complement fullBitmap == 0 = Valid |
| 125 | + | otherwise = Invalid (INV2_Bitmap_unexpected_1_bits b) p |
| 126 | + n = A.length ary |
| 127 | + validArraySize | n < 1 || n >= maxChildren = Invalid (INV3_bad_BitmapIndexed_size n) p |
| 128 | + | popCount b == n = Valid |
| 129 | + | otherwise = Invalid (INV4_bitmap_array_size_mismatch b n) p |
| 130 | + |
| 131 | + validSubTrees p b ary |
| 132 | + | A.length ary == 1 |
| 133 | + , isLeafOrCollision (A.index ary 0) |
| 134 | + = Invalid INV5_BitmapIndexed_invalid_single_subtree p |
| 135 | + | otherwise = go b |
| 136 | + where |
| 137 | + go 0 = Valid |
| 138 | + go b' = validInternal (addSubHash p (fromIntegral c)) (A.index ary i) <> go b'' |
| 139 | + where |
| 140 | + c = countTrailingZeros b' |
| 141 | + m = 1 `unsafeShiftL` c |
| 142 | + i = sparseIndex b m |
| 143 | + b'' = b' .&. complement m |
| 144 | + |
| 145 | + validFull p ary = validArraySize <> validSubTrees p fullBitmap ary |
| 146 | + where |
| 147 | + n = A.length ary |
| 148 | + validArraySize | n == maxChildren = Valid |
| 149 | + | otherwise = Invalid (INV8_bad_Full_size n) p |
0 commit comments