Reimplement readFileCmd using a less memory-hungry expression.

When converting a binary file to a Cryptol expression, we now use
`split <large-literal>` instead of a list of 8-bit numeric literals.

To convert from `ByteString` to `Integer`, we use a balanced binary
fold to reduce the amount of allocation (and runtime) from O(n^2) to
O(n log n).

Fixes #346.
This commit is contained in:
Brian Huffman 2019-10-28 14:45:43 -07:00
parent 3ea5e9e51c
commit 42ab1ea48c

View File

@ -86,7 +86,7 @@ import Control.Monad.IO.Class(liftIO)
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8 import qualified Data.ByteString.Char8 as BS8
import Data.Bits ((.&.)) import Data.Bits (shiftL, (.&.), (.|.))
import Data.Char (isSpace,isPunctuation,isSymbol,isAlphaNum,isAscii) import Data.Char (isSpace,isPunctuation,isSymbol,isAlphaNum,isAscii)
import Data.Function (on) import Data.Function (on)
import Data.List (intercalate, nub, sortBy, partition, isPrefixOf,intersperse) import Data.List (intercalate, nub, sortBy, partition, isPrefixOf,intersperse)
@ -739,12 +739,37 @@ readFileCmd :: FilePath -> REPL ()
readFileCmd fp = do readFileCmd fp = do
bytes <- replReadFile fp (\err -> rPutStrLn (show err) >> return Nothing) bytes <- replReadFile fp (\err -> rPutStrLn (show err) >> return Nothing)
case bytes of case bytes of
Nothing -> return () Nothing -> return ()
Just bs -> Just bs ->
do pm <- getPrimMap do pm <- getPrimMap
let expr = T.eString pm (map (toEnum . fromIntegral) (BS.unpack bs)) let val = byteStringToInteger bs
ty = T.tString (BS.length bs) let len = BS.length bs
bindItVariable ty expr let split = T.ePrim pm (M.packIdent "split")
let number = T.ePrim pm (M.packIdent "number")
let f = T.EProofApp (foldl T.ETApp split [T.tNum len, T.tNum (8::Integer), T.tBit])
let t = T.tWord (T.tNum (toInteger len * 8))
let x = T.EProofApp (T.ETApp (T.ETApp number (T.tNum val)) t)
let expr = T.EApp f x
bindItVariable (T.tString len) expr
-- | Convert a 'ByteString' (big-endian) of length @n@ to an 'Integer'
-- with @8*n@ bits. This function uses a balanced binary fold to
-- achieve /O(n log n)/ total memory allocation and run-time, in
-- contrast to the /O(n^2)/ that would be required by a naive
-- left-fold.
byteStringToInteger :: BS.ByteString -> Integer
-- byteStringToInteger = BS.foldl' (\a b -> a `shiftL` 8 .|. toInteger b) 0
byteStringToInteger bs
| l == 0 = 0
| l == 1 = toInteger (BS.head bs)
| otherwise = x1 `shiftL` (l2 * 8) .|. x2
where
l = BS.length bs
l1 = l `div` 2
l2 = l - l1
(bs1, bs2) = BS.splitAt l1 bs
x1 = byteStringToInteger bs1
x2 = byteStringToInteger bs2
writeFileCmd :: FilePath -> String -> REPL () writeFileCmd :: FilePath -> String -> REPL ()
writeFileCmd file str = do writeFileCmd file str = do