libriscv-0.1.0.0: lib/LibRISCV/Effects/Operations/Default/Machine/Memory.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TupleSections #-}
-- | Provides an implementatio of a byte-addressable memory, intended for internal
-- usage in interpreters for the 'LibRISCV.Effects.Operations.Operations' effect.
module LibRISCV.Effects.Operations.Default.Machine.Memory (
Memory,
HalfStorage (..),
WordStorage (..),
mkMemory,
memSize,
loadByte,
loadHalf,
loadWord,
storeByte,
storeHalf,
storeWord,
storeByteString,
mkWord,
mkBytes,
) where
import Data.Array.IO (
MArray (getBounds, newArray_),
readArray,
writeArray,
)
import Data.BitVector (BV, bitVec)
import Data.Bits (Bits (shift, shiftR, (.&.), (.|.)))
import qualified Data.ByteString.Lazy as BSL
import Data.Int ()
import Data.Word (Word16, Word32, Word8)
import LibRISCV
-- | Since the memory is byte-addressable it requires converting values of a
-- larger size to bytes. This type class is responsible for a conversion of
-- 16-bit values (halfs). That is, it converts halfs to bytes (and vice versa)
-- in little endian.
class HalfStorage halfType byteType where
-- | Convert a list of two bytes to a single half.
toHalf :: [byteType] -> halfType
-- | Convert a single half to a list of two bytes.
halfToBytes :: halfType -> [byteType]
-- | Similar to 'HalfStorage' but handles conversion of 32-bit values (words).
class WordStorage wordType byteType where
-- | Convert a list of four bytes to a single word.
toWord :: [byteType] -> wordType
-- | Convert a single word to a list of four bytes.
wordToBytes :: wordType -> [byteType]
instance WordStorage Word32 Word8 where
toWord = mkWord
wordToBytes = mkBytes
instance HalfStorage Word16 Word8 where
toHalf = fromIntegral . mkWord
halfToBytes = mkBytes . fromIntegral
instance WordStorage BV Word8 where
toWord = (bitVec 32 :: Word32 -> BV) . fromIntegral . mkWord
wordToBytes = mkBytes . fromIntegral
instance HalfStorage BV Word8 where
toHalf = bitVec 16 . mkWord
halfToBytes = mkBytes . fromIntegral
-- | Converts a list of bytes to a 'Word32' in little endian.
mkWord :: [Word8] -> Word32
mkWord bytes =
foldl
(\x (byte, idx) -> (fromIntegral byte `shift` (idx * 8)) .|. x)
0
$ zip bytes [0 .. length bytes - 1]
-- | Split a 32-bit word into four octets in little endian.
mkBytes :: Word32 -> [Word8]
mkBytes w = map (\off -> fromIntegral $ shiftR w off .&. 0xff) offs
where
offs = take 4 $ iterate (+ 8) 0
------------------------------------------------------------------------
-- | Representation of a byte-addressable memory. The type is parameterized
-- over an array implementation (such as 'Data.Array.IO.IOUArray') and a
-- generic value type (used to represent instruction operands).
data Memory t a = Memory
{ memStart :: Address
, memBytes :: t Address a
}
-- | Create a new memory of the given size starting at the given address.
mkMemory :: (MArray t a IO) => Address -> Word32 -> IO (Memory t a)
mkMemory addr size = fmap (Memory addr) (newArray_ (0, size - 1))
-- Translate global address to a memory-local address.
toMemAddr :: Memory t a -> Address -> Address
toMemAddr mem addr = addr - (memStart mem)
-- | Returns the size of the memory in bytes.
memSize :: (MArray t a IO) => Memory t a -> IO Word32
memSize = fmap ((+ 1) . snd) . getBounds . memBytes
------------------------------------------------------------------------
-- TODO: Only provide load/store, remove all wrapper functions. Could
-- use the 'Size' type from the operations language for this purpose too.
-- | Load a single byte from memory at the given address.
loadByte :: (MArray t a IO) => Memory t a -> Address -> IO a
loadByte mem = readArray (memBytes mem) . toMemAddr mem
load :: (MArray t a IO) => ([a] -> b) -> Word32 -> Memory t a -> Address -> IO b
load proc bytesize mem addr =
proc
<$> mapM (\off -> loadByte mem $ addr + off) [0 .. (bytesize - 1)]
-- | Load a half (16-bit) from memory at the given address.
loadHalf :: (MArray t a IO, HalfStorage b a) => Memory t a -> Address -> IO b
loadHalf = load toHalf 2
-- | Load a word (32-bit) from memory at the given address.
loadWord :: (MArray t a IO, WordStorage b a) => Memory t a -> Address -> IO b
loadWord = load toWord 4
-- | Store a single byte in memory.
storeByte :: (MArray t a IO) => Memory t a -> Address -> a -> IO ()
storeByte mem addr = writeArray (memBytes mem) $ toMemAddr mem addr
store :: (MArray t a IO) => (b -> [a]) -> Word32 -> Memory t a -> Address -> b -> IO ()
store proc bytesize mem addr =
mapM_ (\(off, val) -> storeByte mem (addr + off) val)
. zip [0 .. (bytesize - 1)]
. proc
-- | Store a half (16-bit) in memory.
storeHalf :: (MArray t a IO, HalfStorage b a) => Memory t a -> Address -> b -> IO ()
storeHalf = store halfToBytes 2
-- | Store a word (32-bit) in memory.
storeWord :: (MArray t a IO, WordStorage b a) => Memory t a -> Address -> b -> IO ()
storeWord = store wordToBytes 4
-- | Write a 'BSL.ByteString' to memory in little endian byteorder. Expects a
-- function to convert single bytes ('Word8') to the chosen value
-- representation, a t'Memory', as well the 'Address' where the string should be
-- stored and the 'BSL.ByteString' itself.
storeByteString ::
(MArray t a IO) =>
(Word8 -> a) ->
Memory t a ->
Address ->
BSL.ByteString ->
IO ()
storeByteString convert mem addr bs =
mapM_ (\(off, val) -> storeByte mem (addr + off) (convert val)) $
zip [0 ..] $
BSL.unpack bs