diff options
Diffstat (limited to 'src/System/IO/MMap/Fixed.hs')
-rw-r--r-- | src/System/IO/MMap/Fixed.hs | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/System/IO/MMap/Fixed.hs b/src/System/IO/MMap/Fixed.hs new file mode 100644 index 00000000..1e83c350 --- /dev/null +++ b/src/System/IO/MMap/Fixed.hs | |||
@@ -0,0 +1,212 @@ | |||
1 | -- TODO pprint | ||
2 | -- TODO see if this IntervalMap is overkill: Interval dataty have 4 constrs | ||
3 | -- TODO clarify lifetime in docs | ||
4 | -- TODO use madvise | ||
5 | -- TODO unmap selected interval | ||
6 | -- TODO tests | ||
7 | -- TODO benchmarks | ||
8 | -- TODO unmap overlapped regions | ||
9 | -- [A] TODO lazy mapping for 32 bit arch; | ||
10 | -- we need tricky algorithm and a lot of perf tests | ||
11 | -- TODO use memmove in write bytes | ||
12 | -- TODO write elem, write byte, read byte | ||
13 | -- | | ||
14 | -- Copyright : (c) Sam T. 2013 | ||
15 | -- License : MIT | ||
16 | -- Maintainer : pxqr.sta@gmail.com | ||
17 | -- Stability : experimental | ||
18 | -- Portability : portable | ||
19 | -- | ||
20 | -- This library provides mechanism to mmap files to fixed address | ||
21 | -- with fine-grained control. Hovewer, instead of using MAP_FIXED we | ||
22 | -- create our own address space upon virtual address space. If you | ||
23 | -- would like you could call this space as "fixed address space". | ||
24 | -- | ||
25 | -- This solves a few problems: | ||
26 | -- | ||
27 | -- * Page already in use. If you mmap one file at 0..x addresses and | ||
28 | -- want to map second file to x..y addresses using MAP_FIXED you | ||
29 | -- can get in troubles: page might be mapped already. Raw call to | ||
30 | -- mmap will silently unmap x..y addresses and then mmap our second | ||
31 | -- file. So here we have extra unmap we would like to avoid. | ||
32 | -- | ||
33 | -- * Page boundaries. If you mmap one file at x..x+1 you could | ||
34 | -- not map next file to say addresses x+1..x+2. | ||
35 | -- | ||
36 | -- Internally we make ordinary call to mmap to map a file and then | ||
37 | -- using /interval map/ we map fixed address space to virtual | ||
38 | -- address space. It takes TODO time in TODO cases. | ||
39 | -- | ||
40 | -- Basically this library could be used when we need coalesce | ||
41 | -- several files in arbitrary way. We could map at any position as | ||
42 | -- long as offset + size fit in 'Int'. | ||
43 | -- | ||
44 | -- For other details see: | ||
45 | -- | ||
46 | -- > http://hackage.haskell.org/package/mmap | ||
47 | -- > man mmap | ||
48 | -- | ||
49 | {-# LANGUAGE RecordWildCards #-} | ||
50 | module System.IO.MMap.Fixed | ||
51 | ( -- * Intervals | ||
52 | FixedOffset, FileOffset, FixedInterval, FileInterval | ||
53 | , interval, fileInterval | ||
54 | |||
55 | -- * Construction | ||
56 | , Fixed, Bytes | ||
57 | , System.IO.MMap.Fixed.empty, insertTo | ||
58 | , coalesceFiles | ||
59 | |||
60 | -- ** Specialized 'insertTo' | ||
61 | , mmapTo, mallocTo | ||
62 | , lookupRegion | ||
63 | |||
64 | -- * Query | ||
65 | , upperAddr | ||
66 | |||
67 | -- * Access | ||
68 | , viewBytes, readBytes, writeBytes | ||
69 | , readElem, writeElem | ||
70 | ) where | ||
71 | |||
72 | import Data.ByteString.Lazy as Lazy | ||
73 | import Data.ByteString.Lazy.Internal as Lazy | ||
74 | import Data.ByteString.Internal as B | ||
75 | import Data.List as L | ||
76 | import Data.Int | ||
77 | import Data.IntervalMap.Strict as M | ||
78 | import Data.IntervalMap.Interval | ||
79 | import System.IO.MMap | ||
80 | import Foreign | ||
81 | |||
82 | |||
83 | type FixedOffset = Int | ||
84 | type FileOffset = Int64 | ||
85 | type Size = Int | ||
86 | |||
87 | |||
88 | type FileInterval = (FileOffset, Size) | ||
89 | type FixedInterval = Interval FixedOffset | ||
90 | |||
91 | |||
92 | interval :: FixedOffset -> Size -> FixedInterval | ||
93 | interval off s = IntervalCO off (off + fromIntegral (max 0 s)) | ||
94 | {-# INLINE interval #-} | ||
95 | |||
96 | fileInterval :: FileOffset -> Size -> FileInterval | ||
97 | fileInterval off s = (off, s) | ||
98 | {-# INLINE fileInterval #-} | ||
99 | |||
100 | intervalSize :: FixedInterval -> Size | ||
101 | intervalSize i = upperBound i - lowerBound i | ||
102 | {-# INLINE intervalSize #-} | ||
103 | |||
104 | |||
105 | type Bytes = (ForeignPtr Word8, Size) | ||
106 | |||
107 | type FixedMap = IntervalMap FixedOffset Bytes | ||
108 | |||
109 | newtype Fixed = Fixed { imap :: FixedMap } | ||
110 | |||
111 | instance Show Fixed where | ||
112 | show = show . M.toList . imap | ||
113 | |||
114 | |||
115 | mapIM :: (FixedMap -> FixedMap) -> Fixed -> Fixed | ||
116 | mapIM f s = s { imap = f (imap s) } | ||
117 | |||
118 | empty :: Fixed | ||
119 | empty = Fixed M.empty | ||
120 | |||
121 | coalesceFiles :: [(FilePath, Int)] -> IO Fixed | ||
122 | coalesceFiles = go 0 System.IO.MMap.Fixed.empty | ||
123 | where | ||
124 | go _ s [] = return s | ||
125 | go offset s ((path, bsize) : xs) = do | ||
126 | s' <- mmapTo path (0, bsize) offset s | ||
127 | go (offset + bsize) s' xs | ||
128 | |||
129 | upperAddr :: Fixed -> FixedOffset | ||
130 | upperAddr = upperBound . fst . findLast . imap | ||
131 | |||
132 | insertTo :: FixedInterval -> Bytes -> Fixed -> Fixed | ||
133 | insertTo fi mm = mapIM (M.insert fi mm) | ||
134 | {-# INLINE insertTo #-} | ||
135 | |||
136 | mmapTo :: FilePath -> FileInterval -> FixedOffset -> Fixed -> IO Fixed | ||
137 | mmapTo path mrange to s = do | ||
138 | (fptr, offset, fsize) <- mmapFileForeignPtr path ReadWriteEx (Just mrange) | ||
139 | |||
140 | let fixed = interval to fsize | ||
141 | let mmaped = (fptr, offset) | ||
142 | |||
143 | return $ insertTo fixed mmaped s | ||
144 | |||
145 | mallocTo :: FixedInterval -> Fixed -> IO Fixed | ||
146 | mallocTo fi s = do | ||
147 | let bsize = intervalSize fi | ||
148 | fptr <- mallocForeignPtrBytes bsize | ||
149 | return (insertTo fi (fptr, 0) s) | ||
150 | |||
151 | lookupRegion :: FixedOffset -> Fixed -> Maybe B.ByteString | ||
152 | lookupRegion offset Fixed {..} = | ||
153 | case intersecting imap $ IntervalCO offset (succ offset) of | ||
154 | [(i, (fptr, off))] -> let s = upperBound i - lowerBound i | ||
155 | in Just $ fromForeignPtr fptr off (max 0 s) | ||
156 | _ -> Nothing | ||
157 | |||
158 | -- | Note: this is unsafe operation. | ||
159 | viewBytes :: FixedInterval -> Fixed -> Lazy.ByteString | ||
160 | viewBytes fi s = fromChunks $ L.map mk $ (imap s `intersecting` fi) | ||
161 | where | ||
162 | mk (i, (fptr, offset)) = | ||
163 | let dropB = max 0 (lowerBound fi - lowerBound i) | ||
164 | dropT = max 0 (upperBound i - upperBound fi) | ||
165 | bsize = intervalSize i - (dropT + dropB) | ||
166 | in fromForeignPtr fptr (offset + dropB) bsize | ||
167 | |||
168 | |||
169 | readBytes :: FixedInterval -> Fixed -> IO Lazy.ByteString | ||
170 | readBytes fi s = let c = Lazy.copy (viewBytes fi s) in mkCopy c >> return c | ||
171 | {-# INLINE readBytes #-} | ||
172 | |||
173 | writeBytes :: FixedInterval -> Lazy.ByteString -> Fixed -> IO () | ||
174 | writeBytes fi bs s = bscpy (viewBytes fi s) bs | ||
175 | {-# INLINE writeBytes #-} | ||
176 | |||
177 | -- | Note: this operation takes O(log(files count)) time, if possible | ||
178 | -- use readBytes. | ||
179 | readElem :: Storable a => Fixed -> FixedOffset -> IO a | ||
180 | readElem s offset = go undefined | ||
181 | where | ||
182 | go :: Storable a => a -> IO a | ||
183 | go dont_touch = do | ||
184 | let bsize = sizeOf dont_touch | ||
185 | let PS fptr off _ = Lazy.toStrict (viewBytes (interval offset bsize) s) | ||
186 | withForeignPtr fptr $ \ ptr -> peekByteOff ptr off | ||
187 | |||
188 | writeElem :: Storable a => Fixed -> FixedOffset -> a -> IO () | ||
189 | writeElem s offset x = do | ||
190 | let bsize = sizeOf x | ||
191 | let PS fptr off _ = Lazy.toStrict (viewBytes (interval offset bsize) s) | ||
192 | withForeignPtr fptr $ \ptr -> pokeByteOff ptr off x | ||
193 | |||
194 | |||
195 | mkCopy :: Lazy.ByteString -> IO () | ||
196 | mkCopy Empty = return () | ||
197 | mkCopy (Chunk _ x) = mkCopy x | ||
198 | |||
199 | bscpy :: Lazy.ByteString -> Lazy.ByteString -> IO () | ||
200 | bscpy (PS _ _ 0 `Chunk` dest_rest) src = bscpy dest_rest src | ||
201 | bscpy dest (PS _ _ 0 `Chunk` src_rest) = bscpy dest src_rest | ||
202 | bscpy (PS dest_fptr dest_off dest_size `Chunk` dest_rest) | ||
203 | (PS src_fptr src_off src_size `Chunk` src_rest) | ||
204 | = do let csize = min dest_size src_size | ||
205 | withForeignPtr dest_fptr $ \dest_ptr -> | ||
206 | withForeignPtr src_fptr $ \src_ptr -> | ||
207 | memcpy (dest_ptr `advancePtr` dest_off) | ||
208 | (src_ptr `advancePtr` src_off) | ||
209 | (fromIntegral csize) -- TODO memmove? | ||
210 | bscpy (PS dest_fptr (dest_off + csize) (dest_size - csize) `Chunk` dest_rest) | ||
211 | (PS src_fptr (src_off + csize) (src_size - csize) `Chunk` src_rest) | ||
212 | bscpy _ _ = return () \ No newline at end of file | ||