Skip to content

Commit a46ee4d

Browse files
authored
Hashcons NormalizedFilePath values for efficient heap usage (#340)
* intern NormalizedFilePath * force intern * format import * roll our own * make the constructor abstract * add smart constructor
1 parent c73af35 commit a46ee4d

File tree

1 file changed

+28
-4
lines changed
  • lsp-types/src/Language/LSP/Types

1 file changed

+28
-4
lines changed

lsp-types/src/Language/LSP/Types/Uri.hs

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE LambdaCase #-}
12
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
23
{-# LANGUAGE DeriveGeneric #-}
34
{-# LANGUAGE RecordWildCards #-}
@@ -8,7 +9,7 @@ module Language.LSP.Types.Uri
89
, NormalizedUri(..)
910
, toNormalizedUri
1011
, fromNormalizedUri
11-
, NormalizedFilePath(..)
12+
, NormalizedFilePath
1213
, toNormalizedFilePath
1314
, fromNormalizedFilePath
1415
, normalizedFilePathToUri
@@ -23,16 +24,20 @@ import Control.DeepSeq
2324
import qualified Data.Aeson as A
2425
import Data.Binary (Binary, Get, put, get)
2526
import Data.Hashable
27+
import qualified Data.HashMap.Strict as HM
28+
import Data.IORef (atomicModifyIORef', newIORef)
2629
import Data.List (stripPrefix)
2730
import Data.String (IsString, fromString)
2831
import Data.Text (Text)
2932
import qualified Data.Text as T
33+
import Data.Tuple (swap)
3034
import GHC.Generics
3135
import Network.URI hiding (authority)
3236
import qualified System.FilePath as FP
3337
import qualified System.FilePath.Posix as FPP
3438
import qualified System.FilePath.Windows as FPW
3539
import qualified System.Info
40+
import System.IO.Unsafe (unsafePerformIO)
3641

3742
newtype Uri = Uri { getUri :: Text }
3843
deriving (Eq,Ord,Read,Show,Generic,A.FromJSON,A.ToJSON,Hashable,A.ToJSONKey,A.FromJSONKey)
@@ -165,7 +170,11 @@ instance Binary NormalizedFilePath where
165170
get = do
166171
v <- Data.Binary.get :: Get FilePath
167172
let nuri = internalNormalizedFilePathToUri v
168-
return (NormalizedFilePath nuri v)
173+
return (normalizedFilePath nuri v)
174+
175+
-- | A smart constructor that performs UTF-8 encoding and hash consing
176+
normalizedFilePath :: NormalizedUri -> FilePath -> NormalizedFilePath
177+
normalizedFilePath nuri nfp = intern $ NormalizedFilePath nuri nfp
169178

170179
-- | Internal helper that takes a file path that is assumed to
171180
-- already be normalized to a URI. It is up to the caller
@@ -188,7 +197,7 @@ instance IsString NormalizedFilePath where
188197
fromString = toNormalizedFilePath
189198

190199
toNormalizedFilePath :: FilePath -> NormalizedFilePath
191-
toNormalizedFilePath fp = NormalizedFilePath nuri nfp
200+
toNormalizedFilePath fp = normalizedFilePath nuri nfp
192201
where
193202
nfp = FP.normalise fp
194203
nuri = internalNormalizedFilePathToUri nfp
@@ -200,5 +209,20 @@ normalizedFilePathToUri :: NormalizedFilePath -> NormalizedUri
200209
normalizedFilePathToUri (NormalizedFilePath uri _) = uri
201210

202211
uriToNormalizedFilePath :: NormalizedUri -> Maybe NormalizedFilePath
203-
uriToNormalizedFilePath nuri = fmap (NormalizedFilePath nuri) mbFilePath
212+
uriToNormalizedFilePath nuri = fmap (normalizedFilePath nuri) mbFilePath
204213
where mbFilePath = platformAwareUriToFilePath System.Info.os (fromNormalizedUri nuri)
214+
215+
---------------------------------------------------------------------------
216+
-- Unsafe hashcons of NFP
217+
internIO :: (Eq a, Hashable a) => IO (a -> IO a)
218+
internIO = do
219+
tableRef <- newIORef mempty
220+
let f x = atomicModifyIORef' tableRef $ swap . flip HM.alterF x (\case
221+
Just res -> (res, Just res)
222+
Nothing -> (x, Just x)
223+
)
224+
return f
225+
226+
{-# NOINLINE intern #-}
227+
intern :: NormalizedFilePath -> NormalizedFilePath
228+
intern = let f = unsafePerformIO internIO in \x -> unsafePerformIO (f x)

0 commit comments

Comments
 (0)